Add support for external filters
authorFabian Keil <fk@fabiankeil.de>
Mon, 2 Jun 2014 06:19:06 +0000 (06:19 +0000)
committerFabian Keil <fk@fabiankeil.de>
Mon, 2 Jun 2014 06:19:06 +0000 (06:19 +0000)
... which allow to process the response body with a script
or program written in any language the platform supports.

External filters are enabled with +external-filter{} after
they have been defined in one of the filter files with
a header line starting with "EXTERNAL-FILTER:".

For this to work, a temporary directory has to be specified
using the newly-added temporary-directory directive.

External filters are experimental and not expected to
work on all platforms (yet).

14 files changed:
acconfig.h
actionlist.h
cgiedit.c
configure.in
errlog.c
filters.c
jbsockets.c
jbsockets.h
jcc.c
jcc.h
loadcfg.c
loaders.c
project.h
templates/edit-actions-for-url

index d2f50a2..6e4022d 100644 (file)
  */
 #undef FEATURE_EXTENDED_HOST_PATTERNS
 
+/*
+ * Allow filtering with scripts and programs.
+ */
+#undef FEATURE_EXTERNAL_FILTERS
+
 /*
  * Keep connections alive if possible.
  */
index b8aac4a..9a8f28b 100644 (file)
@@ -71,6 +71,9 @@ DEFINE_ACTION_STRING     ("deanimate-gifs",             ACTION_DEANIMATE,
 DEFINE_CGI_PARAM_RADIO   ("deanimate-gifs",             ACTION_DEANIMATE,       ACTION_STRING_DEANIMATE,     "first", 0)
 DEFINE_CGI_PARAM_RADIO   ("deanimate-gifs",             ACTION_DEANIMATE,       ACTION_STRING_DEANIMATE,     "last",  1)
 DEFINE_ACTION_BOOL       ("downgrade-http-version",     ACTION_DOWNGRADE)
+#ifdef FEATURE_EXTERNAL_FILTERS
+DEFINE_ACTION_MULTI      ("external-filter",            ACTION_MULTI_EXTERNAL_FILTER)
+#endif
 #ifdef FEATURE_FAST_REDIRECTS
 DEFINE_ACTION_STRING     ("fast-redirects",             ACTION_FAST_REDIRECTS,  ACTION_STRING_FAST_REDIRECTS)
 DEFINE_CGI_PARAM_RADIO   ("fast-redirects",             ACTION_FAST_REDIRECTS,  ACTION_STRING_FAST_REDIRECTS, "simple-check",  0)
index cb824f9..70f6b45 100644 (file)
--- a/cgiedit.c
+++ b/cgiedit.c
@@ -1,4 +1,4 @@
-const char cgiedit_rcs[] = "$Id: cgiedit.c,v 1.78 2013/11/24 14:22:51 fabiankeil Exp $";
+const char cgiedit_rcs[] = "$Id: cgiedit.c,v 1.79 2013/11/24 14:25:19 fabiankeil Exp $";
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/cgiedit.c,v $
@@ -243,6 +243,14 @@ static const struct filter_type_info filter_type_info[] =
       "server-header-tagger-all", "server_header_tagger_all",
       "E", "SERVER-HEADER-TAGGER"
    },
+#ifdef FEATURE_EXTERNAL_FILTERS
+   {
+      ACTION_MULTI_EXTERNAL_FILTER,
+      "external-content-filter-params", "external-filter",
+      "external-content-filter-all", "external_content_filter_all",
+      "E", "EXTERNAL-CONTENT-FILTER"
+   },
+#endif
 };
 
 /* FIXME: Following non-static functions should be prototyped in .h or made static */
@@ -2820,6 +2828,10 @@ jb_err cgi_edit_actions_for_url(struct client_state *csp,
       }
    }
 
