Introduce dynamic pcrs jobs that can resolve variables.
authorFabian Keil <fk@fabiankeil.de>
Mon, 30 Apr 2007 15:02:19 +0000 (15:02 +0000)
committerFabian Keil <fk@fabiankeil.de>
Mon, 30 Apr 2007 15:02:19 +0000 (15:02 +0000)
filters.h
loaders.c
pcrs.c
pcrs.h
project.h

index f11f0a3..dd729cf 100644 (file)
--- a/filters.h
+++ b/filters.h
@@ -1,6 +1,6 @@
 #ifndef FILTERS_H_INCLUDED
 #define FILTERS_H_INCLUDED
-#define FILTERS_H_VERSION "$Id: filters.h,v 1.25 2007/01/12 15:36:44 fabiankeil Exp $"
+#define FILTERS_H_VERSION "$Id: filters.h,v 1.26 2007/03/13 11:28:43 fabiankeil Exp $"
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/filters.h,v $
  *
  * Revisions   :
  *    $Log: filters.h,v $
+ *    Revision 1.26  2007/03/13 11:28:43  fabiankeil
+ *    - Fix port handling in acl_addr() and use a temporary acl spec
+ *      copy so error messages don't contain a truncated version.
+ *    - Log size of iob before and after decompression.
+ *
  *    Revision 1.25  2007/01/12 15:36:44  fabiankeil
  *    Mark *csp as immutable for is_untrusted_url()
  *    and is_imageurl(). Closes FR 1237736.
@@ -294,6 +299,8 @@ extern char *execute_single_pcrs_command(char *subject, const char *pcrs_command
 extern char *rewrite_url(char *old_url, const char *pcrs_command);
 extern char *get_last_url(char *subject, const char *redirect_mode);
 
+extern pcrs_job *compile_dynamic_pcrs_job_list(const struct client_state *csp, const struct re_filterfile_spec *b);
+
 /*
  * Handling Max-Forwards:
  */
index 422b2ae..2ea9c5f 100644 (file)
--- a/loaders.c
+++ b/loaders.c
@@ -1,4 +1,4 @@
-const char loaders_rcs[] = "$Id: loaders.c,v 1.60 2007/03/20 15:16:34 fabiankeil Exp $";
+const char loaders_rcs[] = "$Id: loaders.c,v 1.61 2007/04/15 16:39:21 fabiankeil Exp $";
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/loaders.c,v $
@@ -35,6 +35,11 @@ const char loaders_rcs[] = "$Id: loaders.c,v 1.60 2007/03/20 15:16:34 fabiankeil
  *
  * Revisions   :
  *    $Log: loaders.c,v $
+ *    Revision 1.61  2007/04/15 16:39:21  fabiankeil
+ *    Introduce tags as alternative way to specify which
+ *    actions apply to a request. At the moment tags can be
+ *    created based on client and server headers.
+ *
  *    Revision 1.60  2007/03/20 15:16:34  fabiankeil
  *    Use dedicated header filter actions instead of abusing "filter".
  *    Replace "filter-client-headers" and "filter-client-headers"
@@ -1377,6 +1382,7 @@ int load_re_filterfile(struct client_state *csp)
    return 0;
 }
 
+
 /*********************************************************************
  *
  * Function    :  load_one_re_filterfile
@@ -1523,12 +1529,46 @@ int load_one_re_filterfile(struct client_state *csp, int fileid)
        */
       if (bl != NULL)
       {
-         enlist(bl->patterns, buf);
+         error = enlist(bl->patterns, buf);
+         if (JB_ERR_MEMORY == error)
+         {
+            log_error(LOG_LEVEL_FATAL,
+               "Out of memory while enlisting re_filter job \'%s\' for filter %s.", buf, bl->name);
+         }
+         assert(JB_ERR_OK == error);
+
+         if (pcrs_job_is_dynamic(buf))
+         {
+            /*
+             * Dynamic pattern that might contain variables
+             * and has to be recompiled for every request
+             */
+            if (bl->joblist != NULL)
+            {
+                pcrs_free_joblist(bl->joblist);
+                bl->joblist = NULL;
+            }
+            bl->dynamic = 1;
+            log_error(LOG_LEVEL_RE_FILTER,
+               "Adding dynamic re_filter job \'%s\' to filter %s succeeded.", buf, bl->name);
+            continue;             
+         }
+         else if (bl->dynamic)
+         {
+            /*
+             * A previous job was dynamic and as we
+             * recompile the whole filter anyway, it
+             * makes no sense to compile this job now.
+             */
+            log_error(LOG_LEVEL_RE_FILTER,
+               "Adding static re_filter job \'%s\' to dynamic filter %s succeeded.", buf, bl->name);
+            continue;
+         }
 
          if ((dummy = pcrs_compile_command(buf, &error)) == NULL)
          {
             log_error(LOG_LEVEL_ERROR,
-                      "Adding re_filter job %s to filter %s failed with error %d.", buf, bl->name, error);
+               "Adding re_filter job \'%s\' to filter %s failed with error %d.", buf, bl->name, error);
             continue;
          }
          else
