Add list_contains_item().
[privoxy.git] / parsers.c
index 5856986..14ce3b6 100644 (file)
--- a/parsers.c
+++ b/parsers.c
@@ -1,4 +1,4 @@
-const char parsers_rcs[] = "$Id: parsers.c,v 1.89 2007/02/07 16:52:11 fabiankeil Exp $";
+const char parsers_rcs[] = "$Id: parsers.c,v 1.96 2007/04/12 12:53:58 fabiankeil Exp $";
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/parsers.c,v $
@@ -10,13 +10,12 @@ const char parsers_rcs[] = "$Id: parsers.c,v 1.89 2007/02/07 16:52:11 fabiankeil
  *                   `client_uagent', `client_x_forwarded',
  *                   `client_x_forwarded_adder', `client_xtra_adder',
  *                   `content_type', `crumble', `destroy_list', `enlist',
- *                   `flush_socket', ``get_header', `sed', `filter_server_header'
- *                   `filter_client_header', `filter_header', `crunch_server_header',
+ *                   `flush_socket', ``get_header', `sed', `filter_header'
  *                   `server_content_encoding', `server_content_disposition',
  *                   `server_last_modified', `client_accept_language',
  *                   `crunch_client_header', `client_if_modified_since',
  *                   `client_if_none_match', `get_destination_from_headers',
- *                   `parse_header_time' and `server_set_cookie'.
+ *                   `parse_header_time', `decompress_iob' and `server_set_cookie'.
  *
  * Copyright   :  Written by and Copyright (C) 2001-2007 the SourceForge
  *                Privoxy team. http://www.privoxy.org/
@@ -45,6 +44,50 @@ const char parsers_rcs[] = "$Id: parsers.c,v 1.89 2007/02/07 16:52:11 fabiankeil
  *
  * Revisions   :
  *    $Log: parsers.c,v $
