Reference action files in CGI URLs by id instead
[privoxy.git] / parsers.c
index 4aa786e..cf2eea8 100644 (file)
--- a/parsers.c
+++ b/parsers.c
@@ -1,4 +1,4 @@
-const char parsers_rcs[] = "$Id: parsers.c,v 1.92 2007/03/05 13:25:32 fabiankeil Exp $";
+const char parsers_rcs[] = "$Id: parsers.c,v 1.95 2007/03/25 14:26:40 fabiankeil Exp $";
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/parsers.c,v $
@@ -44,6 +44,29 @@ const char parsers_rcs[] = "$Id: parsers.c,v 1.92 2007/03/05 13:25:32 fabiankeil
  *
  * Revisions   :
  *    $Log: parsers.c,v $
+ *    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
@@ -634,6 +657,13 @@ const char parsers_rcs[] = "$Id: parsers.c,v 1.92 2007/03/05 13:25:32 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
@@ -881,6 +911,7 @@ 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 */
@@ -891,6 +922,7 @@ jb_err decompress_iob(struct client_state *csp)
 
    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;
 
@@ -968,7 +1000,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
@@ -990,14 +1022,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. */
@@ -1005,6 +1039,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)
@@ -1186,15 +1232,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
    {
@@ -1206,6 +1255,8 @@ jb_err decompress_iob(struct client_state *csp)
       return JB_ERR_COMPRESS;
    }
 
+   return JB_ERR_OK;
+
 }
 #endif /* defined(FEATURE_ZLIB) */
 
@@ -1932,8 +1983,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;
 
@@ -2148,6 +2211,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
@@ -2159,9 +2224,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
@@ -2908,9 +2973,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
@@ -3396,7 +3463,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
@@ -3428,8 +3494,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)
    {
@@ -3477,62 +3543,10 @@ jb_err server_set_cookie(struct client_state *csp, char **header)
           */
          if ((strncmpic(cur_tag, "expires=", 8) == 0) && *(cur_tag + 8))
          {
-            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.
-             */
-            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);
-            }
+            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.
@@ -3580,7 +3594,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,
@@ -3673,54 +3686,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)
+   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)))
    {
-      timeptr = NULL;
-   }
-   else
-   {
-      *tm = timegm(&gmt);
-      timeptr = &gmt;
+      return JB_ERR_PARSE;
    }
-   return(timeptr);
+
+   *result = timegm(&gmt);
+
+   return JB_ERR_OK;
 
 }