Add an enable-compression directive and disable compression by default
[privoxy.git] / parsers.c
index 1fb9983..fc8f6c9 100644 (file)
--- a/parsers.c
+++ b/parsers.c
@@ -1,4 +1,4 @@
-const char parsers_rcs[] = "$Id: parsers.c,v 1.208 2009/09/06 14:10:07 fabiankeil Exp $";
+const char parsers_rcs[] = "$Id: parsers.c,v 1.224 2011/06/23 14:01:01 fabiankeil Exp $";
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/parsers.c,v $
@@ -152,6 +152,9 @@ static jb_err server_http               (struct client_state *csp, char **header
 static jb_err crunch_server_header      (struct client_state *csp, char **header);
 static jb_err server_last_modified      (struct client_state *csp, char **header);
 static jb_err server_content_disposition(struct client_state *csp, char **header);
+#ifdef FEATURE_ZLIB
+static jb_err server_adjust_content_encoding(struct client_state *csp, char **header);
+#endif
 
 #ifdef FEATURE_CONNECTION_KEEP_ALIVE
 static jb_err server_save_content_length(struct client_state *csp, char **header);
@@ -318,7 +321,7 @@ long flush_socket(jb_socket fd, struct iob *iob)
 jb_err add_to_iob(struct client_state *csp, char *buf, long n)
 {
    struct iob *iob = csp->iob;
-   size_t used, offset, need, want;
+   size_t used, offset, need;
    char *p;
 
    if (n <= 0) return JB_ERR_OK;
@@ -341,7 +344,12 @@ jb_err add_to_iob(struct client_state *csp, char *buf, long n)
 
    if (need > iob->size)
    {
-      for (want = csp->iob->size ? csp->iob->size : 512; want <= need;) want *= 2;
+      size_t want = csp->iob->size ? csp->iob->size : 512;
+
+      while (want <= need)
+      {
+         want *= 2;
+      }
       
       if (want <= csp->config->buffer_limit && NULL != (p = (char *)realloc(iob->buf, want)))
       {
@@ -611,10 +619,12 @@ jb_err decompress_iob(struct client_state *csp)
       }
 
       /*
-       * If we tried the limit and still didn't have enough
-       * memory, just give up.
+       * If we reached the buffer limit and still didn't have enough
+       * memory, just give up. Due to the ceiling enforced by the next
+       * if block we could actually check for equality here, but as it
+       * can be easily mistaken for a bug we don't.
        */
-      if (bufsize == csp->config->buffer_limit)
+      if (bufsize >= csp->config->buffer_limit)
       {
          log_error(LOG_LEVEL_ERROR, "Buffer limit reached while decompressing iob");
          return JB_ERR_MEMORY;
@@ -1132,7 +1142,7 @@ jb_err update_server_headers(struct client_state *csp)
       { "Content-Length:",    15, server_adjust_content_length },
       { "Transfer-Encoding:", 18, server_transfer_coding },
 #ifdef FEATURE_ZLIB
-      { "Content-Encoding:",  17, server_content_encoding },
+      { "Content-Encoding:",  17, server_adjust_content_encoding },
 #endif /* def FEATURE_ZLIB */
       { NULL,                  0, NULL }
    };
@@ -1177,6 +1187,18 @@ jb_err update_server_headers(struct client_state *csp)
    }
 #endif /* def FEATURE_CONNECTION_KEEP_ALIVE */
 
+#ifdef FEATURE_COMPRESSION
+   if ((JB_ERR_OK == err)
+      && (csp->flags & CSP_FLAG_BUFFERED_CONTENT_DEFLATED))
+   {
+      err = enlist_unique_header(csp->headers, "Content-Encoding", "deflate");
+      if (JB_ERR_OK == err)
+      {
+         log_error(LOG_LEVEL_HEADER, "Added header: Content-Encoding: deflate");
+      }
+   }
+#endif
+
    return err;
 }
 
@@ -1209,7 +1231,6 @@ static jb_err header_tagger(struct client_state *csp, char *header)
    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)
@@ -1223,21 +1244,7 @@ static jb_err header_tagger(struct client_state *csp, char *header)
       multi_action_index = ACTION_MULTI_CLIENT_HEADER_TAGGER;
    }
 
