Merge in Debian changes from Debian packages 3.0.5-beta-3 and 3.0.6-1.
[privoxy.git] / filters.c
index 1a1da40..1300a7e 100644 (file)
--- a/filters.c
+++ b/filters.c
@@ -1,4 +1,4 @@
-const char filters_rcs[] = "$Id: filters.c,v 1.63 2006/08/31 10:11:28 fabiankeil Exp $";
+const char filters_rcs[] = "$Id: filters.c,v 1.69 2006/12/08 12:39:13 fabiankeil Exp $";
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/filters.c,v $
@@ -10,9 +10,10 @@ const char filters_rcs[] = "$Id: filters.c,v 1.63 2006/08/31 10:11:28 fabiankeil
  *                   `filter_popups', `forward_url', 'redirect_url',
  *                   `ij_untrusted_url', `intercept_url', `pcrs_filter_respose',
  *                   `ijb_send_banner', `trust_url', `gif_deanimate_response',
- *                   `jpeg_inspect_response'
+ *                   `jpeg_inspect_response', `execute_single_pcrs_command',
+ *                   `rewrite_url', `get_last_url'
  *
- * Copyright   :  Written by and Copyright (C) 2001, 2004 the SourceForge
+ * Copyright   :  Written by and Copyright (C) 2001, 2004-2006 the SourceForge
  *                Privoxy team. http://www.privoxy.org/
  *
  *                Based on the Internet Junkbuster originally written
@@ -39,6 +40,28 @@ const char filters_rcs[] = "$Id: filters.c,v 1.63 2006/08/31 10:11:28 fabiankeil
  *
  * Revisions   :
  *    $Log: filters.c,v $
+ *    Revision 1.69  2006/12/08 12:39:13  fabiankeil
+ *    Let get_last_url() catch https URLs as well.
+ *
+ *    Revision 1.68  2006/12/05 14:45:48  fabiankeil
+ *    Make sure get_last_url() behaves like advertised
+ *    and fast-redirects{} can be combined with redirect{}.
+ *
+ *    Revision 1.67  2006/11/28 15:19:43  fabiankeil
+ *    Implemented +redirect{s@foo@bar@} to generate
+ *    a redirect based on a rewritten version of the
+ *    original URL.
+ *
+ *    Revision 1.66  2006/09/23 13:26:38  roro
+ *    Replace TABs by spaces in source code.
+ *
+ *    Revision 1.65  2006/09/21 12:54:43  fabiankeil
+ *    Fix +redirect{}. Didn't work with -fast-redirects.
+ *
+ *    Revision 1.64  2006/08/31 10:55:49  fabiankeil
+ *    Block requests for untrusted URLs with status
+ *    code 403 instead of 200.
+ *
  *    Revision 1.63  2006/08/31 10:11:28  fabiankeil
  *    Don't free p which is still in use and will be later
  *    freed by free_map(). Don't claim the referrer is unknown
@@ -765,7 +788,7 @@ struct http_response *block_url(struct client_state *csp)
    if (((csp->action->flags & ACTION_IMAGE_BLOCKER) != 0)
         && is_imageurl(csp))
    {
-         char *p;
+      char *p;
       /* determine HOW images should be blocked */
       p = csp->action->string[ACTION_STRING_IMAGE_BLOCKER];
 
@@ -1113,102 +1136,312 @@ struct http_response *trust_url(struct client_state *csp)
 }
 #endif /* def FEATURE_TRUST */
 