+ *    Revision 1.96  2007/04/12 12:53:58  fabiankeil
+ *    Log a warning if the content is compressed, filtering is
+ *    enabled and Privoxy was compiled without zlib support.
+ *    Closes FR#1673938.
+ *
+ *    Revision 1.95  2007/03/25 14:26:40  fabiankeil
+ *    - Fix warnings when compiled with glibc.
+ *    - Don't use crumble() for cookie crunching.
+ *    - Move cookie time parsing into parse_header_time().
+ *    - Let parse_header_time() return a jb_err code
+ *      instead of a pointer that can only be used to
+ *      check for NULL anyway.
+ *
+ *    Revision 1.94  2007/03/21 12:23:53  fabiankeil
+ *    - Add better protection against malicious gzip headers.
+ *    - Stop logging the first hundred bytes of decompressed content.
+ *      It looks like it's working and there is always debug 16.
+ *    - Log the content size after decompression in decompress_iob()
+ *      instead of pcrs_filter_response().
+ *
+ *    Revision 1.93  2007/03/20 15:21:44  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".
+ *    - Remove filter_client_header() and filter_client_header(),
+ *      filter_header() now checks the shiny new
+ *      CSP_FLAG_CLIENT_HEADER_PARSING_DONE flag instead.
+ *
+ *    Revision 1.92  2007/03/05 13:25:32  fabiankeil
+ *    - Cosmetical changes for LOG_LEVEL_RE_FILTER messages.
+ *    - Handle "Cookie:" and "Connection:" headers a bit smarter
+ *      (don't crunch them just to recreate them later on).
+ *    - Add another non-standard time format for the cookie
+ *      expiration date detection.
+ *    - Fix a valgrind warning.
+ *
+ *    Revision 1.91  2007/02/24 12:27:32  fabiankeil
+ *    Improve cookie expiration date detection.
+ *
+ *    Revision 1.90  2007/02/08 19:12:35  fabiankeil
+ *    Don't run server_content_length() the first time
+ *    sed() parses server headers; only adjust the
+ *    Content-Length header if the page was modified.
+ *
  *    Revision 1.89  2007/02/07 16:52:11  fabiankeil
  *    Fix log messages regarding the cookie time format
  *    (cookie and request URL were mixed up).
@@ -619,6 +662,13 @@ const char parsers_rcs[] = "$Id: parsers.c,v 1.89 2007/02/07 16:52:11 fabiankeil
 #include <ctype.h>
 #include <assert.h>
 #include <string.h>
+
+#ifdef __GLIBC__
+/*
+ * Convince GNU's libc to provide a strptime prototype.
+ */
+#define __USE_XOPEN
+#endif /*__GLIBC__ */
 #include <time.h>
 
 #ifdef FEATURE_ZLIB
@@ -643,6 +693,7 @@ const char parsers_rcs[] = "$Id: parsers.c,v 1.89 2007/02/07 16:52:11 fabiankeil
 #include "jbsockets.h"
 #include "miscutil.h"
 #include "list.h"
+#include "actions.h"
 
 #ifndef HAVE_STRPTIME
 #include "strptime.h"
@@ -652,7 +703,7 @@ const char parsers_h_rcs[] = PARSERS_H_VERSION;
 
 /* Fix a problem with Solaris.  There should be no effect on other
  * platforms.
- * Solaris's isspace() is a macro which uses it's argument directly
+ * Solaris's isspace() is a macro which uses its argument directly
  * as an array index.  Therefore we need to make sure that high-bit
  * characters generate +ve values, and ideally we also want to make
  * the argument match the declared parameter type of "int".
@@ -663,6 +714,8 @@ const char parsers_h_rcs[] = PARSERS_H_VERSION;
 #define ijb_isupper(__X) isupper((int)(unsigned char)(__X))
 #define ijb_tolower(__X) tolower((int)(unsigned char)(__X))
 
+jb_err header_tagger(struct client_state *csp, char *header);
+jb_err scan_headers(struct client_state *csp);
 
 const struct parsers client_patterns[] = {
    { "referer:",                  8,   client_referrer },
@@ -676,21 +729,21 @@ const struct parsers client_patterns[] = {
    { "Host:",                     5,   client_host },
    { "if-modified-since:",       18,   client_if_modified_since },
    { "Keep-Alive:",              11,   crumble },
-   { "connection:",              11,   crumble },
+   { "connection:",              11,   connection },
    { "proxy-connection:",        17,   crumble },
    { "max-forwards:",            13,   client_max_forwards },
    { "Accept-Language:",         16,   client_accept_language },
    { "if-none-match:",           14,   client_if_none_match },
    { "X-Filter:",                 9,   client_x_filter },
    { "*",                         0,   crunch_client_header },
-   { "*",                         0,   filter_client_header },
+   { "*",                         0,   filter_header },
    { NULL,                        0,   NULL }
 };
 
 const struct parsers server_patterns[] = {
    { "HTTP",                      4, server_http },
    { "set-cookie:",              11, server_set_cookie },
-   { "connection:",              11, crumble },
+   { "connection:",              11, connection },
    { "Content-Type:",            13, server_content_type },
    { "Content-MD5:",             12, server_content_md5 },
    { "Content-Encoding:",        17, server_content_encoding },
@@ -699,7 +752,7 @@ const struct parsers server_patterns[] = {
    { "content-disposition:",     20, server_content_disposition },
    { "Last-Modified:",           14, server_last_modified },
    { "*",                         0, crunch_server_header },
-   { "*",                         0, filter_server_header },
+   { "*",                         0, filter_header },
    { NULL, 0, NULL }
 };
 
@@ -866,13 +919,18 @@ jb_err decompress_iob(struct client_state *csp)
    char  *cur;       /* Current iob position (to keep the original 
                       * iob->cur unmodified if we return early) */
    size_t bufsize;   /* allocated size of the new buffer */
+   size_t old_size;  /* Content size before decompression */
    size_t skip_size; /* Number of bytes at the beginning of the iob
                         that we should NOT decompress. */
    int status;       /* return status of the inflate() call */
    z_stream zstr;    /* used by calls to zlib */
 
+   assert(csp->iob->cur - csp->iob->buf > 0);
+   assert(csp->iob->eod - csp->iob->cur > 0);
+
    bufsize = csp->iob->size;
    skip_size = (size_t)(csp->iob->cur - csp->iob->buf);
+   old_size = (size_t)(csp->iob->eod - csp->iob->cur);
 
    cur = csp->iob->cur;
 
@@ -950,7 +1008,7 @@ jb_err decompress_iob(struct client_state *csp)
             skip_bytes = *cur++;
             skip_bytes = *cur++ << 8;
 
-            assert(skip_bytes == *csp->iob->cur-2 + ((*csp->iob->cur-1) << 8));
+            assert(skip_bytes == *csp->iob->cur - 2 + ((*csp->iob->cur - 1) << 8));
 
             /*
              * The number of bytes to skip should be positive
@@ -972,14 +1030,16 @@ jb_err decompress_iob(struct client_state *csp)
          /* Skip the filename if necessary. */
          if (flags & 0x08)
          {
-            /* A null-terminated string follows. */
-            while (*cur++);
+            /* A null-terminated string is supposed to follow. */
+            while (*cur++ && (cur < csp->iob->eod));
+
          }
 
          /* Skip the comment if necessary. */
          if (flags & 0x10)
          {
-            while (*cur++);
+            /* A null-terminated string is supposed to follow. */
+            while (*cur++ && (cur < csp->iob->eod));
          }
 
          /* Skip the CRC if necessary. */
@@ -987,6 +1047,18 @@ jb_err decompress_iob(struct client_state *csp)
          {
             cur += 2;
          }
+
+         if (cur >= csp->iob->eod)
+         {
+            /*
+             * If the current position pointer reached or passed
+             * the buffer end, we were obviously tricked to skip
+             * too much.
+             */
+            log_error (LOG_LEVEL_ERROR,
+               "Malformed gzip header detected. Aborting decompression.");
+            return JB_ERR_COMPRESS;
+         }
       }
    }
    else if (csp->content_type & CT_DEFLATE)
