Move match_portlist() from filter.c to urlmatch.c.
[privoxy.git] / filters.c
index 36eccf4..d56bf1f 100644 (file)
--- a/filters.c
+++ b/filters.c
@@ -1,4 +1,4 @@
-const char filters_rcs[] = "$Id: filters.c,v 1.76 2007/01/01 19:36:37 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 $
@@ -13,7 +13,7 @@ const char filters_rcs[] = "$Id: filters.c,v 1.76 2007/01/01 19:36:37 fabiankeil
  *                   `jpeg_inspect_response', `execute_single_pcrs_command',
  *                   `rewrite_url', `get_last_url'
  *
- * Copyright   :  Written by and Copyright (C) 2001, 2004-2006 the SourceForge
+ * Copyright   :  Written by and Copyright (C) 2001, 2004-2007 the SourceForge
  *                Privoxy team. http://www.privoxy.org/
  *
  *                Based on the Internet Junkbuster originally written
@@ -40,6 +40,66 @@ const char filters_rcs[] = "$Id: filters.c,v 1.76 2007/01/01 19:36:37 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
+ *      requests where going there anyway doesn't work anyway.
+ *
+ *    Revision 1.80  2007/02/07 10:55:20  fabiankeil
+ *    - Save the reason for generating http_responses.
+ *    - Block (+block) with status code 403 instead of 404.
+ *    - Use a different kludge to remember a failed decompression.
+ *
+ *    Revision 1.79  2007/01/31 16:21:38  fabiankeil
+ *    Search for Max-Forwards headers case-insensitive,
+ *    don't generate the "501 unsupported" message for invalid
+ *    Max-Forwards values and don't increase negative ones.
+ *
+ *    Revision 1.78  2007/01/28 13:41:18  fabiankeil
+ *    - Add HEAD support to finish_http_response.
+ *    - Add error favicon to internal HTML error messages.
+ *
+ *    Revision 1.77  2007/01/12 15:36:44  fabiankeil
+ *    Mark *csp as immutable for is_untrusted_url()
+ *    and is_imageurl(). Closes FR 1237736.
+ *
  *    Revision 1.76  2007/01/01 19:36:37  fabiankeil
  *    Integrate a modified version of Wil Mahan's
  *    zlib patch (PR #895531).
@@ -541,6 +601,7 @@ const char filters_rcs[] = "$Id: filters.c,v 1.76 2007/01/01 19:36:37 fabiankeil
 #include "list.h"
 #include "deanimate.h"
 #include "urlmatch.h"
+#include "loaders.h"
 
 #ifdef _WIN32
 #include "win32.h"
@@ -632,20 +693,33 @@ int block_acl(struct access_control_addr *dst, struct client_state *csp)
  * Returns     :  0 => Ok, everything else is an error.
  *
  *********************************************************************/
-int acl_addr(char *aspec, struct access_control_addr *aca)
+int acl_addr(const char *aspec, struct access_control_addr *aca)
 {
-   int i, masklength, port;
+   int i, masklength;
+   long port;
    char *p;
+   char *acl_spec = NULL;
 
    masklength = 32;
    port       =  0;
 
-   if ((p = strchr(aspec, '/')) != NULL)
+   /*
+    * Use a temporary acl spec copy so we can log
+    * the unmodified original in case of parse errors.
+    */
+   acl_spec = strdup(aspec);
+   if (acl_spec == NULL)
    {
-      *p++ = '\0';
+      /* XXX: This will be logged as parse error. */
+      return(-1);
+   }
 
+   if ((p = strchr(acl_spec, '/')) != NULL)
+   {
+      *p++ = '\0';
       if (ijb_isdigit(*p) == 0)
       {
+         free(acl_spec);
          return(-1);
       }
       masklength = atoi(p);
@@ -653,26 +727,32 @@ int acl_addr(char *aspec, struct access_control_addr *aca)
 
    if ((masklength < 0) || (masklength > 32))
    {
+      free(acl_spec);
       return(-1);
    }
 
-   if ((p = strchr(aspec, ':')) != NULL)
+   if ((p = strchr(acl_spec, ':')) != NULL)
    {
+      char *endptr;
+
       *p++ = '\0';
+      port = strtol(p, &endptr, 10);
 
-      if (ijb_isdigit(*p) == 0)
+      if (port <= 0 || port > 65535 || *endptr != '\0')
       {
+         free(acl_spec);
          return(-1);
       }
-      port = atoi(p);
    }
 
-   aca->port = port;
+   aca->port = (unsigned long)port;
 
-   aca->addr = ntohl(resolve_hostname_to_ip(aspec));
+   aca->addr = ntohl(resolve_hostname_to_ip(acl_spec));
+   free(acl_spec);
 
    if (aca->addr == INADDR_NONE)
    {
+      /* XXX: This will be logged as parse error. */
       return(-1);
    }
 
@@ -694,86 +774,6 @@ int acl_addr(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
@@ -970,7 +970,7 @@ struct http_response *block_url(struct client_state *csp)
       }
       else
       {
-         rsp->status = strdup("404 Request for blocked URL");
+         rsp->status = strdup("403 Request for blocked URL");
       }
 
       if (rsp->status == NULL)
@@ -988,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)
+      /*
+       * 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");
@@ -1013,8 +1021,9 @@ struct http_response *block_url(struct client_state *csp)
          return cgi_error_memory();
       }
    }
+   rsp->reason = RSP_REASON_BLOCKED;
 
-   return finish_http_response(rsp);
+   return finish_http_response(csp, rsp);
 
 }
 
@@ -1136,10 +1145,22 @@ 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
-   err = map(exports, "force-prefix", 1, FORCE_PREFIX, 1);
+   if ((csp->config->feature_flags & RUNTIME_FEATURE_ENFORCE_BLOCKS)
+    || (0 == strcmpic(csp->http->gpc, "connect")))
+   {
+      err = map_block_killer(exports, "force-support");
+   }
+   else
+   {
+      err = map(exports, "force-prefix", 1, FORCE_PREFIX, 1);
+   }
 #else /* ifndef FEATURE_FORCE_LOAD */
    err = map_block_killer(exports, "force-support");
 #endif /* ndef FEATURE_FORCE_LOAD */
@@ -1160,63 +1181,82 @@ struct http_response *trust_url(struct client_state *csp)
       free_http_response(rsp);
       return cgi_error_memory();
    }
+   rsp->reason = RSP_REASON_UNTRUSTED;
 
-   return finish_http_response(rsp);
+   return finish_http_response(csp, rsp);
 }
 #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;