+/*********************************************************************
+ *
+ * Function    :  execute_single_pcrs_command
+ *
+ * Description :  Apply single pcrs command to the subject.
+ *                The subject itself is left untouched, memory for the result
+ *                is malloc()ed and it is the caller's responsibility to free
+ *                the result when it's no longer needed.
+ *
+ * Parameters  :
+ *          1  :  subject = the subject (== original) string
+ *          2  :  pcrs_command = the pcrs command as string (s@foo@bar@) 
+ *          3  :  hits = int* for returning  the number of modifications 
+ *
+ * Returns     :  NULL in case of errors, otherwise the
+ *                result of the pcrs command.  
+ *
+ *********************************************************************/
+char *execute_single_pcrs_command(char *subject, const char *pcrs_command, int *hits)
+{
+   int error;
+   size_t size;
+   char *result = NULL;
+   pcrs_job *job;
+
+   assert(subject);
+   assert(pcrs_command);
+
+   *hits = 0;
+   size = strlen(subject);
+
+   if (NULL == (job = pcrs_compile_command(pcrs_command, &error)))
+   {
+      log_error(LOG_LEVEL_ERROR, "Failed to compile pcrs command \"%s\". Error: %d.",
+         pcrs_command, error);
+   }
+   else if ((*hits = pcrs_execute(job, subject, size, &result, &size)) < 0)
+   {
+      log_error(LOG_LEVEL_ERROR, "Failed to execute pcrs command: %s", pcrs_strerror(*hits));
+      *hits = 0;
+      freez(result);
+   }
+
+   if (job)
+   {
+      job = pcrs_free_job(job);
+   }
+
+   return result;
+
+}
 
-#ifdef FEATURE_FAST_REDIRECTS
 /*********************************************************************
  *
- * Function    :  redirect_url
+ * Function    :  rewrite_url
  *
- * Description :  Checks for redirection URLs and returns a HTTP redirect
- *                to the destination URL, if necessary
+ * Description :  Rewrites a URL with a single pcrs command
+ *                and returns the result if it differs from the
+ *                original and isn't obviously invalid.
  *
  * Parameters  :
- *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          1  :  old_url = URL to rewrite.
+ *          2  :  pcrs_command = pcrs command formatted as string (s@foo@bar@)
  *
- * Returns     :  NULL if URL was clean, HTTP redirect otherwise.
+ *
+ * Returns     :  NULL if the pcrs_command didn't change the url, or 
+ *                the result of the modification.
  *
  *********************************************************************/