-   /* Check if there are any filters */
-   for (i = 0; i < MAX_AF_FILES; i++)
-   {
-      fl = csp->rlist[i];
-      if (NULL != fl)
-      {
-         if (NULL != fl->f)
-         {
-           found_filters = 1;
-           break;
-         }
-      }
-   }
-
-   if (0 == found_filters)
+   if (filters_available(csp) == FALSE)
    {
       log_error(LOG_LEVEL_ERROR, "Inconsistent configuration: "
          "tagging enabled, but no taggers available.");
@@ -1420,7 +1427,7 @@ static jb_err filter_header(struct client_state *csp, char **header)
    struct re_filterfile_spec *b;
    struct list_entry *filtername;
 
-   int i, found_filters = 0;
+   int i;
    int wanted_filter_type;
    int multi_action_index;
 
@@ -1440,23 +1447,7 @@ static jb_err filter_header(struct client_state *csp, char **header)
       multi_action_index = ACTION_MULTI_CLIENT_HEADER_FILTER;
    }
 
-   /*
-    * Need to check the set of re_filterfiles...
-    */
-   for (i = 0; i < MAX_AF_FILES; i++)
-   {
-      fl = csp->rlist[i];
-      if (NULL != fl)
-      {
-         if (NULL != fl->f)
-         {
-           found_filters = 1;
-           break;
-         }
-      }
-   }
-
-   if (0 == found_filters)
+   if (filters_available(csp) == FALSE)
    {
       log_error(LOG_LEVEL_ERROR, "Inconsistent configuration: "
          "header filtering enabled, but no matching filters available.");
@@ -1982,7 +1973,7 @@ static jb_err crunch_server_header(struct client_state *csp, char **header)
  * Function    :  server_content_type
  *
  * Description :  Set the content-type for filterable types (text/.*,
- *                .*xml.*, javascript and image/gif) unless filtering has been
+ *                .*xml.*, .*script.* 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
@@ -2032,7 +2023,7 @@ static jb_err server_content_type(struct client_state *csp, char **header)
        */
       if ((strstr(*header, "text/") && !strstr(*header, "plain"))
         || strstr(*header, "xml")
-        || strstr(*header, "application/x-javascript"))
+        || strstr(*header, "script"))
       {
          csp->content_type |= CT_TEXT;
       }
@@ -2048,7 +2039,7 @@ static jb_err server_content_type(struct client_state *csp, char **header)
    if (csp->action->flags & ACTION_CONTENT_TYPE_OVERWRITE)
    {
       /*
-       * Make sure the user doesn't accidently
+       * Make sure the user doesn't accidentally
        * change the content type of binary documents. 
        */
       if ((csp->content_type & CT_TEXT) || (csp->action->flags & ACTION_FORCE_TEXT_MODE))
@@ -2142,16 +2133,16 @@ static jb_err server_transfer_coding(struct client_state *csp, char **header)
  *
  * Function    :  server_content_encoding
  *
- * Description :  This function is run twice for each request,
- *                unless FEATURE_ZLIB and filtering are disabled.
+ * Description :  Used to check if the content is compressed, and if
+ *                FEATURE_ZLIB is disabled, filtering is disabled as
+ *                well.
  *
- *                The first run is used to check if the content
- *                is compressed, if FEATURE_ZLIB is disabled
- *                filtering is then disabled as well, if FEATURE_ZLIB
- *                is enabled the content is marked for decompression.
+ *                If FEATURE_ZLIB is enabled and the compression type
+ *                supported, the content is marked for decompression.
  *                
- *                The second run is used to remove the Content-Encoding
- *                header if the decompression was successful.
+ *                XXX: Doesn't properly deal with multiple or with
+ *                     unsupported but unknown encodings.
+ *                     Is case-sensitive but shouldn't be.
  *
  * Parameters  :
  *          1  :  csp = Current client state (buffers, headers, etc...)
@@ -2167,19 +2158,25 @@ static jb_err server_transfer_coding(struct client_state *csp, char **header)
 static jb_err server_content_encoding(struct client_state *csp, char **header)
 {
 #ifdef FEATURE_ZLIB
-   if ((csp->flags & CSP_FLAG_MODIFIED)
-    && (csp->content_type & (CT_GZIP | CT_DEFLATE)))
+   if (strstr(*header, "sdch"))
    {
       /*
-       * We successfully decompressed the content,
-       * and have to clean the header now, so the
-       * client no longer expects compressed data..
-       *
-       * XXX: There is a difference between cleaning
-       * and removing it completely.
+       * Shared Dictionary Compression over HTTP isn't supported,
+       * filtering it anyway is pretty much guaranteed to mess up
+       * the encoding.
        */
-      log_error(LOG_LEVEL_HEADER, "Crunching: %s", *header);
-      freez(*header);
+      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,
+            "SDCH-compressed content detected, content filtering disabled. "
+            "Consider suppressing SDCH offers made by the client.");
+      }
    }
    else if (strstr(*header, "gzip"))
    {
@@ -2200,7 +2197,16 @@ static jb_err server_content_encoding(struct client_state *csp, char **header)
       csp->content_type |= CT_TABOO;
    }
 #else /* !defined(FEATURE_ZLIB) */