+#ifndef FEATURE_EXTERNAL_FILTERS
+   if (!err) err = map_block_killer(exports, "external-content-filters");
+#endif
+
    if (err)
    {
       edit_free_file(file);
index 89ebb47..309cbb9 100644 (file)
@@ -1,6 +1,6 @@
 dnl Process this file with autoconf to produce a configure script.
 dnl
-dnl $Id: configure.in,v 1.182 2014/05/20 11:55:09 fabiankeil Exp $
+dnl $Id: configure.in,v 1.183 2014/06/02 05:46:53 fabiankeil Exp $
 dnl
 dnl Written by and Copyright (C) 2001-2010 the
 dnl Privoxy team. http://www.privoxy.org/
@@ -32,7 +32,7 @@ dnl =================================================================
 dnl AutoConf Initialization
 dnl =================================================================
 
-AC_REVISION($Revision: 1.182 $)
+AC_REVISION($Revision: 1.183 $)
 AC_INIT(jcc.c)
 
 if test ! -f config.h.in; then
@@ -962,6 +962,12 @@ AC_ARG_ENABLE(extended-host-patterns,
   AC_DEFINE(FEATURE_EXTENDED_HOST_PATTERNS)
 fi])
 
+AC_ARG_ENABLE(external-filters,
+[  --enable-external-filters      Allow to filter content with scripts and programs. Experimental.],
+[if test $enableval = yes; then
+  AC_DEFINE(FEATURE_EXTERNAL_FILTERS,1,[Define to 1 to allow to filter content with scripts and programs.])
+fi])
+
 AC_ARG_ENABLE(accept-filter,
 [  --enable-accept-filter          Try to use accf_http(9) if supported.],
 [if test $enableval = yes; then
index fe2ee38..d679f61 100644 (file)
--- a/errlog.c
+++ b/errlog.c
@@ -1,4 +1,4 @@
-const char errlog_rcs[] = "$Id: errlog.c,v 1.117 2012/12/09 12:28:14 fabiankeil Exp $";
+const char errlog_rcs[] = "$Id: errlog.c,v 1.118 2014/05/05 09:51:19 fabiankeil Exp $";
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/errlog.c,v $
@@ -75,6 +75,9 @@ const char errlog_rcs[] = "$Id: errlog.c,v 1.117 2012/12/09 12:28:14 fabiankeil
 #include "errlog.h"
 #include "project.h"
 #include "jcc.h"
+#ifdef FEATURE_EXTERNAL_FILTERS
+#include "jbsockets.h"
+#endif
 
 const char errlog_h_rcs[] = ERRLOG_H_VERSION;
 
@@ -354,6 +357,10 @@ void init_error_log(const char *prog_name, const char *logfname)
       log_error(LOG_LEVEL_FATAL, "init_error_log(): can't open logfile: \'%s\'", logfname);
    }
 
+#ifdef FEATURE_EXTERNAL_FILTERS
+   mark_socket_for_close_on_execute(3);
+#endif
+
    /* set logging to be completely unbuffered */
    setbuf(fp, NULL);
 
index fcca266..290940d 100644 (file)
--- a/filters.c
+++ b/filters.c
@@ -1,4 +1,4 @@
-const char filters_rcs[] = "$Id: filters.c,v 1.179 2013/12/24 13:32:51 fabiankeil Exp $";
+const char filters_rcs[] = "$Id: filters.c,v 1.180 2013/12/24 13:33:13 fabiankeil Exp $";
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/filters.c,v $
@@ -65,6 +65,7 @@ const char filters_rcs[] = "$Id: filters.c,v 1.179 2013/12/24 13:32:51 fabiankei
 #include "miscutil.h"
 #include "actions.h"
 #include "cgi.h"
+#include "jcc.h"
 #include "list.h"
 #include "deanimate.h"
 #include "urlmatch.h"
@@ -1732,6 +1733,229 @@ static char *pcrs_filter_response(struct client_state *csp)
 }
 
 
