Move match_portlist() from filter.c to urlmatch.c.
[privoxy.git] / filters.c
index 1c9b254..d56bf1f 100644 (file)
--- a/filters.c
+++ b/filters.c
@@ -1,4 +1,4 @@
-const char filters_rcs[] = "$Id: filters.c,v 1.81 2007/03/05 14:40:53 fabiankeil Exp $";
+const char filters_rcs[] = "$Id: filters.c,v 1.90 2007/09/02 12:44:17 fabiankeil Exp $";
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/filters.c,v $
@@ -40,6 +40,43 @@ const char filters_rcs[] = "$Id: filters.c,v 1.81 2007/03/05 14:40:53 fabiankeil
  *
  * Revisions   :
  *    $Log: filters.c,v $
+ *    Revision 1.90  2007/09/02 12:44:17  fabiankeil
+ *    Remove newline at the end of a log_error() message.
+ *
+ *    Revision 1.89  2007/08/05 13:42:23  fabiankeil
+ *    #1763173 from Stefan Huehner: declare some more functions static.
+ *
+ *    Revision 1.88  2007/06/01 16:41:11  fabiankeil
+ *    Add forward-override{} to change the forwarding settings through
+ *    action sections. This is mainly interesting to forward different
+ *    clients differently (for example based on User-Agent or request
+ *    origin).
+ *
+ *    Revision 1.87  2007/04/30 15:53:10  fabiankeil
+ *    Make sure filters with dynamic jobs actually use them.
+ *
+ *    Revision 1.86  2007/04/30 15:03:28  fabiankeil
+ *    - Introduce dynamic pcrs jobs that can resolve variables.
+ *    - Don't run redirect functions more than once,
+ *      unless they are activated more than once.
+ *
+ *    Revision 1.85  2007/03/21 12:24:47  fabiankeil
+ *    - Log the content size after decompression in decompress_iob()
+ *      instead of pcrs_filter_response().
+ *
+ *    Revision 1.84  2007/03/20 15:16:34  fabiankeil
+ *    Use dedicated header filter actions instead of abusing "filter".
+ *    Replace "filter-client-headers" and "filter-client-headers"
+ *    with "server-header-filter" and "client-header-filter".
+ *
+ *    Revision 1.83  2007/03/17 15:20:05  fabiankeil
+ *    New config option: enforce-blocks.
+ *
+ *    Revision 1.82  2007/03/13 11:28:43  fabiankeil
+ *    - Fix port handling in acl_addr() and use a temporary acl spec
+ *      copy so error messages don't contain a truncated version.
+ *    - Log size of iob before and after decompression.
+ *
  *    Revision 1.81  2007/03/05 14:40:53  fabiankeil
  *    - Cosmetical changes for LOG_LEVEL_RE_FILTER messages.
  *    - Hide the "Go there anyway" link for blocked CONNECT
@@ -564,6 +601,7 @@ const char filters_rcs[] = "$Id: filters.c,v 1.81 2007/03/05 14:40:53 fabiankeil
 #include "list.h"
 #include "deanimate.h"
 #include "urlmatch.h"
+#include "loaders.h"
 
 #ifdef _WIN32
 #include "win32.h"
@@ -736,86 +774,6 @@ int acl_addr(const char *aspec, struct access_control_addr *aca)
 #endif /* def FEATURE_ACL */
 
 