-   if (strstr(*header, "gzip") || strstr(*header, "compress") || strstr(*header, "deflate"))
+   /*
+    * XXX: Using a black list here isn't the right approach.
+    *
+    *      In case of SDCH, building with zlib support isn't
+    *      going to help.
+    */
+   if (strstr(*header, "gzip") ||
+       strstr(*header, "compress") ||
+       strstr(*header, "deflate") ||
+       strstr(*header, "sdch"))
    {
       /*
        * Body is compressed, turn off pcrs and gif filtering.
@@ -2226,6 +2232,49 @@ static jb_err server_content_encoding(struct client_state *csp, char **header)
 }
 
 
+#ifdef FEATURE_ZLIB
+/*********************************************************************
+ *
+ * Function    :  server_adjust_content_encoding
+ *
+ * Description :  Remove the Content-Encoding header if the
+ *                decompression was successful and the content
+ *                has been modifed.
+ *
+ * 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.
+ *
+ *********************************************************************/
+static jb_err server_adjust_content_encoding(struct client_state *csp, char **header)
+{
+   if ((csp->flags & CSP_FLAG_MODIFIED)
+    && (csp->content_type & (CT_GZIP | CT_DEFLATE)))
+   {
+      /*
+       * We successfully decompressed the content,
+       * and have to clean the header now, so the
+       * client no longer expects compressed data..
+       *
+       * XXX: There is a difference between cleaning
+       * and removing it completely.
+       */
+      log_error(LOG_LEVEL_HEADER, "Crunching: %s", *header);
+      freez(*header);
+   }
+
+   return JB_ERR_OK;
+
+}
+#endif /* defined(FEATURE_ZLIB) */
+
+
 /*********************************************************************
  *
  * Function    :  server_adjust_content_length
@@ -2420,15 +2469,9 @@ static jb_err server_last_modified(struct client_state *csp, char **header)
 {
    const char *newval;
    char buf[BUFFER_SIZE];
-
+   time_t last_modified;
    char newheader[50];
-#ifdef HAVE_GMTIME_R
-   struct tm gmt;
-#endif
-   struct tm *timeptr = NULL;
-   time_t now, last_modified;                  
-   long int days, hours, minutes, seconds;
-   
+
    /*
     * Are we messing with the Last-Modified header?
     */
@@ -2473,16 +2516,7 @@ static jb_err server_last_modified(struct client_state *csp, char **header)
       const char *header_time = *header + sizeof("Last-Modified:");
 
       log_error(LOG_LEVEL_HEADER, "Randomizing: %s", *header);
-      now = time(NULL);
-#ifdef HAVE_GMTIME_R
-      gmtime_r(&now, &gmt);
-#elif defined(MUTEX_LOCKS_AVAILABLE)
-      privoxy_mutex_lock(&gmtime_mutex);
-      gmtime(&now);
-      privoxy_mutex_unlock(&gmtime_mutex);
-#else
-      gmtime(&now);
-#endif
+
       if (JB_ERR_OK != parse_header_time(header_time, &last_modified))
       {
          log_error(LOG_LEVEL_HEADER, "Couldn't parse: %s in %s (crunching!)", header_time, *header);
@@ -2490,9 +2524,17 @@ static jb_err server_last_modified(struct client_state *csp, char **header)
       }
       else
       {
-         long int rtime = (long int)difftime(now, last_modified);
+         time_t now;
+         struct tm *timeptr = NULL;
+         long int rtime;
+#ifdef HAVE_GMTIME_R
+         struct tm gmt;
+#endif
+         now = time(NULL);
+         rtime = (long int)difftime(now, last_modified);
          if (rtime)
          {
+            long int days, hours, minutes, seconds;
             const int negative_delta = (rtime < 0);
 
             if (negative_delta)
@@ -2577,25 +2619,17 @@ static jb_err server_last_modified(struct client_state *csp, char **header)
  *********************************************************************/
 static jb_err client_accept_encoding(struct client_state *csp, char **header)
 {
+#ifdef FEATURE_COMPRESSION
+   if ((csp->config->feature_flags & RUNTIME_FEATURE_COMPRESSION)
+      && strstr(*header, "deflate"))
+   {
+      csp->flags |= CSP_FLAG_CLIENT_SUPPORTS_DEFLATE;
+   }
+#endif
    if ((csp->action->flags & ACTION_NO_COMPRESSION) != 0)
    {
       log_error(LOG_LEVEL_HEADER, "Suppressed offer to compress content");
-
       freez(*header);
-
-      /* Temporarily disable the correct behaviour to
-       * work around a PHP bug. 
-       *
-       * if (!strcmpic(csp->http->ver, "HTTP/1.1"))
-       * {
-       *    *header = strdup("Accept-Encoding: identity;q=1.0, *;q=0");
-       *    if (*header == NULL)
-       *    {
-       *       return JB_ERR_MEMORY;
-       *    }
-       * }
-       * 
-       */
    }
 
    return JB_ERR_OK;
@@ -3179,7 +3213,6 @@ static jb_err client_if_modified_since(struct client_state *csp, char **header)
    struct tm *timeptr = NULL;
    time_t tm = 0;                  
    const char *newval;
-   long int hours, minutes, seconds;
    char * endptr;
    
    if ( 0 == strcmpic(*header, "If-Modified-Since: Wed, 08 Jun 1955 12:00:00 GMT"))
@@ -3214,6 +3247,7 @@ static jb_err client_if_modified_since(struct client_state *csp, char **header)
          }
          else
          {
+            long int hours, minutes, seconds;
             long int rtime = strtol(newval, &endptr, 0);
             const int negative_range = (rtime < 0);
 
@@ -3556,7 +3590,7 @@ static jb_err client_x_forwarded_for_adder(struct client_state *csp)
  *
  * Function    :  server_connection_adder
  *
- * Description :  Adds an appropiate "Connection:" header to csp->headers
+ * Description :  Adds an appropriate "Connection:" header to csp->headers
  *                unless the header was already present. Called from `sed'.
  *
  * Parameters  :
@@ -3609,7 +3643,7 @@ static jb_err server_connection_adder(struct client_state *csp)
  *
  * Description :  Adds a "Proxy-Connection: keep-alive" header to
  *                csp->headers if the client asked for keep-alive.
- *                XXX: We should reuse existant ones.
+ *                XXX: We should reuse existent ones.
  *
  * Parameters  :
  *          1  :  csp = Current client state (buffers, headers, etc...)
@@ -3811,6 +3845,17 @@ static jb_err server_set_cookie(struct client_state *csp, char **header)
          {
             char *expiration_date = cur_tag + 8; /* Skip "[Ee]xpires=" */
 
+            if ((expiration_date[0] == '"')
+             && (expiration_date[1] != '\0'))
+            {
+               /*
+                * Skip quotation mark. RFC 2109 10.1.2 seems to hint
+                * that the expiration date isn't supposed to be quoted,
+                * but some servers do it anyway.
+                */
+               expiration_date++;
+            }
+
             /* Did we detect the date properly? */
             if (JB_ERR_OK != parse_header_time(expiration_date, &cookie_time))
             {
@@ -3910,7 +3955,7 @@ static jb_err server_set_cookie(struct client_state *csp, char **header)
  *
  * Function    :  strclean
  *
- * Description :  In-Situ-Eliminate all occurances of substring in
+ * Description :  In-Situ-Eliminate all occurrences of substring in
  *                string
  *
  * Parameters  :
@@ -4030,7 +4075,8 @@ jb_err get_destination_from_headers(const struct list *headers, struct http_requ
       return JB_ERR_PARSE;
    }
 
-   if (NULL == (p = strdup((host))))
+   p = strdup(host);
+   if (NULL == p)
    {
       log_error(LOG_LEVEL_ERROR, "Out of memory while parsing \"Host:\" header");
       return JB_ERR_MEMORY;
@@ -4174,7 +4220,7 @@ static jb_err handle_conditional_hide_referrer_parameter(char **header,
    const char *host, const int parameter_conditional_block)
 {
    char *referer = strdup(*header);
-   const size_t hostlenght = strlen(host);
+   const size_t hostlength = strlen(host);
    const char *referer_url = NULL;
 
    if (NULL == referer)
@@ -4184,14 +4230,14 @@ static jb_err handle_conditional_hide_referrer_parameter(char **header,
    }
 
    /* referer begins with 'Referer: http[s]://' */
-   if ((hostlenght+17) < strlen(referer))
+   if ((hostlength+17) < strlen(referer))
    {
       /*
        * Shorten referer to make sure the referer is blocked
        * if www.example.org/www.example.com-shall-see-the-referer/
        * links to www.example.com/
        */
-      referer[hostlenght+17] = '\0';
+      referer[hostlength+17] = '\0';
    }
    referer_url = strstr(referer, "http://");
    if ((NULL == referer_url) || (NULL == strstr(referer_url, host)))