+#ifdef FEATURE_EXTERNAL_FILTERS
+/*********************************************************************
+ *
+ * Function    :  get_external_filter
+ *
+ * Description :  Lookup the code to execute for an external filter.
+ *                Masks the misuse of the re_filterfile_spec.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  name = Name of the content filter to get
+ *
+ * Returns     :  A pointer to the requested code
+ *                or NULL if the filter wasn't found
+ *
+ *********************************************************************/
+static const char *get_external_filter(const struct client_state *csp,
+                                const char *name)
+{
+   struct re_filterfile_spec *external_filter;
+
+   external_filter = get_filter(csp, name, FT_EXTERNAL_CONTENT_FILTER);
+   if (external_filter == NULL)
+   {
+      log_error(LOG_LEVEL_FATAL,
+         "Didn't find stuff to execute for external filter: %s",
+         name);
+   }
+
+   return external_filter->patterns->first->str;
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  set_privoxy_variables
+ *
+ * Description :  Sets a couple of privoxy-specific environment variables
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+static void set_privoxy_variables(const struct client_state *csp)
+{
+   int i;
+   struct {
+      const char *name;
+      const char *value;
+   } env[] = {
+      { "PRIVOXY_URL",    csp->http->url   },
+      { "PRIVOXY_PATH",   csp->http->path  },
+      { "PRIVOXY_HOST",   csp->http->host  },
+      { "PRIVOXY_ORIGIN", csp->ip_addr_str },
+   };
+
+   for (i = 0; i < SZ(env); i++)
+   {
+      if (setenv(env[i].name, env[i].value, 1))
+      {
+         log_error(LOG_LEVEL_ERROR, "Failed to set %s=%s: %E",
+            env[i].name, env[i].value);
+      }
+   }
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  execute_external_filter
+ *
+ * Description :  Pipe content into external filter and return the output
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  name = Name of the external filter to execute
+ *          3  :  content = The original content to filter
+ *          4  :  size = The size of the content buffer
+ *
+ * Returns     :  a pointer to the (newly allocated) modified buffer.
+ *                or NULL if there were no hits or something went wrong
+ *
+ *********************************************************************/
+static char *execute_external_filter(const struct client_state *csp,
+   const char *name, char *content, size_t *size)
+{
+   char cmd[200];
+   char file_name[FILENAME_MAX];
+   FILE *fp;
+   char *filter_output;
+   int fd;
+   int ret;
+   size_t new_size;
+   const char *external_filter;
+
+   if (csp->config->temporary_directory == NULL)
+   {
+      log_error(LOG_LEVEL_ERROR,
+         "No temporary-directory configured. Can't execute filter: %s",
+         name);
+      return NULL;
+   }
+
+   external_filter = get_external_filter(csp, name);
+
+   if (sizeof(file_name) < snprintf(file_name, sizeof(file_name),
+         "%s/privoxy-XXXXXXXX", csp->config->temporary_directory))
+   {
+      log_error(LOG_LEVEL_ERROR, "temporary-directory path too long");
+      return NULL;
+   }
+
+   fd = mkstemp(file_name);
+   if (fd == -1)
+   {
+      log_error(LOG_LEVEL_ERROR, "mkstemp() failed to create %s: %E", file_name);
+      return NULL;
+   }
+
+   fp = fdopen(fd, "w");
+   if (fp == NULL)
+   {
+      log_error(LOG_LEVEL_ERROR, "fdopen() failed: %E");
+      unlink(file_name);
+      return NULL;
+   }
+
+   /*
+    * The size may be zero if a previous filter discarded everything.
+    *
+    * This isn't necessary unintentional, so we just don't try
+    * to fwrite() nothing and let the user deal with the rest.
+    */
+   if ((*size != 0) && fwrite(content, *size, 1, fp) != 1)
+   {
+      log_error(LOG_LEVEL_ERROR, "fwrite(..., %d, 1, ..) failed: %E", *size);
+      unlink(file_name);
+      return NULL;
+   }
+   fclose(fp);
+
+   if (sizeof(cmd) < snprintf(cmd, sizeof(cmd), "%s < %s", external_filter, file_name))
+   {
+      log_error(LOG_LEVEL_ERROR,
+         "temporary-directory or external filter path too long");
+      unlink(file_name);
+      return NULL;
+   }
+
+   log_error(LOG_LEVEL_RE_FILTER, "Executing '%s': %s", name, cmd);
+
+   /*
+    * The locking is necessary to prevent other threads
+    * from overwriting the environment variables before
+    * the popen fork. Afterwards this no longer matters.
+    */
+   privoxy_mutex_lock(&external_filter_mutex);
+   set_privoxy_variables(csp);
+   fp = popen(cmd, "r");
+   privoxy_mutex_unlock(&external_filter_mutex);
+   if (fp == NULL)
+   {
+      log_error(LOG_LEVEL_ERROR, "popen(\"%s\", \"r\") failed: %E", cmd);
+      unlink(file_name);
+      return NULL;
+   }
+
+   filter_output = malloc_or_die(*size);
+
+   new_size = 0;
+   while (!feof(fp) && !ferror(fp))
+   {
+      size_t len;
+      /* Could be bigger ... */
+      enum { READ_LENGTH = 2048 };
+
+      if (new_size + READ_LENGTH >= *size)
+      {
+         char *p;
+
+         /* Could be considered wasteful if the content is 'large'. */
+         *size = (*size != 0) ? *size * 2 : READ_LENGTH;
+
+         p = realloc(filter_output, *size);
+         if (p == NULL)
+         {
+            log_error(LOG_LEVEL_ERROR, "Out of memory while reading "
+               "external filter output. Using what we got so far.");
+            break;
+         }
+         filter_output = p;
+      }
+      len = fread(&filter_output[new_size], 1, READ_LENGTH, fp);
+      if (len > 0)
+      {
+         new_size += len;
+      }
+   }
+
+   ret = pclose(fp);
+   if (ret == -1)
+   {
+      log_error(LOG_LEVEL_ERROR, "Executing %s failed: %E", cmd);
+   }
+   else
+   {
+      log_error(LOG_LEVEL_RE_FILTER,
+         "Executing '%s' resulted in return value %d. "
+         "Read %d of up to %d bytes.", name, (ret >> 8), new_size, *size);
+   }
+
+   unlink(file_name);
+   *size = new_size;
+
+   return filter_output;
+
+}
+#endif /* def FEATURE_EXTERNAL_FILTERS */
+
+
 /*********************************************************************
  *
  * Function    :  gif_deanimate_response
@@ -1798,7 +2022,8 @@ static char *gif_deanimate_response(struct client_state *csp)
  * Function    :  get_filter_function
  *
  * Description :  Decides which content filter function has
- *                to be applied (if any).
+ *                to be applied (if any). Only considers functions
+ *                for internal filters which are mutually-exclusive.
  *
  * Parameters  :
  *          1  :  csp = Current client state (buffers, headers, etc...)
@@ -1993,6 +2218,7 @@ static jb_err prepare_for_filtering(struct client_state *csp)
  *********************************************************************/
 char *execute_content_filters(struct client_state *csp)
 {
+   char *content;
    filter_function_ptr content_filter;
 
    assert(content_filters_enabled(csp->action));
@@ -2023,8 +2249,32 @@ char *execute_content_filters(struct client_state *csp)
    }
 
    content_filter = get_filter_function(csp);
+   content = (content_filter != NULL) ? (*content_filter)(csp) : NULL;
+
+#ifdef FEATURE_EXTERNAL_FILTERS
+   if (!list_is_empty(csp->action->multi[ACTION_MULTI_EXTERNAL_FILTER]))
+   {
+      struct list_entry *filtername;
+      size_t size = (size_t)csp->content_length;
+
+      if (content == NULL)
+      {
+         content = csp->iob->cur;
+         size = (size_t)(csp->iob->eod - csp->iob->cur);
+      }
+
+      for (filtername = csp->action->multi[ACTION_MULTI_EXTERNAL_FILTER]->first;
+           filtername ; filtername = filtername->next)
+      {
+         content = execute_external_filter(csp, filtername->str, content, &size);
+      }
+      csp->flags |= CSP_FLAG_MODIFIED;
+      csp->content_length = size;
+   }
+#endif /* def FEATURE_EXTERNAL_FILTERS */
+
+   return content;
 
-   return ((*content_filter)(csp));
 }
 
 
@@ -2414,7 +2664,7 @@ int content_requires_filtering(struct client_state *csp)
       return TRUE;
    }
 