-/*********************************************************************
- *
- * Function    :  match_portlist
- *
- * Description :  Check if a given number is covered by a comma
- *                separated list of numbers and ranges (a,b-c,d,..)
- *
- * Parameters  :
- *          1  :  portlist = String with list
- *          2  :  port = port to check
- *
- * Returns     :  0 => no match
- *                1 => match
- *
- *********************************************************************/
-int match_portlist(const char *portlist, int port)
-{
-   char *min, *max, *next, *portlist_copy;
-
-   min = next = portlist_copy = strdup(portlist);
-
-   /*
-    * Zero-terminate first item and remember offset for next
-    */
-   if (NULL != (next = strchr(portlist_copy, (int) ',')))
-   {
-      *next++ = '\0';
-   }
-
-   /*
-    * Loop through all items, checking for match
-    */
-   while(min)
-   {
-      if (NULL == (max = strchr(min, (int) '-')))
-      {
-         /*
-          * No dash, check for equality
-          */
-         if (port == atoi(min))
-         {
-            free(portlist_copy);
-            return(1);
-         }
-      }
-      else
-      {
-         /*
-          * This is a range, so check if between min and max,
-          * or, if max was omitted, between min and 65K
-          */
-         *max++ = '\0';
-         if(port >= atoi(min) && port <= (atoi(max) ? atoi(max) : 65535))
-         {
-            free(portlist_copy);
-            return(1);
-         }
-
-      }
-
-      /*
-       * Jump to next item
-       */
-      min = next;
-
-      /*
-       * Zero-terminate next item and remember offset for n+1
-       */
-      if ((NULL != next) && (NULL != (next = strchr(next, (int) ','))))
-      {
-         *next++ = '\0';
-      }
-   }
-
-   free(portlist_copy);
-   return 0;
-
-}
-
-
 /*********************************************************************
  *
  * Function    :  block_url
@@ -1030,7 +988,15 @@ struct http_response *block_url(struct client_state *csp)
 
 #ifdef FEATURE_FORCE_LOAD
       err = map(exports, "force-prefix", 1, FORCE_PREFIX, 1);
-      if (csp->http->ssl != 0 || 0 == strcmpic(csp->http->gpc, "connect"))
+      /*
+       * Export the force conditional block killer if
+       *
+       * - Privoxy was compiled without FEATURE_FORCE_LOAD, or
+       * - Privoxy is configured to enforce blocks, or
+       * - it's a CONNECT request and enforcing wouldn't work anyway.
+       */
+      if ((csp->config->feature_flags & RUNTIME_FEATURE_ENFORCE_BLOCKS)
+       || (0 == strcmpic(csp->http->gpc, "connect")))
 #endif /* ndef FEATURE_FORCE_LOAD */
       {
          err = map_block_killer(exports, "force-support");
@@ -1179,12 +1145,17 @@ struct http_response *trust_url(struct client_state *csp)
    }
 
    /*
-    * Export the force prefix or the force conditional block killer
+    * Export the force conditional block killer if
+    *
+    * - Privoxy was compiled without FEATURE_FORCE_LOAD, or
+    * - Privoxy is configured to enforce blocks, or
+    * - it's a CONNECT request and enforcing wouldn't work anyway.
     */
 #ifdef FEATURE_FORCE_LOAD
-   if (0 == strcmpic(csp->http->gpc, "connect"))
+   if ((csp->config->feature_flags & RUNTIME_FEATURE_ENFORCE_BLOCKS)
+    || (0 == strcmpic(csp->http->gpc, "connect")))
    {
-       err = map_block_killer(exports, "force-support");
+      err = map_block_killer(exports, "force-support");
    }
    else
    {
@@ -1216,58 +1187,76 @@ struct http_response *trust_url(struct client_state *csp)
 }
 #endif /* def FEATURE_TRUST */
 
+
 /*********************************************************************
  *
- * Function    :  execute_single_pcrs_command
+ * Function    :  compile_dynamic_pcrs_job_list
  *
- * Description :  Apply single pcrs command to the subject.
- *                The subject itself is left untouched, memory for the result
- *                is malloc()ed and it is the caller's responsibility to free
- *                the result when it's no longer needed.
+ * Description :  Compiles a dynamic pcrs job list (one with variables
+ *                resolved at request time)
  *
  * Parameters  :
- *          1  :  subject = the subject (== original) string
- *          2  :  pcrs_command = the pcrs command as string (s@foo@bar@) 
- *          3  :  hits = int* for returning  the number of modifications 
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  b = The filter list to compile
  *
  * Returns     :  NULL in case of errors, otherwise the
- *                result of the pcrs command.  
+ *                pcrs job list.  
  *
  *********************************************************************/
