- Mention other reasons why acl directive loading might have failed.
[privoxy.git] / parsers.c
index 22fc5ef..1d8c07a 100644 (file)
--- a/parsers.c
+++ b/parsers.c
@@ -1,4 +1,4 @@
-const char parsers_rcs[] = "$Id: parsers.c,v 1.82 2007/01/01 19:36:37 fabiankeil Exp $";
+const char parsers_rcs[] = "$Id: parsers.c,v 1.91 2007/02/24 12:27:32 fabiankeil Exp $";
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/parsers.c,v $
@@ -18,7 +18,7 @@ const char parsers_rcs[] = "$Id: parsers.c,v 1.82 2007/01/01 19:36:37 fabiankeil
  *                   `client_if_none_match', `get_destination_from_headers',
  *                   `parse_header_time' and `server_set_cookie'.
  *
- * Copyright   :  Written by and Copyright (C) 2001-2006 the SourceForge
+ * Copyright   :  Written by and Copyright (C) 2001-2007 the SourceForge
  *                Privoxy team. http://www.privoxy.org/
  *
  *                Based on the Internet Junkbuster originally written
@@ -45,6 +45,58 @@ const char parsers_rcs[] = "$Id: parsers.c,v 1.82 2007/01/01 19:36:37 fabiankeil
  *
  * Revisions   :
  *    $Log: parsers.c,v $
+ *    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).
+ *
+ *    Revision 1.88  2007/02/07 11:27:12  fabiankeil
+ *    - Let decompress_iob()
+ *      - not corrupt the content if decompression fails
+ *        early. (the first byte(s) were lost).
+ *      - use pointer arithmetics with defined outcome for
+ *        a change.
+ *    - Use a different kludge to remember a failed decompression.
+ *
+ *    Revision 1.87  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.86  2007/01/30 13:05:26  fabiankeil
+ *    - Let server_set_cookie() check the expiration date
+ *      of cookies and don't touch the ones that are already
+ *      expired. Fixes problems with low quality web applications
+ *      as described in BR 932612.
+ *
+ *    - Adjust comment in client_max_forwards to reality;
+ *      remove invalid Max-Forwards headers.
+ *
+ *    Revision 1.85  2007/01/26 15:33:46  fabiankeil
+ *    Stop filter_header() from unintentionally removing
+ *    empty header lines that were enlisted by the continue
+ *    hack.
+ *
+ *    Revision 1.84  2007/01/24 12:56:52  fabiankeil
+ *    - Repeat the request URL before logging any headers.
+ *      Makes reading the log easier in case of simultaneous requests.
+ *    - If there are more than one Content-Type headers in one request,
+ *      use the first one and remove the others.
+ *    - Remove "newval" variable in server_content_type().
+ *      It's only used once.
+ *
+ *    Revision 1.83  2007/01/12 15:03:02  fabiankeil
+ *    Correct a cast, check inflateEnd() exit code
+ *    to see if we have to, replace sprintf calls
+ *    with snprintf.
+ *
  *    Revision 1.82  2007/01/01 19:36:37  fabiankeil
  *    Integrate a modified version of Wil Mahan's
  *    zlib patch (PR #895531).
@@ -632,7 +684,7 @@ 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 },
@@ -646,9 +698,8 @@ const struct parsers client_patterns[] = {
 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-Length:",          15, server_content_length },
    { "Content-MD5:",             12, server_content_md5 },
    { "Content-Encoding:",        17, server_content_encoding },
    { "Transfer-Encoding:",       18, server_transfer_coding },
@@ -820,6 +871,8 @@ jb_err add_to_iob(struct client_state *csp, char *buf, int n)
 jb_err decompress_iob(struct client_state *csp)
 {
    char  *buf;       /* new, uncompressed buffer */
+   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 skip_size; /* Number of bytes at the beginning of the iob
                         that we should NOT decompress. */
@@ -829,6 +882,8 @@ jb_err decompress_iob(struct client_state *csp)
    bufsize = csp->iob->size;
    skip_size = (size_t)(csp->iob->cur - csp->iob->buf);
 
+   cur = csp->iob->cur;
+
    if (bufsize < 10)
    {
       /*
@@ -853,16 +908,16 @@ jb_err decompress_iob(struct client_state *csp)
        * Strip off the gzip header. Please see RFC 1952 for more
        * explanation of the appropriate fields.
        */
-      if ((*csp->iob->cur++ != (char)0x1f)
-       || (*csp->iob->cur++ != (char)0x8b)
-       || (*csp->iob->cur++ != Z_DEFLATED))
+      if ((*cur++ != (char)0x1f)
+       || (*cur++ != (char)0x8b)
+       || (*cur++ != Z_DEFLATED))
       {
          log_error (LOG_LEVEL_ERROR, "Invalid gzip header when decompressing");
          return JB_ERR_COMPRESS;
       }
       else
       {
-         int flags = *csp->iob->cur++;
+         int flags = *cur++;
          /*
           * XXX: These magic numbers should be replaced
           * with macros to give a better idea what they do.
@@ -870,10 +925,10 @@ jb_err decompress_iob(struct client_state *csp)
          if (flags & 0xe0)
          {
             /* The gzip header has reserved bits set; bail out. */
-            log_error (LOG_LEVEL_ERROR, "Invalid gzip header when decompressing");
+            log_error (LOG_LEVEL_ERROR, "Invalid gzip header flags when decompressing");
             return JB_ERR_COMPRESS;
          }
-         csp->iob->cur += 6;
+         cur += 6;
 
          /* Skip extra fields if necessary. */
          if (flags & 0x04)
@@ -882,26 +937,63 @@ jb_err decompress_iob(struct client_state *csp)
              * Skip a given number of bytes, specified
              * as a 16-bit little-endian value.
              */
-            csp->iob->cur += *csp->iob->cur++ + (*csp->iob->cur++ << 8);
+            /*
+             * XXX: This code used to be:
+             * 
+             * csp->iob->cur += *csp->iob->cur++ + (*csp->iob->cur++ << 8);
+             *
+             * which I had to change into:
+             *
+             * cur += *cur++ + (*cur++ << 8);
+             *
+             * at which point gcc43 finally noticed that the value
+             * of cur is undefined (it depends on which of the
+             * summands is evaluated first).
+             *
+             * I haven't come across a site where this
+             * code is actually executed yet, but I hope
+             * it works anyway.
+             */
+            int skip_bytes;
+            skip_bytes = *cur++;
+            skip_bytes = *cur++ << 8;
+
+            assert(skip_bytes == *csp->iob->cur-2 + ((*csp->iob->cur-1) << 8));
+
+            /*
+             * The number of bytes to skip should be positive
+             * and we'd like to stay in the buffer.
+             */
+            if((skip_bytes < 0) || (skip_bytes >= (csp->iob->eod - cur)))
+            {
+               log_error (LOG_LEVEL_ERROR,
+                  "Unreasonable amount of bytes to skip (%d). Stopping decompression",
+                  skip_bytes);
+               return JB_ERR_COMPRESS;
+            }
+            log_error (LOG_LEVEL_INFO,
+               "Skipping %d bytes for gzip compression. Does this sound right?",
+               skip_bytes);
+            cur += skip_bytes;
          }
 
          /* Skip the filename if necessary. */
          if (flags & 0x08)
          {
             /* A null-terminated string follows. */
-            while (*csp->iob->cur++);
+            while (*cur++);
          }
 
          /* Skip the comment if necessary. */
          if (flags & 0x10)
          {
-            while (*csp->iob->cur++);
+            while (*cur++);
          }
 
          /* Skip the CRC if necessary. */
          if (flags & 0x02)
          {
-            csp->iob->cur += 2;
+            cur += 2;
          }
       }
    }