-   return FALSE;
+   return (!list_is_empty(csp->action->multi[ACTION_MULTI_EXTERNAL_FILTER]));
 
 }
 
@@ -2435,7 +2685,8 @@ int content_requires_filtering(struct client_state *csp)
 int content_filters_enabled(const struct current_action_spec *action)
 {
    return ((action->flags & ACTION_DEANIMATE) ||
-      !list_is_empty(action->multi[ACTION_MULTI_FILTER]));
+      !list_is_empty(action->multi[ACTION_MULTI_FILTER]) ||
+      !list_is_empty(action->multi[ACTION_MULTI_EXTERNAL_FILTER]));
 }
 
 
index 0517ee0..fc79483 100644 (file)
@@ -1,4 +1,4 @@
-const char jbsockets_rcs[] = "$Id: jbsockets.c,v 1.123 2013/03/06 21:06:18 diem Exp $";
+const char jbsockets_rcs[] = "$Id: jbsockets.c,v 1.124 2013/03/20 11:30:05 fabiankeil Exp $";
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/jbsockets.c,v $
@@ -289,6 +289,10 @@ static jb_socket rfc2553_connect_to(const char *host, int portnum, struct client
       }
 #endif
 
+#ifdef FEATURE_EXTERNAL_FILTERS
+      mark_socket_for_close_on_execute(fd);
+#endif
+
 #ifdef TCP_NODELAY
       {  /* turn off TCP coalescence */
          int mi = 1;
@@ -494,6 +498,9 @@ static jb_socket no_rfc2553_connect_to(const char *host, int portnum, struct cli
    {
       flags |= O_NDELAY;
       fcntl(fd, F_SETFL, flags);
+#ifdef FEATURE_EXTERNAL_FILTERS
+      mark_socket_for_close_on_execute(fd);
+#endif
    }
 #endif /* !defined(_WIN32) && !defined(__BEOS__) && !defined(AMIGA) && !defined(__OS2__) */
 
@@ -908,6 +915,10 @@ int bind_port(const char *hostnam, int portnum, jb_socket *pfd)
    fd = socket(AF_INET, SOCK_STREAM, 0);
 #endif /* def HAVE_RFC2553 */
 
+#ifdef FEATURE_EXTERNAL_FILTERS
+   mark_socket_for_close_on_execute(fd);
+#endif
+
 #ifdef _WIN32
    if (fd == JB_INVALID_SOCKET)
 #else
@@ -1319,6 +1330,10 @@ int accept_connection(struct client_state * csp, jb_socket fds[])
    }
 #endif
 
+#ifdef FEATURE_EXTERNAL_FILTERS
+   mark_socket_for_close_on_execute(afd);
+#endif
+
    csp->cfd = afd;
 #ifdef HAVE_RFC2553
    csp->ip_addr_str = malloc(NI_MAXHOST);
@@ -1518,6 +1533,42 @@ int socket_is_still_alive(jb_socket sfd)
 }
 
 