-char *execute_single_pcrs_command(char *subject, const char *pcrs_command, int *hits)
+pcrs_job *compile_dynamic_pcrs_job_list(const struct client_state *csp, const struct re_filterfile_spec *b)
 {
-   int error;
-   size_t size;
-   char *result = NULL;
-   pcrs_job *job;
+   struct list_entry *pattern;
+   pcrs_job *job_list = NULL;
+   pcrs_job *dummy = NULL;
+   pcrs_job *lastjob = NULL;
+   int error = 0;
 
-   assert(subject);
-   assert(pcrs_command);
-
-   *hits = 0;
-   size = strlen(subject);
-
-   if (NULL == (job = pcrs_compile_command(pcrs_command, &error)))
-   {
-      log_error(LOG_LEVEL_ERROR, "Failed to compile pcrs command \"%s\". Error: %d.",
-         pcrs_command, error);
-   }
-   else if ((*hits = pcrs_execute(job, subject, size, &result, &size)) < 0)
+   const struct pcrs_variable variables[] =
    {
-      log_error(LOG_LEVEL_ERROR, "Failed to execute pcrs command: %s", pcrs_strerror(*hits));
-      *hits = 0;
-      freez(result);
-   }
+      {"url",    csp->http->url,   1},
+      {"path",   csp->http->path,  1},
+      {"host",   csp->http->host,  1},
+      {"origin", csp->ip_addr_str, 1},
+      {NULL,     NULL,             1}
+   };
 
-   if (job)
+   for (pattern = b->patterns->first; pattern != NULL; pattern = pattern->next)
    {
-      job = pcrs_free_job(job);
-   }
+      assert(pattern->str != NULL);
 
-   return result;
+      dummy = pcrs_compile_dynamic_command(pattern->str, variables, &error);
+      if (NULL == dummy)
+      {
+         assert(error < 0);
+         log_error(LOG_LEVEL_ERROR,
+            "Adding filter job \'%s\' to dynamic filter %s failed: %s",
+            pattern->str, b->name, pcrs_strerror(error));
+         continue;
+      }
+      else
+      {
+         if (error == PCRS_WARN_TRUNCATION)
+         {
+            log_error(LOG_LEVEL_ERROR,
+               "At least one of the variables in \'%s\' had to "
+               "be truncated before compilation", pattern->str);
+         }
+         if (job_list == NULL)
+         {
+            job_list = dummy;
+         }
+         else
+         {
+            lastjob->next = dummy;
+         }
+         lastjob = dummy;
+      }
+   }
 
+   return job_list;
 }
 
+
 /*********************************************************************
  *
  * Function    :  rewrite_url
@@ -1293,13 +1282,20 @@ char *rewrite_url(char *old_url, const char *pcrs_command)
    assert(old_url);
    assert(pcrs_command);
 
-   new_url = execute_single_pcrs_command(old_url, pcrs_command, &hits);
+   new_url = pcrs_execute_single_command(old_url, pcrs_command, &hits);
 
    if (hits == 0)
    {
       log_error(LOG_LEVEL_REDIRECTS,
          "pcrs command \"%s\" didn't change \"%s\".",
-         pcrs_command, old_url, new_url);
+         pcrs_command, old_url);
+      freez(new_url);
+   }
+   else if (hits < 0)
+   {
+      log_error(LOG_LEVEL_REDIRECTS,
+         "executing pcrs command \"%s\" to rewrite %s failed: %s",
+         pcrs_command, old_url, pcrs_strerror(hits));
       freez(new_url);
    }
    else if (strncmpic(new_url, "http://", 7) && strncmpic(new_url, "https://", 8))
@@ -1484,7 +1480,19 @@ struct http_response *redirect_url(struct client_state *csp)
       new_url = get_last_url(old_url, redirect_mode);
       freez(old_url);
    }
+
+   /*
+    * Disable redirect checkers, so that they
+    * will be only run more than once if the user
+    * also enables them through tags.
+    *
+    * From a performance point of view
+    * it doesn't matter, but the duplicated
+    * log messages are annoying.
+    */
+   csp->action->flags &= ~ACTION_FAST_REDIRECTS;
 #endif /* def FEATURE_FAST_REDIRECTS */
+   csp->action->flags &= ~ACTION_REDIRECT;
 
    /* Did any redirect action trigger? */   
    if (new_url)