@@ -1168,15 +1240,18 @@ jb_err decompress_iob(struct client_state *csp)
     && (csp->iob->cur <= csp->iob->eod)
     && (csp->iob->eod <= csp->iob->buf + csp->iob->size))
    {
-      char t = csp->iob->cur[100];
-      csp->iob->cur[100] = '\0';
-      /*
-       * XXX: The debug level should be lowered
-       * before the next stable release.
-       */
-      log_error(LOG_LEVEL_INFO, "Sucessfully decompressed: %s", csp->iob->cur);
-      csp->iob->cur[100] = t;
-      return JB_ERR_OK;
+      const size_t new_size = (size_t)(csp->iob->eod - csp->iob->cur);
+      if (new_size > 0)
+      {
+         log_error(LOG_LEVEL_RE_FILTER,
+            "Decompression successful. Old size: %d, new size: %d.",
+            old_size, new_size);
+      }
+      else
+      {
+         /* zlib thinks this is OK, so lets do the same. */
+         log_error(LOG_LEVEL_INFO, "Decompression didn't result in any content.");
+      }
    }
    else
    {
@@ -1188,6 +1263,8 @@ jb_err decompress_iob(struct client_state *csp)
       return JB_ERR_COMPRESS;
    }
 
+   return JB_ERR_OK;
+
 }
 #endif /* defined(FEATURE_ZLIB) */
 
@@ -1296,6 +1373,40 @@ char *get_header_value(const struct list *header_list, const char *header_name)
 
 }
 