+#ifdef FEATURE_EXTERNAL_FILTERS
+/*********************************************************************
+ *
+ * Function    :  mark_socket_for_close_on_execute
+ *
+ * Description :  Marks a socket for close on execute.
+ *
+ *                Used so that external filters have no direct
+ *                access to sockets they shouldn't care about.
+ *
+ *                Not implemented for all platforms.
+ *
+ * Parameters  :
+ *          1  :  fd = The socket to mark
+ *
+ * Returns     :  void.
+ *
+ *********************************************************************/
+void mark_socket_for_close_on_execute(jb_socket fd)
+{
+#ifdef FEATURE_PTHREAD
+   int ret;
+
+   ret = fcntl(fd, F_SETFD, FD_CLOEXEC);
+
+   if (ret == -1)
+   {
+      log_error(LOG_LEVEL_ERROR,
+         "fcntl(%d, F_SETFD, FD_CLOEXEC) failed", fd);
+   }
+#else
+#warning "Sockets will be visible to external filters"
+#endif
+}
+#endif /* def FEATURE_EXTERNAL_FILTERS */
+
 /*
   Local Variables:
   tab-width: 3
index 0064bb8..765c43f 100644 (file)
@@ -1,6 +1,6 @@
 #ifndef JBSOCKETS_H_INCLUDED
 #define JBSOCKETS_H_INCLUDED
-#define JBSOCKETS_H_VERSION "$Id: jbsockets.h,v 1.21 2012/10/12 11:17:48 fabiankeil Exp $"
+#define JBSOCKETS_H_VERSION "$Id: jbsockets.h,v 1.22 2013/11/24 14:23:28 fabiankeil Exp $"
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/jbsockets.h,v $
@@ -61,6 +61,10 @@ extern unsigned long resolve_hostname_to_ip(const char *host);
 
 extern int socket_is_still_alive(jb_socket sfd);
 
+#ifdef FEATURE_EXTERNAL_FILTERS
+extern void mark_socket_for_close_on_execute(jb_socket fd);
+#endif
+
 /* Revision control strings from this header and associated .c file */
 extern const char jbsockets_rcs[];
 extern const char jbsockets_h_rcs[];