@@ -1819,9 +1827,6 @@ char *pcrs_filter_response(struct client_state *csp)
           csp->content_type &= ~CT_DEFLATE;
           return(NULL);
       }
-      log_error(LOG_LEVEL_RE_FILTER,
-         "Decompression successful. Old size: %d, new size: %d.",
-         size, csp->iob->eod - csp->iob->cur);
 
       /*
        * Decompression gives us a completely new iob,
@@ -1858,6 +1863,12 @@ char *pcrs_filter_response(struct client_state *csp)
     */
    for (b = fl->f; b; b = b->next)
    {
+      if (b->type != FT_CONTENT_FILTER)
+      {
+         /* Skip header filters */
+         continue;
+      }
+
       for (filtername = csp->action->multi[ACTION_MULTI_FILTER]->first;
            filtername ; filtername = filtername->next)
       {
@@ -1866,8 +1877,11 @@ char *pcrs_filter_response(struct client_state *csp)
             int current_hits = 0; /* Number of hits caused by this filter */
             int job_number   = 0; /* Which job we're currently executing  */
             int job_hits     = 0; /* How many hits the current job caused */
+            pcrs_job *joblist = b->joblist;
+
+            if (b->dynamic) joblist = compile_dynamic_pcrs_job_list(csp, b);
 
-            if ( NULL == b->joblist )
+            if (NULL == joblist)
             {
                log_error(LOG_LEVEL_RE_FILTER, "Filter %s has empty joblist. Nothing to do.", b->name);
                continue;
@@ -1875,7 +1889,7 @@ char *pcrs_filter_response(struct client_state *csp)
 
             prev_size = size;
             /* Apply all jobs from the joblist */
-            for (job = b->joblist; NULL != job; job = job->next)
+            for (job = joblist; NULL != job; job = job->next)
             {
                job_number++;
                job_hits = pcrs_execute(job, old, size, &new, &size);
@@ -1918,6 +1932,8 @@ char *pcrs_filter_response(struct client_state *csp)
                }
             }
 
+            if (b->dynamic) pcrs_free_joblist(joblist);
+
             log_error(LOG_LEVEL_RE_FILTER,
                "filtering %s%s (size %d) with \'%s\' produced %d hits (new size %d).",
                csp->http->hostport, csp->http->path, prev_size, b->name, current_hits, size);
@@ -2158,7 +2174,7 @@ size_t remove_chunked_transfer_coding(char *buffer, const size_t size)
    }
 
    /* FIXME: Should this get its own loglevel? */
-   log_error(LOG_LEVEL_RE_FILTER, "De-chunking successful. Shrunk from %d to %d\n", size, newsize);
+   log_error(LOG_LEVEL_RE_FILTER, "De-chunking successful. Shrunk from %d to %d", size, newsize);
    return(newsize);
 
 }
@@ -2234,12 +2250,156 @@ void apply_url_actions(struct current_action_spec *action,
 }
 
 