@@ -911,7 +1003,7 @@ jb_err decompress_iob(struct client_state *csp)
        * XXX: The debug level should be lowered
        * before the next stable release.
        */
-      log_error (LOG_LEVEL_INFO, "Decompressing deflated iob: %d", *csp->iob->cur);
+      log_error (LOG_LEVEL_INFO, "Decompressing deflated iob: %d", *cur);
       /*
        * In theory (that is, according to RFC 1950), deflate-compressed
        * data should begin with a two-byte zlib header and have an
@@ -937,8 +1029,8 @@ jb_err decompress_iob(struct client_state *csp)
    }
 
    /* Set up the fields required by zlib. */
-   zstr.next_in  = (Bytef *)csp->iob->cur;
-   zstr.avail_in = (unsigned int)(csp->iob->eod - csp->iob->cur);
+   zstr.next_in  = (Bytef *)cur;
+   zstr.avail_in = (unsigned int)(csp->iob->eod - cur);
    zstr.zalloc   = Z_NULL;
    zstr.zfree    = Z_NULL;
    zstr.opaque   = Z_NULL;
@@ -1252,6 +1344,7 @@ char *sed(const struct parsers pats[],
 
    if (first_run) /* Parse and print */
    {
+      log_error(LOG_LEVEL_HEADER, "scanning headers for: %s", csp->http->url);
       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)
@@ -1455,7 +1548,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 */
@@ -1486,14 +1579,19 @@ 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;
             }
          }
       }
    }
 