diff --git a/jcc.c b/jcc.c
index b1d275c..9be9b12 100644 (file)
--- a/jcc.c
+++ b/jcc.c
@@ -1,4 +1,4 @@
-const char jcc_rcs[] = "$Id: jcc.c,v 1.424 2013/03/01 17:38:34 fabiankeil Exp $";
+const char jcc_rcs[] = "$Id: jcc.c,v 1.425 2014/02/10 14:39:43 fabiankeil Exp $";
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/jcc.c,v $
@@ -182,6 +182,10 @@ privoxy_mutex_t log_mutex;
 privoxy_mutex_t log_init_mutex;
 privoxy_mutex_t connection_reuse_mutex;
 
+#ifdef FEATURE_EXTERNAL_FILTERS
+privoxy_mutex_t external_filter_mutex;
+#endif
+
 #if !defined(HAVE_GETHOSTBYADDR_R) || !defined(HAVE_GETHOSTBYNAME_R)
 privoxy_mutex_t resolver_mutex;
 #endif /* !defined(HAVE_GETHOSTBYADDR_R) || !defined(HAVE_GETHOSTBYNAME_R) */
@@ -3134,6 +3138,9 @@ static void initialize_mutexes(void)
    privoxy_mutex_init(&log_mutex);
    privoxy_mutex_init(&log_init_mutex);
    privoxy_mutex_init(&connection_reuse_mutex);
+#ifdef FEATURE_EXTERNAL_FILTERS
+   privoxy_mutex_init(&external_filter_mutex);
+#endif
 
    /*
     * XXX: The assumptions below are a bit naive
@@ -3518,6 +3525,13 @@ int main(int argc, char **argv)
          close(fd);
       }
 
+#ifdef FEATURE_EXTERNAL_FILTERS
+      for (fd = 0; fd < 3; fd++)
+      {
+         mark_socket_for_close_on_execute(fd);
+      }
+#endif
+
       chdir("/");
 
    } /* -END- if (daemon_mode) */
diff --git a/jcc.h b/jcc.h
index aa8d829..9b07df9 100644 (file)
--- a/jcc.h
+++ b/jcc.h
@@ -1,6 +1,6 @@
 #ifndef JCC_H_INCLUDED
 #define JCC_H_INCLUDED
-#define JCC_H_VERSION "$Id: jcc.h,v 1.32 2011/11/06 11:48:23 fabiankeil Exp $"
+#define JCC_H_VERSION "$Id: jcc.h,v 1.33 2013/11/24 14:23:28 fabiankeil Exp $"
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/jcc.h,v $
@@ -79,6 +79,10 @@ extern privoxy_mutex_t log_mutex;
 extern privoxy_mutex_t log_init_mutex;
 extern privoxy_mutex_t connection_reuse_mutex;
 