@@ -1542,7 +1582,7 @@ int load_one_re_filterfile(struct client_state *csp, int fileid)
                lastjob->next = dummy;
             }
             lastjob = dummy;
-            log_error(LOG_LEVEL_RE_FILTER, "Adding re_filter job %s to filter %s succeeded.", buf, bl->name);
+            log_error(LOG_LEVEL_RE_FILTER, "Adding re_filter job \'%s\' to filter %s succeeded.", buf, bl->name);
          }
       }
       else
diff --git a/pcrs.c b/pcrs.c
index ee51d0a..657a286 100644 (file)
--- a/pcrs.c
+++ b/pcrs.c
@@ -1,4 +1,4 @@
-const char pcrs_rcs[] = "$Id: pcrs.c,v 1.23 2006/12/29 17:53:05 fabiankeil Exp $";
+const char pcrs_rcs[] = "$Id: pcrs.c,v 1.24 2007/01/05 15:46:12 fabiankeil Exp $";
 
 /*********************************************************************
  *
@@ -8,10 +8,15 @@ const char pcrs_rcs[] = "$Id: pcrs.c,v 1.23 2006/12/29 17:53:05 fabiankeil Exp $
  *                <ph10@cam.ac.uk> and adds Perl-style substitution. That
  *                is, it mimics Perl's 's' operator. See pcrs(3) for details.
  *
+ *                WARNING: This file contains additional functions and bug
+ *                fixes that aren't part of the latest official pcrs package
+ *                (which apparently is no longer maintained).
  *
  * Copyright   :  Written and Copyright (C) 2000, 2001 by Andreas S. Oesterhelt
  *                <andreas@oesterhelt.org>
  *
+ *                Copyright (C) 2006, 2007 Fabian Keil <fk@fabiankeil.de>
+ *
  *                This program is free software; you can redistribute it 
  *                and/or modify it under the terms of the GNU Lesser
  *                General Public License (LGPL), version 2.1, which  should
@@ -33,6 +38,15 @@ const char pcrs_rcs[] = "$Id: pcrs.c,v 1.23 2006/12/29 17:53:05 fabiankeil Exp $
  *
  * Revisions   :
  *    $Log: pcrs.c,v $
+ *    Revision 1.24  2007/01/05 15:46:12  fabiankeil
+ *    Don't use strlen() to calculate the length of
+ *    the pcrs substitutes. They don't have to be valid C
+ *    strings and getting their length wrong can result in
+ *    user-controlled memory corruption.
+ *
+ *    Thanks to Felix Gröbert for reporting the problem
+ *    and providing the fix [#1627140].
+ *
  *    Revision 1.23  2006/12/29 17:53:05  fabiankeil
  *    Fixed gcc43 conversion warnings.
  *
@@ -163,8 +177,12 @@ const char pcrs_rcs[] = "$Id: pcrs.c,v 1.23 2006/12/29 17:53:05 fabiankeil Exp $
  */
 #include "project.h"
 
+/* For snprintf only */
+#include "miscutil.h"
+
 #include <string.h>
 #include <ctype.h>
+#include <assert.h>
 
 #include "pcrs.h"
 
@@ -222,6 +240,8 @@ const char *pcrs_strerror(const int error)
          case PCRS_ERR_STUDY:          return "(pcrs:) PCRE error while studying the pattern";
          case PCRS_ERR_BADJOB:         return "(pcrs:) Bad job - NULL job, pattern or substitute";
          case PCRS_WARN_BADREF:        return "(pcrs:) Backreference out of range";