+
+/*********************************************************************
+ *
+ * Function    :  scan_headers
+ *
+ * Description :  Scans headers, applies tags and updates action bits. 
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns     :  JB_ERR_OK
+ *
+ *********************************************************************/
+jb_err scan_headers(struct client_state *csp)
+{
+   struct list_entry *h; /* Header */
+   jb_err err = JB_ERR_OK;
+
+   log_error(LOG_LEVEL_HEADER, "scanning headers for: %s", csp->http->url);
+
+   for (h = csp->headers->first; (err == JB_ERR_OK) && (h != NULL) ; h = h->next)
+   {
+      /* Header crunch()ed in previous run? -> ignore */
+      if (h->str == NULL) continue;
+      log_error(LOG_LEVEL_HEADER, "scan: %s", h->str);
+      err = header_tagger(csp, h->str);
+   }
+
+   update_action_bits(csp);
+
+   return err;
+}
+
+
 /*********************************************************************
  *
  * Function    :  sed
@@ -1336,7 +1447,8 @@ char *sed(const struct parsers pats[],
 
    if (first_run) /* Parse and print */
    {
-      log_error(LOG_LEVEL_HEADER, "scanning headers for: %s", csp->http->url);
+      scan_headers(csp);
+
       for (v = pats; (err == JB_ERR_OK) && (v->str != NULL) ; v++)
       {
          for (p = csp->headers->first; (err == JB_ERR_OK) && (p != NULL) ; p = p->next)
@@ -1344,8 +1456,6 @@ char *sed(const struct parsers pats[],
             /* Header crunch()ed in previous run? -> ignore */
             if (p->str == NULL) continue;
 
-            if (v == pats) log_error(LOG_LEVEL_HEADER, "scan: %s", p->str);
-
             /* Does the current parser handle this header? */
             if ((strncmpic(p->str, v->str, v->len) == 0) || (v->len == CHECK_EVERY_HEADER_REMAINING))
             {
@@ -1394,66 +1504,179 @@ char *sed(const struct parsers pats[],
 }
 
 
-/* here begins the family of parser functions that reformat header lines */
 
 /*********************************************************************
  *
- * Function    :  filter_server_header
+ * Function    :  header_tagger
+ *
+ * Description :  Executes all text substitutions from applying
+ *                tag actions and saves the result as tag.
  *
- * Description :  Checks if server header filtering is enabled.
- *                If it is, filter_header is called to do the work. 
+ *                XXX: Shares enough code with filter_header() and
+ *                pcrs_filter_response() to warrant some helper functions.
  *
  * Parameters  :
  *          1  :  csp = Current client state (buffers, headers, etc...)
- *          2  :  header = On input, pointer to header to modify.
- *                On output, pointer to the modified header, or NULL
- *                to remove the header.  This function frees the
- *                original string if necessary.
+ *          2  :  header = Header that is used as tagger input
  *
  * Returns     :  JB_ERR_OK on success and always succeeds
  *
  *********************************************************************/
-jb_err filter_server_header(struct client_state *csp, char **header)
+jb_err header_tagger(struct client_state *csp, char *header)
 {
-   if (csp->action->flags & ACTION_FILTER_SERVER_HEADERS)
+   int wanted_filter_type;
+   int multi_action_index;
+   int i;
+   pcrs_job *job;
+
+   struct file_list *fl;
+   struct re_filterfile_spec *b;
+   struct list_entry *tag_name;
+
+   int found_filters = 0;
+   const size_t header_length = strlen(header);
+
+   if (csp->flags & CSP_FLAG_CLIENT_HEADER_PARSING_DONE)
    {
-      filter_header(csp, header);
+      wanted_filter_type = FT_SERVER_HEADER_TAGGER;
+      multi_action_index = ACTION_MULTI_SERVER_HEADER_TAGGER;
+   }
+   else
+   {
+      wanted_filter_type = FT_CLIENT_HEADER_TAGGER;
+      multi_action_index = ACTION_MULTI_CLIENT_HEADER_TAGGER;
    }
-   return(JB_ERR_OK);
-}
 
-/*********************************************************************
- *
- * Function    :  filter_client_header
- *
- * Description :  Checks if client header filtering is enabled.
- *                If it is, filter_header is called to do the work. 
- *
- * Parameters  :
- *          1  :  csp = Current client state (buffers, headers, etc...)
- *          2  :  header = On input, pointer to header to modify.
- *                On output, pointer to the modified header, or NULL
- *                to remove the header.  This function frees the
- *                original string if necessary.
- *
- * Returns     :  JB_ERR_OK on success and always succeeds
- *
- *********************************************************************/
-jb_err filter_client_header(struct client_state *csp, char **header)
-{
-   if (csp->action->flags & ACTION_FILTER_CLIENT_HEADERS)
+   /* Check if there are any filters */
+   for (i = 0; i < MAX_AF_FILES; i++)
    {
-      filter_header(csp, header);
+      fl = csp->rlist[i];
+      if (NULL != fl)
+      {
+         if (NULL != fl->f)
+         {
+           found_filters = 1;
+           break;
+         }
+      }
    }
-   return(JB_ERR_OK);
+
+   if (0 == found_filters)
+   {
+      log_error(LOG_LEVEL_ERROR, "Unable to get current state of regex tagging.");
+      return(JB_ERR_OK);
+   }
+
+   for (i = 0; i < MAX_AF_FILES; i++)
+   {
+      fl = csp->rlist[i];
+      if ((NULL == fl) || (NULL == fl->f))
+      {
+         /*
+          * Either there are no filter files
+          * left, or this filter file just
+          * contains no valid filters.
+          *
+          * Continue to be sure we don't miss
+          * valid filter files that are chained
+          * after empty or invalid ones.
+          */
+         continue;
+      }
+
+      /* For all filters, */
+      for (b = fl->f; b; b = b->next)
+      {
+         if (b->type != wanted_filter_type)
+         {
+            /* skip the ones we don't care about, */
+            continue;
+         }
+         /* leaving only taggers that could apply, of which we use the ones, */
+         for (tag_name = csp->action->multi[multi_action_index]->first;
+              NULL != tag_name; tag_name = tag_name->next)
+         {
+            /* that do apply, and */
+            if (strcmp(b->name, tag_name->str) == 0)
+            {
+               char *modified_tag = NULL;
+               char *tag = header;
+               size_t size = header_length;
+
+               if (NULL == b->joblist)
+               {
+                  log_error(LOG_LEVEL_RE_FILTER,
+                     "Tagger %s has empty joblist. Nothing to do.", b->name);
+                  continue;
+               }
+
+               /* execute their pcrs_joblist on the header. */
+               for (job = b->joblist; NULL != job; job = job->next)
+               {
+                  const int hits = pcrs_execute(job, tag, size, &modified_tag, &size);
+
+                  if (0 < hits)
+                  {
+                     /* Success, continue with the modified version. */
+                     if (tag != header)
+                     {
+                        freez(tag);
+                     }
+                     tag = modified_tag;
+                  }
+                  else
+                  {
+                     /* Tagger doesn't match */
+                     if (0 > hits)
+                     {
+                        /* Regex failure, log it but continue anyway. */
+                        log_error(LOG_LEVEL_ERROR,
+                           "Problems with tagger \'%s\' and header \'%s\': %s",
+                           b->name, *header, pcrs_strerror(hits));
+                     }
+                     freez(modified_tag);
+                  }
+               }
+
+               /* If this tagger matched */
+               if (tag != header)
+               {
+                  /* and there is something left to save, */
+                  if (0 < size)
+                  {
+                     /* enlist a unique version of it as tag. */
+                     if (JB_ERR_OK != enlist_unique(csp->tags, tag, 0))
+                     {
+                        log_error(LOG_LEVEL_ERROR,
+                           "Insufficient memory to add tag \'%s\', "
+                           "based on tagger \'%s\' and header \'%s\'",
+                           tag, b->name, *header);
+                     }
+                     else
+                     {
+                        log_error(LOG_LEVEL_HEADER,
+                           "Adding tag \'%s\' created by header tagger \'%s\'",
+                           tag, b->name);
+                     }
+                  }
+                  freez(tag);
+               }
+            } /* if the tagger applies */
+         } /* for every tagger that could apply */
+      } /* for all filters */
+   } /* for all filter files */
+
+   return JB_ERR_OK;
 }
 
+/* here begins the family of parser functions that reformat header lines */
+
 /*********************************************************************
  *
  * Function    :  filter_header
  *
  * Description :  Executes all text substitutions from all applying
- *                +filter actions on the header.
+ *                +(server|client)-header-filter actions on the header.
  *                Most of the code was copied from pcrs_filter_response,
  *                including the rather short variable names
  *
@@ -1481,6 +1704,19 @@ jb_err filter_header(struct client_state *csp, char **header)
    struct list_entry *filtername;
 
    int i, found_filters = 0;
+   int wanted_filter_type;
+   int multi_action_index;
+
+   if (csp->flags & CSP_FLAG_CLIENT_HEADER_PARSING_DONE)
+   {
+      wanted_filter_type = FT_SERVER_HEADER_FILTER;
+      multi_action_index = ACTION_MULTI_SERVER_HEADER_FILTER;
+   }
+   else
+   {
+      wanted_filter_type = FT_CLIENT_HEADER_FILTER;
+      multi_action_index = ACTION_MULTI_CLIENT_HEADER_FILTER;
+   }
 
    /*
     * Need to check the set of re_filterfiles...
@@ -1527,7 +1763,13 @@ jb_err filter_header(struct client_state *csp, char **header)
        */
       for (b = fl->f; b; b = b->next)
       {
-         for (filtername = csp->action->multi[ACTION_MULTI_FILTER]->first;
+         if (b->type != wanted_filter_type)
+         {
+            /* Skip other filter types */
+            continue;
+         }
+
+         for (filtername = csp->action->multi[multi_action_index]->first;
               filtername ; filtername = filtername->next)
          {
             if (strcmp(b->name, filtername->str) == 0)
@@ -1540,7 +1782,7 @@ jb_err filter_header(struct client_state *csp, char **header)
                   continue;
                }
 
-               log_error(LOG_LEVEL_RE_FILTER, "re_filtering %s (size %d) with filter %s...",
+               log_error(LOG_LEVEL_RE_FILTER, "filtering \'%s\' (size %d) with \'%s\' ...",
                          *header, size, b->name);
 
                /* Apply all jobs from the joblist */
@@ -1571,7 +1813,7 @@ jb_err filter_header(struct client_state *csp, char **header)
                      }
                   }
                }
-               log_error(LOG_LEVEL_RE_FILTER, " ...produced %d hits (new size %d).", current_hits, size);
+               log_error(LOG_LEVEL_RE_FILTER, "... produced %d hits (new size %d).", current_hits, size);
                hits += current_hits;
             }
          }
@@ -1593,6 +1835,56 @@ jb_err filter_header(struct client_state *csp, char **header)
 }
 
 
+/*********************************************************************
+ *
+ * Function    :  connection
+ *
+ * Description :  Makes sure that the value of the Connection: header
+ *                is "close" and signals connection_close_adder 
+ *                to do nothing.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  header = On input, pointer to header to modify.
+ *                On output, pointer to the modified header, or NULL
+ *                to remove the header.  This function frees the
+ *                original string if necessary.
+ *
+ * Returns     :  JB_ERR_OK on success, or
+ *                JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+jb_err connection(struct client_state *csp, char **header)
+{
+   char *old_header = *header;
+
+   /* Do we have a 'Connection: close' header? */
+   if (strcmpic(*header, "Connection: close"))
+   {
+      /* No, create one */
+      *header = strdup("Connection: close");
+      if (header == NULL)
+      { 
+         return JB_ERR_MEMORY;
+      }
+      log_error(LOG_LEVEL_HEADER, "Replaced: \'%s\' with \'%s\'", old_header, *header);
+      freez(old_header);
+   }
+
+   /* Signal connection_close_adder() to return early. */
+   if (csp->flags & CSP_FLAG_CLIENT_HEADER_PARSING_DONE)
+   {
+      csp->flags |= CSP_FLAG_SERVER_CONNECTION_CLOSE_SET;
+   }
+   else
+   {
+      csp->flags |= CSP_FLAG_CLIENT_CONNECTION_CLOSE_SET;
+   }
+
+   return JB_ERR_OK;
+}
+
+
 /*********************************************************************
  *
  * Function    :  crumble
@@ -1637,13 +1929,13 @@ jb_err crumble(struct client_state *csp, char **header)
 jb_err crunch_server_header(struct client_state *csp, char **header)
 {
    const char *crunch_pattern;
-   /*Is there a header to crunch*/
 