+#ifdef FEATURE_EXTERNAL_FILTERS
+extern privoxy_mutex_t external_filter_mutex;
+#endif
+
 #ifndef HAVE_GMTIME_R
 extern privoxy_mutex_t gmtime_mutex;
 #endif /* ndef HAVE_GMTIME_R */
index 5b175a6..b9a15c9 100644 (file)
--- a/loadcfg.c
+++ b/loadcfg.c
@@ -1,4 +1,4 @@
-const char loadcfg_rcs[] = "$Id: loadcfg.c,v 1.138 2013/04/23 09:42:53 fabiankeil Exp $";
+const char loadcfg_rcs[] = "$Id: loadcfg.c,v 1.139 2013/11/24 14:25:19 fabiankeil Exp $";
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/loadcfg.c,v $
@@ -157,6 +157,7 @@ static struct file_list *current_configfile = NULL;
 #define hash_split_large_cgi_forms        671658948U /* "split-large-cgi-forms" */
 #define hash_suppress_blocklists         1948693308U /* "suppress-blocklists" */
 #define hash_templdir                      11067889U /* "templdir" */
+#define hash_temporary_directory         1824125181U /* "temporary-directory" */
 #define hash_tolerate_pipelining         1360286620U /* "tolerate-pipelining" */
 #define hash_toggle                          447966U /* "toggle" */
 #define hash_trust_info_url               430331967U /* "trust-info-url" */
@@ -222,6 +223,9 @@ static void unload_configfile (void * data)
    freez(config->logdir);
    freez(config->templdir);
    freez(config->hostname);