-   if ( 0 == size )
+   /*
+    * Additionally checking for hits is important because if
+    * the continue hack is triggered, server headers can
+    * arrive empty to separate multiple heads from each other.
+    */
+   if ((0 == size) && hits)
    {
       log_error(LOG_LEVEL_HEADER, "Removing empty header %s", *header);
       freez(*header);
@@ -1503,6 +1601,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
@@ -1547,13 +1695,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);  
@@ -1563,12 +1711,14 @@ jb_err crunch_server_header(struct client_state *csp, char **header)
 
    return JB_ERR_OK;
 }
+
+
 /*********************************************************************
  *
  * Function    :  server_content_type
  *
  * Description :  Set the content-type for filterable types (text/.*,
- *                javascript and image/gif) unless filtering has been
+ *                .*xml.*, javascript and image/gif) unless filtering has been
  *                forbidden (CT_TABOO) while parsing earlier headers.
  *                NOTE: Since text/plain is commonly used by web servers
  *                      for files whose correct type is unknown, we don't
@@ -1587,11 +1737,22 @@ jb_err crunch_server_header(struct client_state *csp, char **header)
  *********************************************************************/
 jb_err server_content_type(struct client_state *csp, char **header)
 {
-   const char *newval;
-   
-   newval = csp->action->string[ACTION_STRING_CONTENT_TYPE]; 
+   /* Remove header if it isn't the first Content-Type header */
+   if(csp->content_type && (csp->content_type != CT_TABOO))
+   {
+     /*
+      * Another, slightly slower, way to see if
+      * we already parsed another Content-Type header.
+      */
+      assert(NULL != get_header_value(csp->headers, "Content-Type:"));
+
+      log_error(LOG_LEVEL_ERROR,
+         "Multiple Content-Type headers. Removing and ignoring: \'%s\'",
+         *header);
+      freez(*header);
 
-   assert(!csp->content_type || (csp->content_type == CT_TABOO));
+      return JB_ERR_OK;
+   }
 
    if (!(csp->content_type & CT_TABOO))
    {
@@ -1645,12 +1806,11 @@ jb_err server_content_type(struct client_state *csp, char **header)
       { 
          freez(*header);
          *header = strdup("Content-Type: ");
-         string_append(header, newval);
+         string_append(header, csp->action->string[ACTION_STRING_CONTENT_TYPE]);
 
          if (header == NULL)
          { 
-            log_error(LOG_LEVEL_HEADER,
-               "Insufficient memory. Content-Type crunched without replacement!");
+            log_error(LOG_LEVEL_HEADER, "Insufficient memory to replace Content-Type!");
             return JB_ERR_MEMORY;
          }
          log_error(LOG_LEVEL_HEADER, "Modified: %s!", *header);
@@ -1756,8 +1916,8 @@ jb_err server_transfer_coding(struct client_state *csp, char **header)
 jb_err server_content_encoding(struct client_state *csp, char **header)
 {
 #ifdef FEATURE_ZLIB
-   /* XXX: Why would we modify the content if it was taboo? */
-   if ((csp->flags & CSP_FLAG_MODIFIED) && !(csp->content_type & CT_TABOO))
+   if ((csp->flags & CSP_FLAG_MODIFIED)
+    && (csp->content_type & (CT_GZIP | CT_DEFLATE)))
    {
       /*
        * We successfully decompressed the content,
@@ -1824,12 +1984,10 @@ jb_err server_content_encoding(struct client_state *csp, char **header)
 jb_err server_content_length(struct client_state *csp, char **header)
 {
    const size_t max_header_length = 80;
-   if (csp->content_length != 0) /* Content length could have been modified */
+
+   /* Regenerate header if the content was modified. */
+   if (csp->flags & CSP_FLAG_MODIFIED)
    {
-      /*
-       * XXX: Shouldn't we check if csp->content_length
-       * is different than the original value?
-       */
       freez(*header);
       *header = (char *) zalloc(max_header_length);
       if (*header == NULL)
@@ -2078,6 +2236,7 @@ jb_err server_last_modified(struct client_state *csp, char **header)
    return JB_ERR_OK;
 }
 
+
 /*********************************************************************
  *
  * Function    :  client_accept_encoding
@@ -2365,13 +2524,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);  
@@ -2507,9 +2666,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...)
@@ -2524,25 +2684,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)
+   if (csp->action->flags & ACTION_NO_COOKIE_READ)
    {
-      /* strlen("cookie: ") == 8 */
-      result = enlist(csp->cookie_list, *header + 8);
-   }
-   else
-   {
-      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;
 }
 
 
@@ -2608,26 +2756,42 @@ jb_err client_x_forwarded(struct client_state *csp, char **header)
  *********************************************************************/
 jb_err client_max_forwards(struct client_state *csp, char **header)
 {
-   unsigned int max_forwards;
+   int max_forwards;
 
    if ((0 == strcmpic(csp->http->gpc, "trace")) ||
        (0 == strcmpic(csp->http->gpc, "options")))
    {
-      if (1 == sscanf(*header, "Max-Forwards: %u", &max_forwards))
+      assert(*(*header+12) == ':');
+      if (1 == sscanf(*header+12, ": %u", &max_forwards))
       {
-         if (max_forwards-- > 0)
+         if (max_forwards > 0)
+         {
+            snprintf(*header, strlen(*header)+1, "Max-Forwards: %u", --max_forwards);
+            log_error(LOG_LEVEL_HEADER, "Max-Forwards value for %s request reduced to %u.",
+               csp->http->gpc, max_forwards);
+         }
+         else if (max_forwards < 0)
          {
-            snprintf(*header, strlen(*header)+1, "Max-Forwards: %u", max_forwards);
-            log_error(LOG_LEVEL_HEADER, "Max-Forwards header for %s request replaced with: %s",
-               csp->http->gpc, *header);
+            log_error(LOG_LEVEL_ERROR, "Crunching invalid header: %s", *header);
+            freez(*header);
          }
          else
          {
-            /* FIXME: Follow spec and intercept the request. */
+            /*
+             * Not supposed to be reached. direct_response() which
+             * was already called earlier in chat() should have
+             * intercepted the request.
+             */
             log_error(LOG_LEVEL_ERROR,
                "Non-intercepted %s request with Max-Forwards zero!", csp->http->gpc);
+            assert(max_forwards != 0);
          }
       }
+      else
+      {
+         log_error(LOG_LEVEL_ERROR, "Crunching invalid header: %s", *header);
+         freez(*header);
+      }
    }
 
    return JB_ERR_OK;
@@ -2707,6 +2871,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;
 }
 
@@ -2923,8 +3090,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;
    }
 
@@ -2940,6 +3115,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;
@@ -2953,6 +3129,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...)
  *
@@ -3033,6 +3211,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")) )
    {
@@ -3128,9 +3308,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  :
@@ -3142,7 +3323,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");
 }
 
@@ -3170,6 +3371,9 @@ jb_err connection_close_adder(struct client_state *csp)
  *********************************************************************/
 jb_err server_http(struct client_state *csp, char **header)
 {
+   /* Signal that were now parsing server headers. */
+   csp->flags |= CSP_FLAG_CLIENT_HEADER_PARSING_DONE;
+
    sscanf(*header, "HTTP/%*d.%*d %d", &(csp->http->status));
    if (csp->http->status == 206)
    {
@@ -3191,8 +3395,13 @@ jb_err server_http(struct client_state *csp, char **header)
  * Function    :  server_set_cookie
  *
  * Description :  Handle the server "cookie" header properly.
- *                Log cookie to the jar file.  Then "crunch" it,
- *                or accept it.  Called from `sed'.
+ *                Log cookie to the jar file.  Then "crunch",
+ *                accept or rewrite it to a session cookie.
+ *                Called from `sed'.
+ *
+ *                TODO: Allow the user to specify a new expiration
+ *                time to cause the cookie to expire even before the
+ *                browser is closed.
  *
  * Parameters  :
  *          1  :  csp = Current client state (buffers, headers, etc...)
@@ -3207,6 +3416,12 @@ jb_err server_http(struct client_state *csp, char **header)
  *********************************************************************/
 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
    if (csp->config->jar)
    {
@@ -3217,17 +3432,15 @@ jb_err server_set_cookie(struct client_state *csp, char **header)
        * the %z field in strftime()
        */
       char tempbuf[ BUFFER_SIZE ];
-      time_t now; 
-      struct tm tm_now; 
-      time (&now); 
 #ifdef HAVE_LOCALTIME_R
       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); 
 
@@ -3280,22 +3493,146 @@ jb_err server_set_cookie(struct client_state *csp, char **header)
             next_tag = cur_tag + strlen(cur_tag);
          }
 
-         /* Is this the "Expires" tag? */
-         if (strncmpic(cur_tag, "expires=", 8) == 0)
+         /*
+          * Check the expiration date to see
+          * if the cookie is still valid, if yes,
+          * rewrite it to a session cookie.
+          */
+         if ((strncmpic(cur_tag, "expires=", 8) == 0) && *(cur_tag + 8))
          {
-            /* Delete the tag 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.)
+            char *match;
+            const char *expiration_date = cur_tag + 8; /* Skip "[Ee]xpires=" */
+            memset(&tm_cookie, 0, sizeof(tm_cookie));
+            /*
+             * Try the valid time formats we know about.
+             *
+             * XXX: This should be moved to parse_header_time().
+             *
+             * 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.
              */
-            memmove(cur_tag, next_tag, strlen(next_tag) + 1);
+            if (NULL != (match = strptime(expiration_date, "%a, %e-%b-%y %H:%M:%S ", &tm_cookie)))
+            {
+               /* 22-Feb-2008 12:01:18 GMT */
+               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(expiration_date, "%A, %e-%b-%Y %H:%M:%S ", &tm_cookie)))
+            {
+               /* Tue, 02-Jun-2037 20:00:00 GMT */
+               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(expiration_date, "%a, %e-%b-%Y %H:%M:%S ", &tm_cookie)))
+            {
+               /* Tuesday, 02-Jun-2037 20:00:00 GMT */
+               /*
+                * On FreeBSD this is never reached because it's handled
+                * by "format 2" as well. I am, however, not sure if all
+                * strptime() implementations behave that way.
+                */
+               log_error(LOG_LEVEL_HEADER,
+                  "cookie \'%s\' send by %s appears to be using time format 3.",
+                   *header, csp->http->url);
+            }
+            else if (NULL != (match = strptime(expiration_date, "%a, %e %b %Y %H:%M:%S ", &tm_cookie)))
+            {
+               /* Fri, 22 Feb 2008 19:20:05 GMT */
+               log_error(LOG_LEVEL_HEADER,
+                  "cookie \'%s\' send by %s appears to be using time format 4.",
+                   *header, csp->http->url);
+            }
+            else if (NULL != (match = strptime(expiration_date, "%A %b %e %H:%M:%S %Y", &tm_cookie)))
+            {
+               /* Thu Mar 08 23:00:00 2007 GMT */
+               log_error(LOG_LEVEL_HEADER,
+                  "cookie \'%s\' send by %s appears to be using time format 5.",
+                   *header, csp->http->url);
+            }
 
-            /* That changed the header, need to issue a log message */
-            changed = 1;
+            /* Did any of them match? */
+            if (NULL == match)
+            {
+               /*
+                * Nope, treat it as if it was still valid.
+                *
+                * XXX: Should we remove the whole cookie instead?
+                */
+               log_error(LOG_LEVEL_ERROR,
+                  "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;
+            }
+            else
+            {
+               /*
+                * Yes. Check if the cookie is still valid.
+                *
+                * If the cookie is already expired it's probably
+                * a delete cookie and even if it isn't, the browser
+                * will discard it anyway.
+                */
+
+               /*
+                * XXX: timegm() isn't available on some AmigaOS
+                * versions and our replacement doesn't work.
+                *
+                * Our options are to either:
+                *
+                * - disable session-cookies-only completely if timegm
+                *   is missing,
+                *
+                * - to simply remove all expired tags, like it has
+                *   been done until Privoxy 3.0.6 and to live with
+                *    the consequence that it can cause login/logout
+                *   problems on servers that don't validate their
+                *   input properly, or
+                *
+                * - to replace it with mktime in which
+                *   case there is a slight chance of valid cookies
+                *   passing as already expired.
+                *
+                *   This is the way it's currently done and it's not
+                *   as bad as it sounds. If the missing GMT offset is
+                *   enough to change the result of the expiration check
+                *   the cookie will be only valid for a few hours
+                *   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,
+                     "Cookie \'%s\' is already expired and can pass unmodified.", *header);
+                  /* Just in case some clown sets more then one expiration date */
+                  cur_tag = next_tag;
+               }
+               else
+               {
+                  /*
+                   * 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.)
+                   */
+                  memmove(cur_tag, next_tag, strlen(next_tag) + 1);
+
+                  /* That changed the header, need to issue a log message */
+                  changed = 1;
+
+                  /*
+                   * Note that the next tag has now been moved to *cur_tag,
+                   * so we do not need to update the cur_tag pointer.
+                   */
+               }
+            }
 
-            /* Note that the next tag has now been moved to *cur_tag,
-             * so we do not need to update the cur_tag pointer.
-             */
          }
          else
          {
@@ -3306,7 +3643,9 @@ jb_err server_set_cookie(struct client_state *csp, char **header)
 
       if (changed)
       {
-         log_error(LOG_LEVEL_HEADER, "Changed cookie to a temporary one.");
+         assert(NULL != *header);
+         log_error(LOG_LEVEL_HEADER, "Cookie rewritten to a temporary one: %s",
+            *header);
       }
    }