+   /* Do we feel like crunching? */
    if ((csp->action->flags & ACTION_CRUNCH_SERVER_HEADER))
    {
       crunch_pattern = csp->action->string[ACTION_STRING_SERVER_HEADER];
 
-      /*Is the current header the lucky one?*/
+      /* Is the current header the lucky one? */
       if (strstr(*header, crunch_pattern))
       {
          log_error(LOG_LEVEL_HEADER, "Crunching server header: %s (contains: %s)", *header, crunch_pattern);  
@@ -1897,8 +2189,20 @@ jb_err server_content_encoding(struct client_state *csp, char **header)
        * Body is compressed, turn off pcrs and gif filtering.
        */
       csp->content_type |= CT_TABOO;
+
+      /*
+       * Log a warning if the user expects the content to be filtered.
+       */
+      if ((csp->rlist != NULL) &&
+         (!list_is_empty(csp->action->multi[ACTION_MULTI_FILTER])))
+      {
+         log_error(LOG_LEVEL_INFO,
+            "Compressed content detected, content filtering disabled. "
+            "Consider recompiling Privoxy with zlib support or "
+            "enable the prevent-compression action.");
+      }
    }
-#endif /* !defined(FEATURE_ZLIB) */
+#endif /* defined(FEATURE_ZLIB) */
 
    return JB_ERR_OK;
 
@@ -2113,6 +2417,8 @@ jb_err server_last_modified(struct client_state *csp, char **header)
    }
    else if (0 == strcmpic(newval, "randomize"))
    {
+      const char *header_time = *header + sizeof("Last-Modified:");
+
       log_error(LOG_LEVEL_HEADER, "Randomizing: %s", *header);
       now = time(NULL);
 #ifdef HAVE_GMTIME_R
@@ -2124,9 +2430,9 @@ jb_err server_last_modified(struct client_state *csp, char **header)
 #else
       timeptr = gmtime(&now);
 #endif
-      if ((timeptr = parse_header_time(*header, &last_modified)) == NULL)
+      if (JB_ERR_OK != parse_header_time(header_time, &last_modified))
       {
-         log_error(LOG_LEVEL_HEADER, "Couldn't parse: %s (crunching!)", *header);
+         log_error(LOG_LEVEL_HEADER, "Couldn't parse: %s in %s (crunching!)", header_time, *header);
          freez(*header);
       }
       else
@@ -2178,6 +2484,7 @@ jb_err server_last_modified(struct client_state *csp, char **header)
    return JB_ERR_OK;
 }
 