+#ifdef FEATURE_EXTERNAL_FILTERS
+   freez(config->temporary_directory);
+#endif
 
    for (i = 0; i < MAX_LISTENING_SOCKETS; i++)
    {
@@ -1364,6 +1368,16 @@ struct configuration_spec * load_config(void)
             config->templdir = make_path(NULL, arg);
             break;
 
+#ifdef FEATURE_EXTERNAL_FILTERS
+/* *************************************************************************
+ * temporary-directory directory-name
+ * *************************************************************************/
+         case hash_temporary_directory :
+            freez(config->temporary_directory);
+            config->temporary_directory = make_path(NULL, arg);
+            break;
+#endif
+
 /* *************************************************************************
  * tolerate-pipelining (0|1)
  * *************************************************************************/
index 37b7d65..a23ae78 100644 (file)
--- a/loaders.c
+++ b/loaders.c
@@ -1,4 +1,4 @@
-const char loaders_rcs[] = "$Id: loaders.c,v 1.96 2013/11/24 14:22:51 fabiankeil Exp $";
+const char loaders_rcs[] = "$Id: loaders.c,v 1.97 2013/11/24 14:25:19 fabiankeil Exp $";
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/loaders.c,v $
@@ -1170,6 +1170,12 @@ int load_one_re_filterfile(struct client_state *csp, int fileid)
       {
          new_filter = FT_SERVER_HEADER_TAGGER;
       }
+#ifdef FEATURE_EXTERNAL_FILTERS
+      else if (strncmp(buf, "EXTERNAL-FILTER:", 16) == 0)
+      {
+         new_filter = FT_EXTERNAL_CONTENT_FILTER;
+      }
+#endif
 
       /*
        * If this is the head of a new filter block, make it a
@@ -1186,6 +1192,12 @@ int load_one_re_filterfile(struct client_state *csp, int fileid)
          {
             new_bl->name = chomp(buf + 7);
          }
+#ifdef FEATURE_EXTERNAL_FILTERS
+         else if (new_filter == FT_EXTERNAL_CONTENT_FILTER)
+         {
+            new_bl->name = chomp(buf + 16);
+         }
+#endif
          else
          {
             new_bl->name = chomp(buf + 21);
@@ -1234,12 +1246,33 @@ int load_one_re_filterfile(struct client_state *csp, int fileid)
          continue;
       }
 
-      /*
-       * Else, save the expression, make it a pcrs_job
-       * and chain it into the current filter's joblist
-       */
+#ifdef FEATURE_EXTERNAL_FILTERS
+      if ((bl != NULL) && (bl->type == FT_EXTERNAL_CONTENT_FILTER))
+      {
+         /* Save the code as "pattern", but do not compile anything. */
+         if (bl->patterns->first != NULL)
+         {
+            log_error(LOG_LEVEL_FATAL, "External filter '%s' contains several jobss. "
+               "Did you forget to escape a line break?",
+               bl->name);
+         }
+         error = enlist(bl->patterns, buf);
+         if (JB_ERR_MEMORY == error)
+         {
+            log_error(LOG_LEVEL_FATAL,
+               "Out of memory while enlisting external filter code \'%s\' for filter %s.",
+               buf, bl->name);
+         }
+         freez(buf);
+         continue;
+      }
+#endif
       if (bl != NULL)
       {
+         /*
+          * Save the expression, make it a pcrs_job
+          * and chain it into the current filter's joblist
+          */
          error = enlist(bl->patterns, buf);
          if (JB_ERR_MEMORY == error)
          {
index a129509..f57381a 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.203 2013/11/24 14:26:39 fabiankeil Exp $"
+#define PROJECT_H_VERSION "$Id: project.h,v 1.204 2014/05/26 10:46:45 fabiankeil Exp $"
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/project.h,v $
@@ -566,7 +566,9 @@ struct iob
 /** Index into current_action_spec::multi[] for server-header tags to apply. */
 #define ACTION_MULTI_SERVER_HEADER_TAGGER    5
 /** Number of multi-string actions. */
-#define ACTION_MULTI_COUNT                   6
+#define ACTION_MULTI_EXTERNAL_FILTER         6
+/** Number of multi-string actions. */
+#define ACTION_MULTI_COUNT                   7
 
 
 /**
@@ -1115,9 +1117,17 @@ enum filter_type
    FT_SERVER_HEADER_FILTER = 2,
    FT_CLIENT_HEADER_TAGGER = 3,
    FT_SERVER_HEADER_TAGGER = 4,
+#ifdef FEATURE_EXTERNAL_FILTERS
+   FT_EXTERNAL_CONTENT_FILTER = 5,
+#endif
    FT_INVALID_FILTER       = 42,
 };
+
+#ifdef FEATURE_EXTERNAL_FILTERS
+#define MAX_FILTER_TYPES        6
+#else
 #define MAX_FILTER_TYPES        5
+#endif
 
 /**
  * This struct represents one filter (one block) from
@@ -1246,6 +1256,11 @@ struct configuration_spec
    /** The directory for customized CGI templates. */
    const char *templdir;
 
+#ifdef FEATURE_EXTERNAL_FILTERS
+   /** The template used to create temporary files. */
+   const char *temporary_directory;
+#endif
+
    /** The log file directory. */
    const char *logdir;
 
index 0c62e7e..5382518 100644 (file)
@@ -527,6 +527,24 @@ function show_limit_connect_opts(tf)
       <td>Change HTTP/1.1 requests to HTTP/1.0.  Only change if you know
         what you're doing!</td>
     </tr>
+
+<!-- @if-external-content-filters-start -->
+    <tr class="bg1" align="left" valign="top">
+      <td class="en1">&nbsp;</td>
+      <td class="dis1" align="center" valign="middle"><input type="radio"
+        name="external_content_filter_all" id="external_content_filter_all_n" value="N" @external-content-filter-all-n@ ></td>
+      <td class="noc1" align="center" valign="middle"><input type="radio"
+        name="external_content_filter_all" id="external_content_filter_all_x" value="X" @external-content-filter-all-x@ ></td>
+      <td class="action"><a href="@user-manual@@actions-help-prefix@EXTERNAL_FILTER">external-filter</a> *</td>
+      <td>Filter the website through external scripts or programs.
+        You can use the radio buttons on this line to disable
+        all filters applied by previous rules, and/or you can enable or
+        disable the filters individually below.</td>
+    </tr>
+
+@external-content-filter-params@
+<!-- if-external-content-filters-end@ -->
+
     <tr class="bg1" align="left" valign="top">
       <td class="en1" align="center" valign="middle"><input type="radio"
         name="fast_redirects" value="Y" @fast-redirects-y@