-
-   assert(subject);
-   assert(pcrs_command);
-
-   *hits = 0;
-   size = strlen(subject);
+   struct list_entry *pattern;
+   pcrs_job *job_list = NULL;
+   pcrs_job *dummy = NULL;
+   pcrs_job *lastjob = NULL;
+   int error = 0;
 
-   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
@@ -1242,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))
@@ -1433,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)
@@ -1462,8 +1521,10 @@ struct http_response *redirect_url(struct client_state *csp)
             free_http_response(rsp);
             return cgi_error_memory();
          }
+         rsp->reason = RSP_REASON_REDIRECTED;
          freez(new_url);
-         return finish_http_response(rsp);
+
+         return finish_http_response(csp, rsp);
       }
    }
 
@@ -1652,6 +1713,7 @@ int is_untrusted_url(const struct client_state *csp)
          return 0;
       }
    }
+
    return 1;
 }
 #endif /* def FEATURE_TRUST */
@@ -1745,22 +1807,26 @@ char *pcrs_filter_response(struct client_state *csp)
     * uncompress it first, adjusting size and iob->eod.
     * Note that decompression occurs after de-chunking.
     */
-   if (csp->content_type & CT_GZIP || csp->content_type & CT_DEFLATE)
+   if (csp->content_type & (CT_GZIP | CT_DEFLATE))
    {
       /* Notice that we at least tried to decompress. */
       if (JB_ERR_OK != decompress_iob(csp))
       {
          /*
           * We failed to decompress the data; there's no point
-          * in continuing since we can't filter. This is
-          * slightly tricky because we need to remember not to
-          * modify the Content-Encoding header later; using
-          * CT_TABOO flag is a kludge for this purpose.
+          * in continuing since we can't filter.
+          *
+          * XXX: Actually the Accept-Encoding header may
+          * just be incorrect in which case we could continue
+          * with filtering.
+          *
+          * Unset CT_GZIP and CT_DEFLATE to remember not
+          * to modify the Content-Encoding header later.
           */
-          csp->content_type |= CT_TABOO;
+          csp->content_type &= ~CT_GZIP;
+          csp->content_type &= ~CT_DEFLATE;
           return(NULL);
       }
-      log_error(LOG_LEVEL_RE_FILTER, "Decompressing successful");
 
       /*
        * Decompression gives us a completely new iob,
@@ -1797,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)
       {
@@ -1805,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 ( NULL == b->joblist )
+            if (b->dynamic) joblist = compile_dynamic_pcrs_job_list(csp, b);
+
+            if (NULL == joblist)
             {
                log_error(LOG_LEVEL_RE_FILTER, "Filter %s has empty joblist. Nothing to do.", b->name);
                continue;
@@ -1814,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);
@@ -1857,8 +1932,10 @@ char *pcrs_filter_response(struct client_state *csp)
                }
             }
 
+            if (b->dynamic) pcrs_free_joblist(joblist);
+
             log_error(LOG_LEVEL_RE_FILTER,
-               "re_filtering %s%s (size %d) with filter %s produced %d hits (new size %d).",
+               "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);
 
             hits += current_hits;
@@ -2097,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);
 
 }
@@ -2173,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...)
@@ -2192,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;
@@ -2237,29 +2463,42 @@ struct http_response *direct_response(struct client_state *csp)
    {
       for (p = csp->headers->first; (p != NULL) ; p = p->next)
       {
-         if (!strncmp("Max-Forwards:", p->str, 13)
-             && (*(p->str+13) != '\0') && (atoi(p->str+13) == 0))
+         if (!strncmpic("Max-Forwards:", p->str, 13))
          {
-            /* FIXME: We could handle at least TRACE here,
-               but that would require a verbatim copy of
-               the request which we don't have anymore */
+            unsigned int max_forwards;
 
-            log_error(LOG_LEVEL_HEADER, "Found Max-Forwards:0 in OPTIONS or TRACE request -- Returning 501");
-
-            /* Get mem for response or fail*/
-            if (NULL == (rsp = alloc_http_response()))
+            /*
+             * If it's a Max-Forwards value of zero,
+             * we have to intercept the request.
+             */
+            if (1 == sscanf(p->str+12, ": %u", &max_forwards) && max_forwards == 0)
             {
-               return cgi_error_memory();
-            }
+               /*
+                * FIXME: We could handle at least TRACE here,
+                * but that would require a verbatim copy of
+                * the request which we don't have anymore
+                */
+                log_error(LOG_LEVEL_HEADER,
+                  "Detected header \'%s\' in OPTIONS or TRACE request. Returning 501.",
+                  p->str);
+
+               /* Get mem for response or fail*/
+               if (NULL == (rsp = alloc_http_response()))
+               {
+                  return cgi_error_memory();
+               }
             
-            if (NULL == (rsp->status = strdup("501 Not Implemented")))
-            {
-               free_http_response(rsp);
-               return cgi_error_memory();
-            }
+               if (NULL == (rsp->status = strdup("501 Not Implemented")))
+               {
+                  free_http_response(rsp);
+                  return cgi_error_memory();
+               }
 
-            rsp->is_static = 1;
-            return(finish_http_response(rsp));
+               rsp->is_static = 1;
+               rsp->reason = RSP_REASON_UNSUPPORTED;
+
+               return(finish_http_response(csp, rsp));
+            }
          }
       }
    }