-struct http_response *redirect_url(struct client_state *csp)
+char *rewrite_url(char *old_url, const char *pcrs_command)
 {
-   char *p, *q;
-   struct http_response *rsp;
-   char *redirect_mode = NULL;
-   int x, y;
+   char *new_url = NULL;
+   int hits;
 
-   if ((csp->action->flags & ACTION_REDIRECT))
+   assert(old_url);
+   assert(pcrs_command);
+
+   new_url = execute_single_pcrs_command(old_url, pcrs_command, &hits);
+
+   if (hits == 0)
    {
-      q = csp->action->string[ACTION_STRING_REDIRECT];
+      log_error(LOG_LEVEL_REDIRECTS,
+         "pcrs command \"%s\" didn't change \"%s\".",
+         pcrs_command, old_url, new_url);
+      freez(new_url);
+   }
+   else if (strncmpic(new_url, "http://", 7) && strncmpic(new_url, "https://", 8))
+   {
+      log_error(LOG_LEVEL_ERROR,
+         "pcrs command \"%s\" changed \"%s\" to \"%s\" (%u hi%s), "
+         "but the result doesn't look like a valid URL and will be ignored.",
+         pcrs_command, old_url, new_url, hits, (hits == 1) ? "t" : "ts");
+      freez(new_url);
    }
    else
    {
-      redirect_mode = csp->action->string[ACTION_STRING_FAST_REDIRECTS];
-      if (0 == strcmpic(redirect_mode, "check-decoded-url"))
-      {  
-         p = q = csp->http->path; 
-         log_error(LOG_LEVEL_REDIRECTS, "Decoding path: %s if necessary.", p);
-         while (*p)
-         {
-            if (*p == '%') /* Escape sequence? */
-            {
-               /* Yes, translate from hexadecimal to decimal */
-               p++;
-               /* First byte */
-               x=((int)*p++)-48;
-               if (x>9) x-=7;
-               x<<=4;
-               /* Second byte */
-               y=((int)*p++)-48;
-               if (y>9)y-=7;
-               /* Merge */
-               *q++=(char)(x|y);
-            }
-            else
-            {
-               /* No, forward character. */
-               *q++=*p++;
-            }
-         }
-         *q='\0';
+      log_error(LOG_LEVEL_REDIRECTS,
+         "pcrs command \"%s\" changed \"%s\" to \"%s\" (%u hi%s).",
+         pcrs_command, old_url, new_url, hits, (hits == 1) ? "t" : "ts");
+   }
+
+   return new_url;
+
+}
+
+
+#ifdef FEATURE_FAST_REDIRECTS
+/*********************************************************************
+ *
+ * Function    :  get_last_url
+ *
+ * Description :  Search for the last URL inside a string.
+ *                If the string already is a URL, it will
+ *                be the first URL found.
+ *
+ * Parameters  :
+ *          1  :  subject = the string to check
+ *          2  :  redirect_mode = +fast-redirect{} mode 
+ *
+ * Returns     :  NULL if no URL was found, or
+ *                the last URL found.
+ *
+ *********************************************************************/
+char *get_last_url(char *subject, const char *redirect_mode)
+{
+   char *new_url = NULL;
+   char *tmp;
+
+   assert(subject);
+   assert(redirect_mode);
+
+   subject = strdup(subject);
+   if (subject == NULL)
+   {
+      log_error(LOG_LEVEL_ERROR, "Out of memory while searching for redirects.");
+      return NULL;
+   }
+
+   if (0 == strcmpic(redirect_mode, "check-decoded-url"))
+   {  
+      log_error(LOG_LEVEL_REDIRECTS, "Decoding \"%s\" if necessary.", subject);
+      new_url = url_decode(subject);
+      if (new_url != NULL)
+      {
+         freez(subject);
+         subject = new_url;
+      }
+      else
+      {
+         log_error(LOG_LEVEL_ERROR, "Unable to decode \"%s\".", subject);
       }
-      p = q = csp->http->path;
-      log_error(LOG_LEVEL_REDIRECTS, "Checking path for redirects: %s", p);
+   }
 
+   log_error(LOG_LEVEL_REDIRECTS, "Checking \"%s\" for redirects.", subject);
+
+   /*
+    * Find the last URL encoded in the request
+    */
+   tmp = subject;
+   while ((tmp = strstr(tmp, "http://")) != NULL)
+   {
+      new_url = tmp++;
+   }
+   tmp = (new_url != NULL) ? new_url : subject;
+   while ((tmp = strstr(tmp, "https://")) != NULL)
+   {
+      new_url = tmp++;
+   }
+
+   if ((new_url != NULL)
+      && (  (new_url != subject)
+         || (0 == strncmpic(subject, "http://", 7))
+         || (0 == strncmpic(subject, "https://", 8))
+         ))
+   {
       /*
-       * find the last URL encoded in the request
+       * Return new URL if we found a redirect 
+       * or if the subject already was a URL.
+       *
+       * The second case makes sure that we can
+       * chain get_last_url after another redirection check
+       * (like rewrite_url) without losing earlier redirects.
        */
-      while ((p = strstr(p, "http://")) != NULL)
-      {
-         q = p++;
-      }
+      new_url = strdup(new_url);
+      freez(subject);
+      return new_url;
    }
+
+   freez(subject);
+   return NULL;
+
+}
+#endif /* def FEATURE_FAST_REDIRECTS */
+
+
+/*********************************************************************
+ *
+ * Function    :  redirect_url
+ *
+ * Description :  Checks if Privoxy should answer the request with
+ *                a HTTP redirect and generates the redirect if
+ *                necessary.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns     :  NULL if the request can pass, HTTP redirect otherwise.
+ *
+ *********************************************************************/
+struct http_response *redirect_url(struct client_state *csp)
+{
+   struct http_response *rsp;
+#ifdef FEATURE_FAST_REDIRECTS
    /*
-    * if there was any, generate and return a HTTP redirect
+    * XXX: Do we still need FEATURE_FAST_REDIRECTS
+    * as compile-time option? The user can easily disable
+    * it in his action file.
     */
-   if (q != csp->http->path)
+   char * redirect_mode;
+#endif /* def FEATURE_FAST_REDIRECTS */
+   char *old_url = NULL;
+   char *new_url = NULL;
+   char *redirection_string;
+
+   if ((csp->action->flags & ACTION_REDIRECT))
    {
-      log_error(LOG_LEVEL_REDIRECTS, "redirecting to: %s", q);
+      redirection_string = csp->action->string[ACTION_STRING_REDIRECT];
 
-      if (NULL == (rsp = alloc_http_response()))
+      /*
+       * If the redirection string begins with 's',
+       * assume it's a pcrs command, otherwise treat it as
+       * properly formatted URL and use it for the redirection
+       * directly.
+       *
+       * According to RFC 2616 section 14.30 the URL
+       * has to be absolute and if the user tries:
+       * +redirect{shit/this/will/be/parsed/as/pcrs_command.html}
+       * she would get undefined results anyway.
+       *
+       */
+
+      if (*redirection_string == 's')
       {
-         return cgi_error_memory();
+         old_url = csp->http->url;
+         new_url = rewrite_url(old_url, redirection_string);
       }
-
-      if ( enlist_unique_header(rsp->headers, "Location", q)
-        || (NULL == (rsp->status = strdup("302 Local Redirect from Privoxy"))) )
+      else
       {
-         free_http_response(rsp);
-         return cgi_error_memory();
+         log_error(LOG_LEVEL_REDIRECTS,
+            "No pcrs command recognized, assuming that \"%s\" is already properly formatted.",
+            redirection_string);
+         new_url = strdup(redirection_string);
       }
+   }
 
-      return finish_http_response(rsp);
+#ifdef FEATURE_FAST_REDIRECTS
+   if ((csp->action->flags & ACTION_FAST_REDIRECTS))
+   {
+      redirect_mode = csp->action->string[ACTION_STRING_FAST_REDIRECTS];
+
+      /*
+       * If it exists, use the previously rewritten URL as input
+       * otherwise just use the old path.
+       */
+      old_url = new_url ? new_url : strdup(csp->http->path);
+      new_url = get_last_url(old_url, redirect_mode);
+      freez(old_url);
    }
-   else
+#endif /* def FEATURE_FAST_REDIRECTS */
+
+   /* Did any redirect action trigger? */   
+   if (new_url)
    {
-      return NULL;
+      if (0 == strcmpic(new_url, csp->http->url))
+      {
+         log_error(LOG_LEVEL_ERROR,
+            "New URL \"%s\" and old URL \"%s\" are the same. Redirection loop prevented.",
+            csp->http->url, new_url);
+            freez(new_url);
+      }
+      else
+      {
+         log_error(LOG_LEVEL_REDIRECTS, "New URL is: %s", new_url);
+
+         if (NULL == (rsp = alloc_http_response()))
+         {
+            freez(new_url);
+            return cgi_error_memory();
+         }
+
+         if ( enlist_unique_header(rsp->headers, "Location", new_url)
+           || (NULL == (rsp->status = strdup("302 Local Redirect from Privoxy"))) )
+         {
+            freez(new_url);
+            free_http_response(rsp);
+            return cgi_error_memory();
+         }
+         freez(new_url);
+         return finish_http_response(rsp);
+      }
    }
 
+   /* Only reached if no redirect is required */
+   return NULL;
+
 }
-#endif /* def FEATURE_FAST_REDIRECTS */
 
 
 #ifdef FEATURE_IMAGE_BLOCKING