+/*********************************************************************
+ *
+ * Function    :  get_forward_override_settings
+ *
+ * Description :  Returns forward settings as specified with the
+ *                forward-override{} action. forward-override accepts
+ *                forward lines similar to the one used in the
+ *                configuration file, but without the URL pattern.
+ *
+ *                For example:
+ *
+ *                   forward / .
+ *
+ *                in the configuration file can be replaced with
+ *                the action section:
+ *
+ *                 {+forward-override{forward .}}
+ *                 /
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns     :  Pointer to forwarding structure in case of success.
+ *                Invalid syntax is fatal.
+ *
+ *********************************************************************/
+static const struct forward_spec *get_forward_override_settings(struct client_state *csp)
+{
+   const char *forward_override_line = csp->action->string[ACTION_STRING_FORWARD_OVERRIDE];
+   char forward_settings[BUFFER_SIZE];
+   char *http_parent = NULL;
+   /* variable names were chosen for consistency reasons. */
+   struct forward_spec *fwd = NULL;
+   int vec_count;
+   char *vec[3];
+
+   assert(csp->action->flags & ACTION_FORWARD_OVERRIDE);
+   /* Should be enforced by load_one_actions_file() */
+   assert(strlen(forward_override_line) < sizeof(forward_settings) - 1);
+
+   /* Create a copy ssplit can modify */
+   strlcpy(forward_settings, forward_override_line, sizeof(forward_settings));
+
+   if (NULL != csp->fwd)
+   {
+      /*
+       * XXX: Currently necessary to prevent memory
+       * leaks when the show-url-info cgi page is visited.
+       */
+      unload_forward_spec(csp->fwd);
+   }
+
+   /*
+    * allocate a new forward node, valid only for
+    * the lifetime of this request. Save its location
+    * in csp as well, so sweep() can free it later on.
+    */
+   fwd = csp->fwd = zalloc(sizeof(*fwd));
+   if (NULL == fwd)
+   {
+      log_error(LOG_LEVEL_FATAL,
+         "can't allocate memory for forward-override{%s}", forward_override_line);
+      /* Never get here - LOG_LEVEL_FATAL causes program exit */
+   }
+
+   vec_count = ssplit(forward_settings, " \t", vec, SZ(vec), 1, 1);
+   if ((vec_count == 2) && !strcasecmp(vec[0], "forward"))
+   {
+      fwd->type = SOCKS_NONE;
+
+      /* Parse the parent HTTP proxy host:port */
+      http_parent = vec[1];
+
+   }
+   else if (vec_count == 3)
+   {
+      char *socks_proxy = NULL;
+
+      if  (!strcasecmp(vec[0], "forward-socks4"))
+      {
+         fwd->type = SOCKS_4;
+         socks_proxy = vec[1];
+      }
+      else if (!strcasecmp(vec[0], "forward-socks4a"))
+      {
+         fwd->type = SOCKS_4A;
+         socks_proxy = vec[1];
+      }
+
+      if (NULL != socks_proxy)
+      {
+         /* Parse the SOCKS proxy host[:port] */
+         fwd->gateway_host = strdup(socks_proxy);
+
+         if (NULL != (socks_proxy = strchr(fwd->gateway_host, ':')))
+         {
+            *socks_proxy++ = '\0';
+            fwd->gateway_port = strtol(socks_proxy, NULL, 0);
+         }
+
+         if (fwd->gateway_port <= 0)
+         {
+            fwd->gateway_port = 1080;
+         }
+
+         http_parent = vec[2];
+      }
+   }
+
+   if (NULL == http_parent)
+   {
+      log_error(LOG_LEVEL_FATAL,
+         "Invalid forward-override syntax in: %s", forward_override_line);
+      /* Never get here - LOG_LEVEL_FATAL causes program exit */
+   }
+
+   /* Parse http forwarding settings */
+   if (strcmp(http_parent, ".") != 0)
+   {
+      fwd->forward_host = strdup(http_parent);
+
+      if (NULL != (http_parent = strchr(fwd->forward_host, ':')))
+      {
+         *http_parent++ = '\0';
+         fwd->forward_port = strtol(http_parent, NULL, 0);
+      }
+
+      if (fwd->forward_port <= 0)
+      {
+         fwd->forward_port = 8000;
+      }
+   }
+
+   assert (NULL != fwd);
+
+   log_error(LOG_LEVEL_CONNECT,
+      "Overriding forwarding settings based on \'%s\'", forward_override_line);
+
+   return fwd;
+}
+
+
 /*********************************************************************
  *
  * Function    :  forward_url
  *
  * Description :  Should we forward this to another proxy?
  *
+ *                XXX: Should be changed to make use of csp->fwd.
+ *
  * Parameters  :
  *          1  :  http = http_request request for current URL
  *          2  :  csp = Current client state (buffers, headers, etc...)
@@ -2253,6 +2413,11 @@ const struct forward_spec * forward_url(struct http_request *http,
    static const struct forward_spec fwd_default[1] = { FORWARD_SPEC_INITIALIZER };
    struct forward_spec *fwd = csp->config->forward;
 
+   if (csp->action->flags & ACTION_FORWARD_OVERRIDE)
+   {
+      return get_forward_override_settings(csp);
+   }
+
    if (fwd == NULL)
    {
       return fwd_default;