+         case PCRS_WARN_TRUNCATION:
+            return "(pcrs:) At least one variable was too big and has been truncated before compilation";
 
          /* 
           * XXX: With the exception of PCRE_ERROR_MATCHLIMIT we
@@ -830,7 +850,7 @@ int pcrs_execute_list(pcrs_job *joblist, char *subject, size_t subject_length, c
  *                 failure, which may be translated to text using pcrs_strerror().
  *
  *********************************************************************/
-int pcrs_execute(pcrs_job *job, char *subject, size_t subject_length, char **result, size_t *result_length)
+int pcrs_execute(pcrs_job *job, const char *subject, size_t subject_length, char **result, size_t *result_length)
 {
    int offsets[3 * PCRS_MAX_SUBMATCHES],
        offset,
@@ -994,6 +1014,243 @@ int pcrs_execute(pcrs_job *job, char *subject, size_t subject_length, char **res
 
 }
 
+/*
+ * Functions below this line are only part of the pcrs version
+ * included in Privoxy. If you use any of them you should not
+ * try to dynamically link against external pcrs versions.
+ */
+
+/*********************************************************************
+ *
+ * Function    :  pcrs_job_is_dynamic
+ *
+ * Description :  Checks if a job has the "D" (dynamic) option set.
+ *
+ * Parameters  :
+ *          1  :  job = The job to check
+ *
+ * Returns     :  TRUE if the job is indeed dynamic, otherwise
+ *                FALSE
+ *
+ *********************************************************************/
+int pcrs_job_is_dynamic (char *job)
+{
+   const char delimiter = job[1];
+   const size_t length = strlen(job);
+   char *option;
+
+   if (length < 5)
+   {
+      /*
+       * The shortest valid (but useless)
+       * dynamic pattern is "s@@@D"
+       */
+      return FALSE;
+   }
+
+   /*
+    * Everything between the last character
+    * and the last delimiter is an option ...
+    */
+   for (option = job + length; *option != delimiter; option--)
+   {
+      if (*option == 'D')
+      {
+         /*
+          * ... and if said option is 'D' the job is dynamic.
+          */
+         return TRUE;
+      }
+   }
+   return FALSE;
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  pcrs_get_delimiter
+ *
+ * Description :  Tries to find a character that is safe to
+ *                be used as a pcrs delimiter for a certain string.
+ *
+ * Parameters  :
+ *          1  :  string = The string to search in
+ *
+ * Returns     :  A safe delimiter if one was found, otherwise '\0'.  
+ *
+ *********************************************************************/
+char pcrs_get_delimiter(const char *string)
+{
+   /*
+    * Some characters that are unlikely to
+    * be part of pcrs replacement strings.
+    */
+   char delimiters[] = "><§#+*~%^°-:;µ!@";
+   char *d = delimiters;
+
+   /* Take the first delimiter that isn't part of the string */
+   while (*d && NULL != strchr(string, *d))
+   {
+      d++;
+   }
+   return *d;
+
+}
+
+
+
+/*********************************************************************
+ *
+ * Function    :  pcrs_execute_single_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 *pcrs_execute_single_command(const char *subject, const char *pcrs_command, int *hits)
+{
+   size_t size;
+   char *result = NULL;
+   pcrs_job *job;
+
+   assert(subject);
+   assert(pcrs_command);
+
+   *hits = 0;
+   size = strlen(subject);
+
+   job = pcrs_compile_command(pcrs_command, hits);
+   if (NULL != job)
+   {
+      *hits = pcrs_execute(job, subject, size, &result, &size);
+      if (*hits < 0)
+      {
+         freez(result);
+      }
+      pcrs_free_job(job);
+   }
+   return result;
+
+}
+
+
+const static char warning[] = "... [too long, truncated]";
+/*********************************************************************
+ *
+ * Function    :  pcrs_compile_dynamic_command
+ *
+ * Description :  Takes a dynamic pcrs command, fills in the
+ *                values of the variables and compiles it.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  pcrs_command = The dynamic pcrs command to compile
+ *          3  :  v = NULL terminated array of variables and their values.
+ *          4  :  error = pcrs error code
+ *
+ * Returns     :  NULL in case of hard errors, otherwise the
+ *                compiled pcrs job.   
+ *
+ *********************************************************************/
+pcrs_job *pcrs_compile_dynamic_command(char *pcrs_command, const struct pcrs_variable v[], int *error)
+{
+   char buf[PCRS_BUFFER_SIZE];
+   const char *original_pcrs_command = pcrs_command;
+   char *pcrs_command_tmp = NULL;
+   pcrs_job *job = NULL;
+   int truncation = 0;
+   char d;
+   int ret;
+
+   while ((NULL != v->name) && (NULL != pcrs_command))
+   {
+      assert(NULL != v->value);
+
+      if (NULL == strstr(pcrs_command, v->name))
+      {
+         /*
+          * Skip the substitution if the variable
+          * name isn't part of the pattern.
+          */
+         v++;
+         continue;
+      }
+
+      /* Use pcrs to replace the variable with its value. */
+      d = pcrs_get_delimiter(v->value);
+      if ('\0' == d)
+      {
+         /* No proper delimiter found */
+         *error = PCRS_ERR_CMDSYNTAX;
+         return NULL;
+      }
+
+      /*
+       * Variable names are supposed to contain alpha
+       * numerical characters plus '_' only.
+       */
+      assert(NULL == strchr(v->name, d));
+
+      ret = snprintf(buf, sizeof(buf), "s%c\\$%s%c%s%cgT", d, v->name, d, v->value, d);
+      assert(ret >= 0);
+      if (ret >= sizeof(buf))
+      {
+         /*
+          * Value didn't completely fit into buffer,
+          * overwrite the end of the substitution text
+          * with a truncation message and close the pattern
+          * properly.
+          */
+         const size_t trailer_size = sizeof(warning) + 3; /* 3 for d + "gT" */
+         char *trailer_start = buf + sizeof(buf) - trailer_size;
+
+         ret = snprintf(trailer_start, trailer_size, "%s%cgT", warning, d);
+         assert(ret == trailer_size - 1);
+         assert(sizeof(buf) == strlen(buf) + 1);
+         truncation = 1;
+      }
+
+      pcrs_command_tmp = pcrs_execute_single_command(pcrs_command, buf, error);
+      if (NULL == pcrs_command_tmp)
+      {
+         return NULL;
+      }
+
+      if (pcrs_command != original_pcrs_command)
+      {
+         freez(pcrs_command);
+      }
+      pcrs_command = pcrs_command_tmp;
+
+      v++;
+   }
+
+   job = pcrs_compile_command(pcrs_command, error);
+   if (pcrs_command != original_pcrs_command)
+   {
+      freez(pcrs_command);
+   }
+
+   if (truncation)
+   {
+      *error = PCRS_WARN_TRUNCATION;
+   }
+
+   return job;
+
+}
+
 
 /*
   Local Variables:
diff --git a/pcrs.h b/pcrs.h
index a99293f..a3bfede 100644 (file)
--- a/pcrs.h
+++ b/pcrs.h
  *
  * Revisions   :
  *    $Log: pcrs.h,v $
+ *    Revision 1.15  2007/01/05 15:46:12  fabiankeil
+ *    Don't use strlen() to calculate the length of
+ *    the pcrs substitutes. They don't have to be valid C
+ *    strings and getting their length wrong can result in
+ *    user-controlled memory corruption.
+ *
+ *    Thanks to Felix Gröbert for reporting the problem
+ *    and providing the fix [#1627140].
+ *
  *    Revision 1.14  2006/12/24 17:27:37  fabiankeil
  *    Increase pcrs error code offset to prevent overlaps
  *    with pcre versions newer than our own.
@@ -70,7 +79,7 @@
  *
  *********************************************************************/
 
-#define PCRS_H_VERSION "$Id: pcrs.h,v 1.14 2006/12/24 17:27:37 fabiankeil Exp $"
+#define PCRS_H_VERSION "$Id: pcrs.h,v 1.15 2007/01/05 15:46:12 fabiankeil Exp $"
 \f
 
 #ifndef _PCRE_H
@@ -102,11 +111,13 @@ extern "C" {
  * PCRE 6.7 uses error codes from -1 to -21, PCRS error codes
  * below -100 should be safe for a while.
  */
-#define PCRS_ERR_NOMEM     -100      /* Failed to acquire memory. */
-#define PCRS_ERR_CMDSYNTAX -101      /* Syntax of s///-command */
-#define PCRS_ERR_STUDY     -102      /* pcre error while studying the pattern */
-#define PCRS_ERR_BADJOB    -103      /* NULL job pointer, pattern or substitute */
-#define PCRS_WARN_BADREF   -104      /* Backreference out of range */
+#define PCRS_ERR_NOMEM           -100      /* Failed to acquire memory. */
+#define PCRS_ERR_CMDSYNTAX       -101      /* Syntax of s///-command */
+#define PCRS_ERR_STUDY           -102      /* pcre error while studying the pattern */
+#define PCRS_ERR_BADJOB          -103      /* NULL job pointer, pattern or substitute */
+#define PCRS_WARN_BADREF         -104      /* Backreference out of range */
+#define PCRS_WARN_TRUNCATION     -105      /* At least one pcrs variable was too big,
+                                            * only the first part was used. */
 
 /* Flags */
 #define PCRS_GLOBAL          1      /* Job should be applied globally, as with perl's g option */
@@ -164,7 +175,7 @@ typedef struct PCRS_JOB {
 /* Main usage */
 extern pcrs_job        *pcrs_compile_command(const char *command, int *errptr);
 extern pcrs_job        *pcrs_compile(const char *pattern, const char *substitute, const char *options, int *errptr);
-extern int              pcrs_execute(pcrs_job *job, char *subject, size_t subject_length, char **result, size_t *result_length);
+extern int              pcrs_execute(pcrs_job *job, const char *subject, size_t subject_length, char **result, size_t *result_length);
 extern int              pcrs_execute_list(pcrs_job *joblist, char *subject, size_t subject_length, char **result, size_t *result_length);
 
 /* Freeing jobs */
@@ -174,6 +185,25 @@ extern void             pcrs_free_joblist(pcrs_job *joblist);
 /* Info on errors: */
 extern const char *pcrs_strerror(const int error);
 
+extern int pcrs_job_is_dynamic(char *job);
+extern char pcrs_get_delimiter(const char *string);
+extern char *pcrs_execute_single_command(const char *subject, const char *pcrs_command, int *hits);
+/*
+ * Variable/value pair for dynamic pcrs commands.
+ */
+struct pcrs_variable
+{
+   const char *name;
+   char *value;
+   int static_value;
+};
+
+extern pcrs_job *pcrs_compile_dynamic_command(char *pcrs_command, const struct pcrs_variable v[], int *error);
+
+/* Only relevant for maximum pcrs variable size */
+#ifndef PCRS_BUFFER_SIZE
+#define PCRS_BUFFER_SIZE 4000
+#endif /* ndef PCRS_BUFFER_SIZE */
 
 #ifdef __cplusplus
 } /* extern "C" */
index f52b5d6..0b40f2b 100644 (file)
--- a/project.h
+++ b/project.h
@@ -1,7 +1,7 @@
 #ifndef PROJECT_H_INCLUDED
 #define PROJECT_H_INCLUDED
 /** Version string. */
-#define PROJECT_H_VERSION "$Id: project.h,v 1.93 2007/03/20 15:16:34 fabiankeil Exp $"
+#define PROJECT_H_VERSION "$Id: project.h,v 1.94 2007/04/15 16:39:21 fabiankeil Exp $"
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/project.h,v $
  *
  * Revisions   :
  *    $Log: project.h,v $
+ *    Revision 1.94  2007/04/15 16:39:21  fabiankeil
+ *    Introduce tags as alternative way to specify which
+ *    actions apply to a request. At the moment tags can be
+ *    created based on client and server headers.
+ *
  *    Revision 1.93  2007/03/20 15:16:34  fabiankeil
  *    Use dedicated header filter actions instead of abusing "filter".
  *    Replace "filter-client-headers" and "filter-client-headers"
@@ -1486,6 +1491,8 @@ struct re_filterfile_spec
    struct list patterns[1];         /**< The patterns from the re_filterfile. */
    pcrs_job *joblist;               /**< The resulting compiled pcrs_jobs. */
    int type;                        /**< Filter type (content, client-header, server-header). */
+   int dynamic;                     /**< Set to one if the pattern might contain variables
+                                         and has to be recompiled for every request. */
    struct re_filterfile_spec *next; /**< The pointer for chaining. */
 };