+
 /*********************************************************************
  *
  * Function    :  client_accept_encoding
@@ -2465,13 +2772,13 @@ jb_err client_accept_language(struct client_state *csp, char **header)
 jb_err crunch_client_header(struct client_state *csp, char **header)
 {
    const char *crunch_pattern;
-   /*Is there a header to crunch*/
-   
+
+   /* Do we feel like crunching? */
    if ((csp->action->flags & ACTION_CRUNCH_CLIENT_HEADER))
    {
       crunch_pattern = csp->action->string[ACTION_STRING_CLIENT_HEADER];
 
-      /*Is the current header the lucky one?*/
+      /* Is the current header the lucky one? */
       if (strstr(*header, crunch_pattern))
       {
          log_error(LOG_LEVEL_HEADER, "Crunching client header: %s (contains: %s)", *header, crunch_pattern);  
@@ -2607,9 +2914,10 @@ jb_err client_from(struct client_state *csp, char **header)
  *
  * Function    :  client_send_cookie
  *
- * Description :  Handle the "cookie" header properly.  Called from `sed'.
- *                If cookie is accepted, add it to the cookie_list,
- *                else we crunch it.  Mmmmmmmmmmm ... cookie ......
+ * Description :  Crunches the "cookie" header if necessary.
+ *                Called from `sed'.
+ *
+ *                XXX: Stupid name, doesn't send squat.
  *
  * Parameters  :
  *          1  :  csp = Current client state (buffers, headers, etc...)
@@ -2624,25 +2932,13 @@ jb_err client_from(struct client_state *csp, char **header)
  *********************************************************************/
 jb_err client_send_cookie(struct client_state *csp, char **header)
 {
-   jb_err result = JB_ERR_OK;
-
-   if ((csp->action->flags & ACTION_NO_COOKIE_READ) == 0)
-   {
-      /* strlen("cookie: ") == 8 */
-      result = enlist(csp->cookie_list, *header + 8);
-   }
-   else
+   if (csp->action->flags & ACTION_NO_COOKIE_READ)
    {
-      log_error(LOG_LEVEL_HEADER, "Crunched outgoing cookie -- yum!");
+      log_error(LOG_LEVEL_HEADER, "Crunched outgoing cookie: %s", *header);
+      freez(*header);
    }
 
-   /*
-    * Always remove the cookie here.  The cookie header
-    * will be sent at the end of the header.
-    */
-   freez(*header);
-
-   return result;
+   return JB_ERR_OK;
 }
 
 
@@ -2823,6 +3119,9 @@ jb_err client_host(struct client_state *csp, char **header)
                 csp->http->hostport, csp->http->host, csp->http->port);
    }
 
+   /* Signal client_host_adder() to return right away */
+   csp->flags |= CSP_FLAG_HOST_HEADER_IS_SET;
+
    return JB_ERR_OK;
 }
 
@@ -2880,9 +3179,11 @@ jb_err client_if_modified_since(struct client_state *csp, char **header)
       }
       else /* add random value */
       {
-         if ((timeptr = parse_header_time(*header, &tm)) == NULL)
+         const char *header_time = *header + sizeof("If-Modified-Since:");
+
+         if (JB_ERR_OK != parse_header_time(header_time, &tm))
          {
-            log_error(LOG_LEVEL_HEADER, "Couldn't parse: %s (crunching!)", *header);
+            log_error(LOG_LEVEL_HEADER, "Couldn't parse: %s in %s (crunching!)", header_time, *header);
             freez(*header);
          }
          else
@@ -3039,8 +3340,16 @@ jb_err client_host_adder(struct client_state *csp)
    char *p;
    jb_err err;
 
+   if (csp->flags & CSP_FLAG_HOST_HEADER_IS_SET)
+   {
+      /* Header already set by the client, nothing to do. */
+      return JB_ERR_OK;
+   }
+
    if ( !csp->http->hostport || !*(csp->http->hostport))
    {
+      /* XXX: When does this happen and why is it OK? */
+      log_error(LOG_LEVEL_INFO, "Weirdness in client_host_adder detected and ignored.");
       return JB_ERR_OK;
    }
 
@@ -3056,6 +3365,7 @@ jb_err client_host_adder(struct client_state *csp)
       p = csp->http->hostport;
    }
 
+   /* XXX: Just add it, we already made sure that it will be unique */
    log_error(LOG_LEVEL_HEADER, "addh-unique: Host: %s", p);
    err = enlist_unique_header(csp->headers, "Host", p);
    return err;
@@ -3069,6 +3379,8 @@ jb_err client_host_adder(struct client_state *csp)
  *
  * Description :  Used in the add_client_headers list.  Called from `sed'.
  *
+ *                XXX: Remove csp->cookie_list which is no longer used.
+ *
  * Parameters  :
  *          1  :  csp = Current client state (buffers, headers, etc...)
  *
@@ -3149,6 +3461,8 @@ jb_err client_cookie_adder(struct client_state *csp)
  *********************************************************************/
 jb_err client_accept_encoding_adder(struct client_state *csp)
 {
+   assert(0); /* Not in use */
+
    if (   ((csp->action->flags & ACTION_NO_COMPRESSION) != 0)
        && (!strcmpic(csp->http->ver, "HTTP/1.1")) )
    {
@@ -3244,9 +3558,10 @@ jb_err client_x_forwarded_adder(struct client_state *csp)
  *
  * Function    :  connection_close_adder
  *
- * Description :  Adds a "Connection: close" header to csp->headers
- *                as a temporary fix for the needed but missing HTTP/1.1
- *                support. Called from `sed'.
+ * Description :  "Temporary" fix for the needed but missing HTTP/1.1
+ *                support. Adds a "Connection: close" header to csp->headers
+ *                unless the header was already present. Called from `sed'.
+ *
  *                FIXME: This whole function shouldn't be neccessary!
  *
  * Parameters  :
@@ -3258,7 +3573,27 @@ jb_err client_x_forwarded_adder(struct client_state *csp)
  *********************************************************************/
 jb_err connection_close_adder(struct client_state *csp)
 {
+   const unsigned int flags = csp->flags;
+
+   /*
+    * Return right away if
+    *
+    * - we're parsing server headers and the server header
+    *   "Connection: close" is already set, or if
+    *
+    * - we're parsing client headers and the client header 
+    *   "Connection: close" is already set.
+    */
+   if ((flags & CSP_FLAG_CLIENT_HEADER_PARSING_DONE
+     && flags & CSP_FLAG_SERVER_CONNECTION_CLOSE_SET)
+   ||(!(flags & CSP_FLAG_CLIENT_HEADER_PARSING_DONE)
+     && flags & CSP_FLAG_CLIENT_CONNECTION_CLOSE_SET))
+   {
+      return JB_ERR_OK;
+   }
+
    log_error(LOG_LEVEL_HEADER, "Adding: Connection: close");
+
    return enlist(csp->headers, "Connection: close");
 }
 
@@ -3331,7 +3666,6 @@ jb_err server_set_cookie(struct client_state *csp, char **header)
    time_t now;
    time_t cookie_time; 
    struct tm tm_now; 
-   struct tm tm_cookie;
    time(&now);
 
 #ifdef FEATURE_COOKIE_JAR
@@ -3349,10 +3683,10 @@ jb_err server_set_cookie(struct client_state *csp, char **header)
       tm_now = *localtime_r(&now, &tm_now);
 #elif FEATURE_PTHREAD
       pthread_mutex_lock(&localtime_mutex);
-      tm_now = *localtime (&now); 
+      tm_now = *localtime (&now);
       pthread_mutex_unlock(&localtime_mutex);
 #else
-      tm_now = *localtime (&now); 
+      tm_now = *localtime (&now);
 #endif
       strftime(tempbuf, BUFFER_SIZE-6, "%b %d %H:%M:%S ", &tm_now); 
 
@@ -3363,8 +3697,8 @@ jb_err server_set_cookie(struct client_state *csp, char **header)
 
    if ((csp->action->flags & ACTION_NO_COOKIE_SET) != 0)
    {
-      log_error(LOG_LEVEL_HEADER, "Crunched incoming cookie -- yum!");
-      return crumble(csp, header);
+      log_error(LOG_LEVEL_HEADER, "Crunching incoming cookie: %s", *header);
+      freez(*header);
    }
    else if ((csp->action->flags & ACTION_NO_COOKIE_KEEP) != 0)
    {
@@ -3410,39 +3744,12 @@ jb_err server_set_cookie(struct client_state *csp, char **header)
           * if the cookie is still valid, if yes,
           * rewrite it to a session cookie.
           */
-         if (strncmpic(cur_tag, "expires=", 8) == 0)
+         if ((strncmpic(cur_tag, "expires=", 8) == 0) && *(cur_tag + 8))
          {
-            char *match;
-            /*
-             * Try the valid time formats we know about.
-             *
-             * XXX: Maybe the log messages should be removed
-             * for the next stable release. They just exist to
-             * see which time format gets the most hits and
-             * should be checked for first.
-             */
-            if (NULL != (match = strptime(cur_tag, "expires=%a, %e-%b-%y %H:%M:%S ", &tm_cookie)))
-            {
-               log_error(LOG_LEVEL_HEADER,
-                  "cookie \'%s\' send by %s appears to be using time format 1.",
-                  *header, csp->http->url);
-            }
-            else if (NULL != (match = strptime(cur_tag, "expires=%A, %e-%b-%Y %H:%M:%S ", &tm_cookie)))
-            {
-               log_error(LOG_LEVEL_HEADER,
-                  "cookie \'%s\' send by %s appears to be using time format 2.",
-                  *header, csp->http->url);
-
-            }
-            else if (NULL != (match = strptime(cur_tag, "expires=%a, %e-%b-%Y %H:%M:%S ", &tm_cookie)))
-            {
-               log_error(LOG_LEVEL_HEADER,
-                  "cookie \'%s\' send by %s appears to be using time format 3.",
-                   *header, csp->http->url);
-            }
+            char *expiration_date = cur_tag + 8; /* Skip "[Ee]xpires=" */
 
-            /* Did any of them match? */
-            if (NULL == match)
+            /* Did we detect the date properly? */
+            if (JB_ERR_OK != parse_header_time(expiration_date, &cookie_time))
             {
                /*
                 * Nope, treat it as if it was still valid.
@@ -3450,7 +3757,7 @@ jb_err server_set_cookie(struct client_state *csp, char **header)
                 * XXX: Should we remove the whole cookie instead?
                 */
                log_error(LOG_LEVEL_ERROR,
-                  "Can't parse %s. Unsupported time format?", cur_tag);
+                  "Can't parse \'%s\', send by %s. Unsupported time format?", cur_tag, csp->http->url);
                memmove(cur_tag, next_tag, strlen(next_tag) + 1);
                changed = 1;
             }
@@ -3490,7 +3797,6 @@ jb_err server_set_cookie(struct client_state *csp, char **header)
                 *   anyway, which in many cases will be shorter
                 *   than a browser session.
                 */
-               cookie_time = timegm(&tm_cookie);
                if (cookie_time - now < 0)
                {
                   log_error(LOG_LEVEL_HEADER,
@@ -3500,11 +3806,10 @@ jb_err server_set_cookie(struct client_state *csp, char **header)
                }
                else
                {
-                  log_error(LOG_LEVEL_HEADER,
-                     "Cookie \'%s\' is still valid and has to be rewritten.", *header);
-
                   /*
-                   * Delete the tag by copying the rest of the string over it.
+                   * Still valid, delete expiration date by copying
+                   * the rest of the string over it.
+                   *
                    * (Note that we cannot just use "strcpy(cur_tag, next_tag)",
                    * since the behaviour of strcpy is undefined for overlapping
                    * strings.)
@@ -3584,54 +3889,48 @@ int strclean(const char *string, const char *substring)
  *
  * Function    :  parse_header_time
  *
- * Description :  Transforms time inside a HTTP header into
- *                the usual time format.
+ * Description :  Parses time formats used in HTTP header strings
+ *                to get the numerical respresentation.
  *
  * Parameters  :
- *          1  :  header = header to parse
- *          2  :  tm = storage for the resulting time in seconds 
+ *          1  :  header_time = HTTP header time as string. 
+ *          2  :  result = storage for header_time in seconds
  *
- * Returns     :  Time struct containing the header time, or
- *                NULL in case of a parsing problems.
+ * Returns     :  JB_ERR_OK if the time format was recognized, or
+ *                JB_ERR_PARSE otherwise.
  *
  *********************************************************************/
-struct tm *parse_header_time(char *header, time_t *tm) {
-
-   char * timestring;
+jb_err parse_header_time(const char *header_time, time_t *result)
+{
    struct tm gmt;
-   struct tm * timeptr;
 
    /*
-    * Initializing gmt to prevent time zone offsets.
+    * Zero out gmt to prevent time zone offsets.
     *
     * While this is only necessary on some platforms
     * (mingw32 for example), I don't know how to
     * detect these automatically and doing it everywhere
     * shouldn't hurt.
     */
-   time(tm); 
-#ifdef HAVE_LOCALTIME_R
-   gmt = *localtime_r(tm, &gmt);
-#elif FEATURE_PTHREAD
-   pthread_mutex_lock(&localtime_mutex);
-   gmt = *localtime(tm); 
-   pthread_mutex_unlock(&localtime_mutex);
-#else
-   gmt = *localtime(tm); 
-#endif
-
-   /* Skipping header name */
-   timestring = strstr(header, ": ");
-   if (strptime(timestring, ": %a, %d %b %Y %H:%M:%S", &gmt) == NULL)
-   {
-      timeptr = NULL;
-   }
-   else
+   memset(&gmt, 0, sizeof(gmt));
+
+                            /* Tue, 02 Jun 2037 20:00:00 */
+   if ((NULL == strptime(header_time, "%a, %d %b %Y %H:%M:%S", &gmt))
+                            /* Tue, 02-Jun-2037 20:00:00 */
+    && (NULL == strptime(header_time, "%a, %d-%b-%Y %H:%M:%S", &gmt))
+                            /* Tue, 02-Jun-37 20:00:00 */
+    && (NULL == strptime(header_time, "%a, %d-%b-%y %H:%M:%S", &gmt))
+                        /* Tuesday, 02-Jun-2037 20:00:00 */
+    && (NULL == strptime(header_time, "%A, %d-%b-%Y %H:%M:%S", &gmt))
+                        /* Tuesday Jun 02 20:00:00 2037 */
+    && (NULL == strptime(header_time, "%A %b %d %H:%M:%S %Y", &gmt)))
    {
-      *tm = timegm(&gmt);
-      timeptr = &gmt;
+      return JB_ERR_PARSE;
    }
-   return(timeptr);
+
+   *result = timegm(&gmt);
+
+   return JB_ERR_OK;
 
 }