Add support for filering client request bodies
authorMaxim Antonov <mantonov@gmail.com>
Thu, 17 Dec 2020 08:05:23 +0000 (15:05 +0700)
committerFabian Keil <fk@fabiankeil.de>
Wed, 30 Dec 2020 11:47:37 +0000 (12:47 +0100)
... by using CLIENT-BODY-FILTER filters which can
be enabled with the client-body-filter action.

13 files changed:
actionlist.h
actions.c
cgiedit.c
default.filter
doc/source/user-manual.sgml
filters.c
filters.h
jcc.c
loaders.c
parsers.c
parsers.h
project.h
templates/edit-actions-for-url

index 6129bb0..fc7f514 100644 (file)
@@ -56,6 +56,7 @@ DEFINE_CGI_PARAM_NO_RADIO("block",                      ACTION_BLOCK, ACTION_STR
 DEFINE_ACTION_STRING     ("change-x-forwarded-for",     ACTION_CHANGE_X_FORWARDED_FOR,  ACTION_STRING_CHANGE_X_FORWARDED_FOR)
 DEFINE_CGI_PARAM_RADIO   ("change-x-forwarded-for",     ACTION_CHANGE_X_FORWARDED_FOR,  ACTION_STRING_CHANGE_X_FORWARDED_FOR, "block", 0)
 DEFINE_CGI_PARAM_RADIO   ("change-x-forwarded-for",     ACTION_CHANGE_X_FORWARDED_FOR,  ACTION_STRING_CHANGE_X_FORWARDED_FOR, "add", 1)
+DEFINE_ACTION_MULTI      ("client-body-filter",         ACTION_MULTI_CLIENT_BODY_FILTER)
 DEFINE_ACTION_MULTI      ("client-header-filter",       ACTION_MULTI_CLIENT_HEADER_FILTER)
 DEFINE_ACTION_MULTI      ("client-header-tagger",       ACTION_MULTI_CLIENT_HEADER_TAGGER)
 DEFINE_ACTION_STRING     ("content-type-overwrite",     ACTION_CONTENT_TYPE_OVERWRITE, ACTION_STRING_CONTENT_TYPE)
index 7905231..6a30577 100644 (file)
--- a/actions.c
+++ b/actions.c
@@ -1117,6 +1117,8 @@ static const char *filter_type_to_string(enum filter_type filter_type)
 #endif
    case FT_SUPPRESS_TAG:
       return "suppress tag filter";
+   case FT_CLIENT_BODY_FILTER:
+      return "client body filter";
    case FT_INVALID_FILTER:
       return "invalid filter type";
    }
@@ -1187,7 +1189,8 @@ static int action_spec_is_valid(struct client_state *csp, const struct action_sp
       {ACTION_MULTI_CLIENT_HEADER_FILTER, FT_CLIENT_HEADER_FILTER},
       {ACTION_MULTI_SERVER_HEADER_FILTER, FT_SERVER_HEADER_FILTER},
       {ACTION_MULTI_CLIENT_HEADER_TAGGER, FT_CLIENT_HEADER_TAGGER},
-      {ACTION_MULTI_SERVER_HEADER_TAGGER, FT_SERVER_HEADER_TAGGER}
+      {ACTION_MULTI_SERVER_HEADER_TAGGER, FT_SERVER_HEADER_TAGGER},
+      {ACTION_MULTI_CLIENT_BODY_FILTER, FT_CLIENT_BODY_FILTER}
    };
    int errors = 0;
    int i;
index e979ebc..4f73db2 100644 (file)
--- a/cgiedit.c
+++ b/cgiedit.c
@@ -240,6 +240,18 @@ static const struct filter_type_info filter_type_info[] =
       "server-header-tagger-all", "server_header_tagger_all",
       "E", "SERVER-HEADER-TAGGER"
    },
+   {
+      ACTION_MULTI_SUPPRESS_TAG,
+      "suppress-tag-params", "suppress-tag",
+      "suppress-tag-all", "suppress_tag_all",
+      "U", "SUPPRESS-TAG"
+   },
+   {
+      ACTION_MULTI_CLIENT_BODY_FILTER,
+      "client-body-filter-params", "client-body-filter",
+      "client-body-filter-all", "client_body_filter_all",
+      "P", "CLIENT-BODY-FILTER"
+   },
 #ifdef FEATURE_EXTERNAL_FILTERS
    {
       ACTION_MULTI_EXTERNAL_FILTER,
@@ -248,12 +260,6 @@ static const struct filter_type_info filter_type_info[] =
       "E", "EXTERNAL-CONTENT-FILTER"
    },
 #endif
-   {
-      ACTION_MULTI_SUPPRESS_TAG,
-      "suppress-tag-params", "suppress-tag",
-      "suppress-tag-all", "suppress_tag_all",
-      "U", "SUPPRESS-TAG"
-   },
 };
 
 /* FIXME: Following non-static functions should be prototyped in .h or made static */
@@ -3187,6 +3193,9 @@ jb_err cgi_edit_actions_submit(struct client_state *csp,
          case 'E':
             multi_action_index = ACTION_MULTI_SERVER_HEADER_TAGGER;
             break;
+         case 'P':
+            multi_action_index = ACTION_MULTI_CLIENT_BODY_FILTER;
+            break;
          default:
             log_error(LOG_LEVEL_ERROR,
                "Unknown filter type: %c for filter %s. Filter ignored.", type, name);
index f266e15..901bb68 100644 (file)
@@ -906,3 +906,17 @@ s@^X-Privoxy-Control:\s*@@i
 SERVER-HEADER-FILTER: privoxy-control Removes X-Privoxy-Control headers.
 
 s@^X-Privoxy-Control:.*@@i
+
+#################################################################################
+#
+# client-body: Modify client request body
+#
+#################################################################################
+CLIENT-BODY-FILTER: remove-first-byte Removes the first byte from the request body
+s@^.@@
+
+CLIENT-BODY-FILTER: remove-test Removes "test" everywhere in the request body
+s@test@@g
+
+CLIENT-BODY-FILTER: overwrite-test-value Overwrites the value of the "test" variable with blafasel
+s@(test=)[^&\s]*@$1blafasel@g
index cb1243d..d7477d7 100644 (file)
@@ -2955,6 +2955,83 @@ example.org/blocked-example-page</screen>
 </variablelist>
 </sect3>
 
+<!--   ~~~~~       New section      ~~~~~     -->
+<sect3 renderas="sect4" id="client-body-filter">
+<title>client-body-filter</title>
+
+<variablelist>
+ <varlistentry>
+  <term>Typical use:</term>
+  <listitem>
+   <para>
+   Rewrite or remove client request body.
+   </para>
+  </listitem>
+ </varlistentry>
+
+ <varlistentry>
+  <term>Effect:</term>
+  <listitem>
+   <para>
+    All request bodies to which this action applies are filtered on-the-fly through
+    the specified regular expression based substitutions.
+   </para>
+  </listitem>
+ </varlistentry>
+
+ <varlistentry>
+  <term>Type:</term>
+  <!-- boolean, parameterized, Multi-value -->
+  <listitem>
+   <para>Multi-value.</para>
+  </listitem>
+ </varlistentry>
+
+ <varlistentry>
+  <term>Parameter:</term>
+  <listitem>
+   <para>
+    The name of a client-body filter, as defined in one of the
+    <link linkend="filter-file">filter files</link>.
+   </para>
+  </listitem>
+ </varlistentry>
+
+ <varlistentry>
+  <term>Notes:</term>
+  <listitem>
+   <para>
+    Please refer to the <link linkend="filter-file">filter file chapter</link>
+    to learn how to create your own client-body filters.
+   </para>
+   <para>
+    The distribution <filename>default.filter</filename> file contains a selection of
+    client-body filters for example purposes.
+   </para>
+   <para>
+    The amount of data that can be filtered is limited by the
+    <literal><link linkend="buffer-limit">buffer-limit</link></literal>
+    option in the main <link linkend="config">config file</link>. The
+    default is 4096 KB (4 Megs). Once this limit is exceeded, the whole
+    request body is passed through unfiltered.
+   </para>
+  </listitem>
+ </varlistentry>
+
+ <varlistentry>
+  <term>Example usage (section):</term>
+  <listitem>
+     <screen>
+# Remove "test" everywhere in the request body
+{+client-body-filter{remove-test}}
+/
+</screen>
+  </listitem>
+ </varlistentry>
+
+</variablelist>
+</sect3>
+
 
 <!--   ~~~~~       New section      ~~~~~     -->
 <sect3 renderas="sect4" id="client-header-tagger">
@@ -4062,7 +4139,7 @@ problem-host.example.com</screen>
     <quote>action</quote> is not available.
    </para>
    <para>
-    The amount of data that can be filtered is limited to the
+    The amount of data that can be filtered is limited by the
     <literal><link linkend="buffer-limit">buffer-limit</link></literal>
     option in the main <link linkend="config">config file</link>. The
     default is 4096 KB (4 Megs). Once this limit is exceeded, the buffered
@@ -6889,9 +6966,11 @@ stupid-server.example.com/</screen>
  <literal><link linkend="filter">filter</link></literal> to
  rewrite the content that is send to the client,
  <literal><link linkend="client-header-filter">client-header-filter</link></literal>
- to rewrite headers that are send by the client, and
+ to rewrite headers that are send by the client,
  <literal><link linkend="server-header-filter">server-header-filter</link></literal>
- to rewrite headers that are send by the server.
+ to rewrite headers that are send by the server, and
+ <literal><link linkend="client-body-filter">client-body-filter</link></literal>
+ to rewrite client request body.
 </para>
 
 <para>
@@ -6950,7 +7029,8 @@ stupid-server.example.com/</screen>
  filter file is organized in sections, which are called <emphasis>filters</emphasis>
  here. Each filter consists of a heading line, that starts with one of the
  <emphasis>keywords</emphasis> <literal>FILTER:</literal>,
- <literal>CLIENT-HEADER-FILTER:</literal> or <literal>SERVER-HEADER-FILTER:</literal>
+ <literal>CLIENT-HEADER-FILTER:</literal>, <literal>SERVER-HEADER-FILTER:</literal> or
+ <literal>CLIENT-BODY-FILTER:</literal>
  followed by the filter's <emphasis>name</emphasis>, and a short (one line)
  <emphasis>description</emphasis> of what it does. Below that line
  come the <emphasis>jobs</emphasis>, i.e. lines that define the actual
index 748042c..95fbe8d 100644 (file)
--- a/filters.c
+++ b/filters.c
@@ -1568,25 +1568,34 @@ struct re_filterfile_spec *get_filter(const struct client_state *csp,
 
 /*********************************************************************
  *
- * Function    :  pcrs_filter_response
+ * Function    :  pcrs_filter_impl
  *
  * Description :  Execute all text substitutions from all applying
- *                +filter actions on the text buffer that's been
- *                accumulated in csp->iob->buf.
+ *                (based on filter_response_body value) +filter
+ *                or +client_body_filter actions on the given buffer.
  *
  * Parameters  :
  *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  filter_response_body = when TRUE execute +filter
+ *                actions; execute +client_body_filter actions otherwise
+ *          3  :  data = Target data
+ *          4  :  data_len = Target data len
  *
  * Returns     :  a pointer to the (newly allocated) modified buffer.
  *                or NULL if there were no hits or something went wrong
  *
  *********************************************************************/
-static char *pcrs_filter_response(struct client_state *csp)
+static char *pcrs_filter_impl(const struct client_state *csp, int filter_response_body,
+                              const char *data, size_t *data_len)
 {
    int hits = 0;
    size_t size, prev_size;
+   const int filters_idx =
+      filter_response_body ? ACTION_MULTI_FILTER : ACTION_MULTI_CLIENT_BODY_FILTER;
+   const enum filter_type filter_type =
+      filter_response_body ? FT_CONTENT_FILTER : FT_CLIENT_BODY_FILTER;
 
-   char *old = NULL;
+   const char *old = NULL;
    char *new = NULL;
    pcrs_job *job;
 
@@ -1596,7 +1605,7 @@ static char *pcrs_filter_response(struct client_state *csp)
    /*
     * Sanity first
     */
-   if (csp->iob->cur >= csp->iob->eod)
+   if (*data_len == 0)
    {
       return(NULL);
    }
@@ -1608,15 +1617,15 @@ static char *pcrs_filter_response(struct client_state *csp)
       return(NULL);
    }
 
-   size = (size_t)(csp->iob->eod - csp->iob->cur);
-   old = csp->iob->cur;
+   size = *data_len;
+   old = data;
 
    /*
-    * For all applying +filter actions, look if a filter by that
+    * For all applying actions, look if a filter by that
     * name exists and if yes, execute it's pcrs_joblist on the
     * buffer.
     */
-   for (filtername = csp->action->multi[ACTION_MULTI_FILTER]->first;
+   for (filtername = csp->action->multi[filters_idx]->first;
         filtername != NULL; filtername = filtername->next)
    {
       int current_hits = 0; /* Number of hits caused by this filter */
@@ -1624,7 +1633,7 @@ static char *pcrs_filter_response(struct client_state *csp)
       int job_hits     = 0; /* How many hits the current job caused */
       pcrs_job *joblist;
 
-      b = get_filter(csp, filtername->str, FT_CONTENT_FILTER);
+      b = get_filter(csp, filtername->str, filter_type);
       if (b == NULL)
       {
          continue;
@@ -1655,7 +1664,7 @@ static char *pcrs_filter_response(struct client_state *csp)
              * input for the next one.
              */
             current_hits += job_hits;
-            if (old != csp->iob->cur)
+            if (old != data)
             {
                freez(old);
             }
@@ -1687,9 +1696,18 @@ static char *pcrs_filter_response(struct client_state *csp)
 
       if (b->dynamic) pcrs_free_joblist(joblist);
 
-      log_error(LOG_LEVEL_RE_FILTER,
-         "filtering %s%s (size %lu) with \'%s\' produced %d hits (new size %lu).",
-         csp->http->hostport, csp->http->path, prev_size, b->name, current_hits, size);
+      if (filter_response_body)
+      {
+         log_error(LOG_LEVEL_RE_FILTER,
+            "filtering %s%s (size %lu) with \'%s\' produced %d hits (new size %lu).",
+            csp->http->hostport, csp->http->path, prev_size, b->name, current_hits, size);
+      }
+      else
+      {
+         log_error(LOG_LEVEL_RE_FILTER,
+            "filtering client %s request body (size %lu) with \'%s\' produced %d hits (new size %lu).",
+            csp->ip_addr_str, prev_size, b->name, current_hits, size);
+      }
 #ifdef FEATURE_EXTENDED_STATISTICS
       update_filter_statistics(b->name, current_hits);
 #endif
@@ -1698,11 +1716,11 @@ static char *pcrs_filter_response(struct client_state *csp)
 
    /*
     * If there were no hits, destroy our copy and let
-    * chat() use the original in csp->iob
+    * chat() use the original content
     */
    if (!hits)
    {
-      if (old != csp->iob->cur && old != new)
+      if (old != data && old != new)
       {
          freez(old);
       }
@@ -1710,12 +1728,50 @@ static char *pcrs_filter_response(struct client_state *csp)
       return(NULL);
    }
 
-   csp->flags |= CSP_FLAG_MODIFIED;
-   csp->content_length = size;
-   clear_iob(csp->iob);
-
+   *data_len = size;
    return(new);
+}
+
 
+/*********************************************************************
+ *
+ * Function    :  pcrs_filter_response_body
+ *
+ * Description :  Execute all text substitutions from all applying
+ *                +filter actions on the text buffer that's been
+ *                accumulated in csp->iob->buf.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns     :  a pointer to the (newly allocated) modified buffer.
+ *                or NULL if there were no hits or something went wrong
+ *
+ *********************************************************************/
+static char *pcrs_filter_response_body(struct client_state *csp)
+{
+   size_t size = (size_t)(csp->iob->eod - csp->iob->cur);
+
+   char *new = NULL;
+
+   /*
+    * Sanity first
+    */
+   if (csp->iob->cur >= csp->iob->eod)
+   {
+      return NULL;
+   }
+
+   new = pcrs_filter_impl(csp, TRUE, csp->iob->cur, &size);
+
+   if (new != NULL)
+   {
+      csp->flags |= CSP_FLAG_MODIFIED;
+      csp->content_length = size;
+      clear_iob(csp->iob);
+   }
+
+   return new;
 }
 
 
@@ -1946,6 +2002,28 @@ static char *execute_external_filter(const struct client_state *csp,
 #endif /* def FEATURE_EXTERNAL_FILTERS */
 
 
+/*********************************************************************
+ *
+ * Function    :  pcrs_filter_request_body
+ *
+ * Description :  Execute all text substitutions from all applying
+ *                +client_body_filter actions on the given text buffer.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  data = Target data
+ *          3  :  data_len = Target data len
+ *
+ * Returns     :  a pointer to the (newly allocated) modified buffer.
+ *                or NULL if there were no hits or something went wrong
+ *
+ *********************************************************************/
+static char *pcrs_filter_request_body(const struct client_state *csp, const char *data, size_t *data_len)
+{
+   return pcrs_filter_impl(csp, FALSE, data, data_len);
+}
+
+
 /*********************************************************************
  *
  * Function    :  gif_deanimate_response
@@ -2034,7 +2112,7 @@ static filter_function_ptr get_filter_function(const struct client_state *csp)
    if ((csp->content_type & CT_TEXT) &&
        (!list_is_empty(csp->action->multi[ACTION_MULTI_FILTER])))
    {
-      filter_function = pcrs_filter_response;
+      filter_function = pcrs_filter_response_body;
    }
    else if ((csp->content_type & CT_GIF) &&
             (csp->action->flags & ACTION_DEANIMATE))
@@ -2332,6 +2410,46 @@ char *execute_content_filters(struct client_state *csp)
 }
 
 
+/*********************************************************************
+ *
+ * Function    :  execute_client_body_filters
+ *
+ * Description :  Executes client body filters for the request that is buffered
+ *                in the client_iob. Upon success moves client_iob cur pointer
+ *                to the end of the processed data.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  content_length = content length. Upon successful filtering
+ *                the passed value is updated with the new content length.
+ *
+ * Returns     :  Pointer to the modified buffer, or
+ *                NULL if filtering failed or wasn't necessary.
+ *
+ *********************************************************************/
+char *execute_client_body_filters(struct client_state *csp, size_t *content_length)
+{
+   char *ret;
+
+   assert(client_body_filters_enabled(csp->action));
+
+   if (content_length == 0)
+   {
+      /*
+       * No content, no filtering necessary.
+       */
+      return NULL;
+   }
+
+   ret = pcrs_filter_request_body(csp, csp->client_iob->cur, content_length);
+   if (ret != NULL)
+   {
+      csp->client_iob->cur = csp->client_iob->eod;
+   }
+   return ret;
+}
+
+
 /*********************************************************************
  *
  * Function    :  get_url_actions
@@ -2755,6 +2873,25 @@ int content_filters_enabled(const struct current_action_spec *action)
 }
 
 
+/*********************************************************************
+ *
+ * Function    :  client_body_filters_enabled
+ *
+ * Description :  Checks whether there are any client body filters
+ *                enabled for the current request.
+ *
+ * Parameters  :
+ *          1  :  action = Action spec to check.
+ *
+ * Returns     :  TRUE for yes, FALSE otherwise
+ *
+ *********************************************************************/
+int client_body_filters_enabled(const struct current_action_spec *action)
+{
+   return !list_is_empty(action->multi[ACTION_MULTI_CLIENT_BODY_FILTER]);
+}
+
+
 /*********************************************************************
  *
  * Function    :  filters_available
index 6b603fc..e16a3ea 100644 (file)
--- a/filters.h
+++ b/filters.h
@@ -84,6 +84,7 @@ extern const struct forward_spec *forward_url(struct client_state *csp,
  * Content modification
  */
 extern char *execute_content_filters(struct client_state *csp);
+extern char *execute_client_body_filters(struct client_state *csp, size_t *filtered_data_len);
 extern char *execute_single_pcrs_command(char *subject, const char *pcrs_command, int *hits);
 extern char *rewrite_url(char *old_url, const char *pcrs_command);
 
@@ -91,6 +92,7 @@ extern pcrs_job *compile_dynamic_pcrs_job_list(const struct client_state *csp, c
 
 extern int content_requires_filtering(struct client_state *csp);
 extern int content_filters_enabled(const struct current_action_spec *action);
+extern int client_body_filters_enabled(const struct current_action_spec *action);
 extern int filters_available(const struct client_state *csp);
 
 /*
diff --git a/jcc.c b/jcc.c
index d8e25d5..44e7561 100644 (file)
--- a/jcc.c
+++ b/jcc.c
@@ -1989,6 +1989,142 @@ static jb_err parse_client_request(struct client_state *csp)
 }
 
 
+/*********************************************************************
+ *
+ * Function    : read_http_request_body
+ *
+ * Description : Reads remaining request body from the client.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns     :  0 on success, anything else is an error.
+ *
+ *********************************************************************/
+static int read_http_request_body(struct client_state *csp)
+{
+   size_t to_read = csp->expected_client_content_length;
+   int len;
+
+   assert(to_read != 0);
+
+   /* check if all data has been already read */
+   if (to_read <= (csp->client_iob->eod - csp->client_iob->cur))
+   {
+      return 0;
+   }
+
+   for (to_read -= (size_t)(csp->client_iob->eod - csp->client_iob->cur);
+        to_read > 0 && data_is_available(csp->cfd, csp->config->socket_timeout);
+        to_read -= (unsigned)len)
+   {
+      char buf[BUFFER_SIZE];
+      size_t max_bytes_to_read = to_read < sizeof(buf) ? to_read : sizeof(buf);
+
+      log_error(LOG_LEVEL_CONNECT,
+         "Waiting for up to %d bytes of request body from the client.",
+         max_bytes_to_read);
+      len = read_socket(csp->cfd, buf, (int)max_bytes_to_read);
+      if (len <= -1)
+      {
+         log_error(LOG_LEVEL_CONNECT, "Failed receiving request body from %s: %E", csp->ip_addr_str);
+         return 1;
+      }
+      if (add_to_iob(csp->client_iob, csp->config->buffer_limit, (char *)buf, len))
+      {
+         return 1;
+      }
+      assert(to_read >= len);
+   }
+
+   if (to_read != 0)
+   {
+      log_error(LOG_LEVEL_CONNECT, "Not enough request body has been read: expected %d more bytes",
+         csp->expected_client_content_length);
+      return 1;
+   }
+   log_error(LOG_LEVEL_CONNECT, "The last %d bytes of the request body have been read",
+      csp->expected_client_content_length);
+   return 0;
+}
+
+
+/*********************************************************************
+ *
+ * Function    : update_client_headers
+ *
+ * Description : Updates the HTTP headers from the client request.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  new_content_length = new content length value to set
+ *
+ * Returns     :  0 on success, anything else is an error.
+ *
+ *********************************************************************/
+static int update_client_headers(struct client_state *csp, size_t new_content_length)
+{
+   static const char content_length[] = "Content-Length:";
+   int updated = 0;
+   struct list_entry *p;
+
+#ifndef FEATURE_HTTPS_INSPECTION
+   for (p = csp->headers->first;
+#else
+   for (p = csp->http->client_ssl ? csp->https_headers->first : csp->headers->first;
+#endif
+        !updated  && (p != NULL); p = p->next)
+   {
+      /* Header crunch()ed in previous run? -> ignore */
+      if (p->str == NULL)
+      {
+         continue;
+      }
+
+      /* Does the current parser handle this header? */
+      if (0 == strncmpic(p->str, content_length, sizeof(content_length) - 1))
+      {
+         updated = (JB_ERR_OK == header_adjust_content_length((char **)&(p->str), new_content_length));
+         if (!updated)
+         {
+            return 1;
+         }
+      }
+   }
+
+   return !updated;
+}
+
+
+/*********************************************************************
+ *
+ * Function    : can_filter_request_body
+ *
+ * Description : Checks if the current request body can be stored in
+ *               the client_iob without hitting buffer limit.
+ *
+ * Parameters  :
+ *          1  : csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns     : TRUE if the current request size do not exceed buffer limit
+ *               FALSE otherwise.
+ *
+ *********************************************************************/
+static int can_filter_request_body(const struct client_state *csp)
+{
+   if (!can_add_to_iob(csp->client_iob, csp->config->buffer_limit,
+                       csp->expected_client_content_length))
+   {
+      log_error(LOG_LEVEL_INFO,
+         "Not filtering request body from %s: buffer limit %d will be exceeded "
+         "(content length %d)", csp->ip_addr_str, csp->config->buffer_limit,
+         csp->expected_client_content_length);
+      return FALSE;
+   }
+   return TRUE;
+}
+
+
 /*********************************************************************
  *
  * Function    : send_http_request
@@ -2006,6 +2142,32 @@ static int send_http_request(struct client_state *csp)
 {
    char *hdr;
    int write_failure;
+   const char *to_send;
+   size_t to_send_len;
+   int filter_client_body = csp->expected_client_content_length != 0 &&
+      client_body_filters_enabled(csp->action) && can_filter_request_body(csp);
+
+   if (filter_client_body)
+   {
+      if (read_http_request_body(csp))
+      {
+         return 1;
+      }
+      to_send_len = csp->expected_client_content_length;
+      to_send = execute_client_body_filters(csp, &to_send_len);
+      if (to_send == NULL)
+      {
+         /* just flush client_iob */
+         filter_client_body = FALSE;
+      }
+      else if (to_send_len != csp->expected_client_content_length &&
+         update_client_headers(csp, to_send_len))
+      {
+         log_error(LOG_LEVEL_HEADER, "Error updating client headers");
+         return 1;
+      }
+      csp->expected_client_content_length = 0;
+   }
 
    hdr = list_to_text(csp->headers);
    if (hdr == NULL)
@@ -2026,26 +2188,100 @@ static int send_http_request(struct client_state *csp)
    {
       log_error(LOG_LEVEL_CONNECT, "Failed sending request headers to: %s: %E",
          csp->http->hostport);
+      return 1;
+   }
+
+   if (filter_client_body)
+   {
+      write_failure = 0 != write_socket(csp->server_connection.sfd, to_send, to_send_len);
+      freez(to_send);
+      if (write_failure)
+      {
+         log_error(LOG_LEVEL_CONNECT, "Failed sending filtered request body to: %s: %E",
+            csp->http->hostport);
+         return 1;
+      }
    }
-   else if (((csp->flags & CSP_FLAG_PIPELINED_REQUEST_WAITING) == 0)
+
+   if (((csp->flags & CSP_FLAG_PIPELINED_REQUEST_WAITING) == 0)
       && (flush_iob(csp->server_connection.sfd, csp->client_iob, 0) < 0))
    {
-      write_failure = 1;
       log_error(LOG_LEVEL_CONNECT, "Failed sending request body to: %s: %E",
          csp->http->hostport);
+      return 1;
    }
+   return 0;
+}
+
+
+#ifdef FEATURE_HTTPS_INSPECTION
+/*********************************************************************
+ *
+ * Function    : read_https_request_body
+ *
+ * Description : Reads remaining request body from the client.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns     :  0 on success, anything else is an error.
+ *
+ *********************************************************************/
+static int read_https_request_body(struct client_state *csp)
+{
+   size_t to_read = csp->expected_client_content_length;
+   int len;
 
-   return write_failure;
+   assert(to_read != 0);
 
+   /* check if all data has been already read */
+   if (to_read <= (csp->client_iob->eod - csp->client_iob->cur))
+   {
+      return 0;
+   }
+
+   for (to_read -= (size_t)(csp->client_iob->eod - csp->client_iob->cur);
+        to_read > 0 && (is_ssl_pending(&(csp->ssl_client_attr)) ||
+          data_is_available(csp->cfd, csp->config->socket_timeout));
+        to_read -= (unsigned)len)
+   {
+      unsigned char buf[BUFFER_SIZE];
+      size_t max_bytes_to_read = to_read < sizeof(buf) ? to_read : sizeof(buf);
+
+      log_error(LOG_LEVEL_CONNECT,
+         "Waiting for up to %d bytes of request body from the client.",
+         max_bytes_to_read);
+      len = ssl_recv_data(&(csp->ssl_client_attr), buf,
+         (unsigned)max_bytes_to_read);
+      if (len <= 0)
+      {
+         log_error(LOG_LEVEL_CONNECT, "Failed receiving request body from %s", csp->ip_addr_str);
+         return 1;
+      }
+      if (add_to_iob(csp->client_iob, csp->config->buffer_limit, (char *)buf, len))
+      {
+         return 1;
+      }
+      assert(to_read >= len);
+   }
+
+   if (to_read != 0)
+   {
+      log_error(LOG_LEVEL_CONNECT, "Not enough request body has been read: expected %d more bytes", to_read);
+      return 1;
+   }
+
+   log_error(LOG_LEVEL_CONNECT, "The last %d bytes of the request body have been read",
+      csp->expected_client_content_length);
+   return 0;
 }
 
 
-#ifdef FEATURE_HTTPS_INSPECTION
 /*********************************************************************
  *
  * Function    : receive_and_send_encrypted_post_data
  *
- * Description : Reads remaining POST data from the client and sends
+ * Description : Reads remaining request body from the client and sends
  *               it to the server.
  *
  * Parameters  :
@@ -2070,7 +2306,7 @@ static int receive_and_send_encrypted_post_data(struct client_state *csp)
          max_bytes_to_read = (int)csp->expected_client_content_length;
       }
       log_error(LOG_LEVEL_CONNECT,
-         "Waiting for up to %d bytes of POST data from the client.",
+         "Waiting for up to %d bytes of request body from the client.",
          max_bytes_to_read);
       len = ssl_recv_data(&(csp->ssl_client_attr), buf,
          (unsigned)max_bytes_to_read);
@@ -2083,7 +2319,7 @@ static int receive_and_send_encrypted_post_data(struct client_state *csp)
          /* XXX: Does this actually happen? */
          break;
       }
-      log_error(LOG_LEVEL_CONNECT, "Forwarding %d bytes of encrypted POST data",
+      log_error(LOG_LEVEL_CONNECT, "Forwarding %d bytes of encrypted request body",
          len);
       len = ssl_send_data(&(csp->ssl_server_attr), buf, (size_t)len);
       if (len == -1)
@@ -2104,7 +2340,7 @@ static int receive_and_send_encrypted_post_data(struct client_state *csp)
       }
    }
 
-   log_error(LOG_LEVEL_CONNECT, "Done forwarding encrypted POST data");
+   log_error(LOG_LEVEL_CONNECT, "Done forwarding encrypted request body");
 
    return 0;
 
@@ -2129,6 +2365,32 @@ static int send_https_request(struct client_state *csp)
    char *hdr;
    int ret;
    long flushed = 0;
+   const char *to_send;
+   size_t to_send_len;
+   int filter_client_body = csp->expected_client_content_length != 0 &&
+      client_body_filters_enabled(csp->action) && can_filter_request_body(csp);
+
+   if (filter_client_body)
+   {
+      if (read_https_request_body(csp))
+      {
+         return 1;
+      }
+      to_send_len = csp->expected_client_content_length;
+      to_send = execute_client_body_filters(csp, &to_send_len);
+      if (to_send == NULL)
+      {
+         /* just flush client_iob */
+         filter_client_body = FALSE;
+      }
+      else if (to_send_len != csp->expected_client_content_length &&
+         update_client_headers(csp, to_send_len))
+      {
+         log_error(LOG_LEVEL_HEADER, "Error updating client headers");
+         return 1;
+      }
+      csp->expected_client_content_length = 0;
+   }
 
    hdr = list_to_text(csp->https_headers);
    if (hdr == NULL)
@@ -2155,6 +2417,18 @@ static int send_https_request(struct client_state *csp)
       return 1;
    }
 
+   if (filter_client_body)
+   {
+      ret = ssl_send_data(&(csp->ssl_server_attr), (const unsigned char *)to_send, to_send_len);
+      freez(to_send);
+      if (ret < 0)
+      {
+         log_error(LOG_LEVEL_CONNECT, "Failed sending filtered request body to: %s",
+            csp->http->hostport);
+         return 1;
+      }
+   }
+
    if (((csp->flags & CSP_FLAG_PIPELINED_REQUEST_WAITING) == 0)
       && ((flushed = ssl_flush_socket(&(csp->ssl_server_attr),
             csp->client_iob)) < 0))
index 4712295..61d2763 100644 (file)
--- a/loaders.c
+++ b/loaders.c
@@ -1164,6 +1164,10 @@ int load_one_re_filterfile(struct client_state *csp, int fileid)
          new_filter = FT_EXTERNAL_CONTENT_FILTER;
       }
 #endif
+      else if (strncmp(buf, "CLIENT-BODY-FILTER:", 19) == 0)
+      {
+         new_filter = FT_CLIENT_BODY_FILTER;
+      }
 
       /*
        * If this is the head of a new filter block, make it a
@@ -1182,6 +1186,10 @@ int load_one_re_filterfile(struct client_state *csp, int fileid)
             new_bl->name = chomp(buf + 16);
          }
 #endif
+         else if (new_filter == FT_CLIENT_BODY_FILTER)
+         {
+            new_bl->name = chomp(buf + 19);
+         }
          else
          {
             new_bl->name = chomp(buf + 21);
index bef3ca9..69a8fb4 100644 (file)
--- a/parsers.c
+++ b/parsers.c
@@ -291,6 +291,27 @@ long flush_iob(jb_socket fd, struct iob *iob, unsigned int delay)
 }
 
 
+/*********************************************************************
+ *
+ * Function    :  can_add_to_iob
+ *
+ * Description :  Checks if the given number of bytes can be added to the given iob
+ *                without exceeding the given buffer limit.
+ *
+ * Parameters  :
+ *          1  :  iob = Destination buffer.
+ *          2  :  buffer_limit = Limit to which the destination may grow
+ *          3  :  n = number of bytes to be added
+ *
+ * Returns     :  TRUE if the given iob can handle given number of bytes
+ *                FALSE buffer limit will be exceeded
+ *
+ *********************************************************************/
+int can_add_to_iob(const struct iob *iob, const size_t buffer_limit, size_t n)
+{
+   return ((size_t)(iob->eod - iob->buf) + n + 1) > buffer_limit ? FALSE : TRUE;
+}
+
 /*********************************************************************
  *
  * Function    :  add_to_iob
@@ -308,7 +329,7 @@ long flush_iob(jb_socket fd, struct iob *iob, unsigned int delay)
  *                or buffer limit reached.
  *
  *********************************************************************/
-jb_err add_to_iob(struct iob *iob, const size_t buffer_limit, char *src, long n)
+jb_err add_to_iob(struct iob *iob, const size_t buffer_limit, const char *src, long n)
 {
    size_t used, offset, need;
    char *p;
@@ -2003,10 +2024,7 @@ static jb_err get_content_length(const char *header_value, unsigned long long *l
  *
  * 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.
+ *          2  :  header = pointer to the Content-Length header
  *
  * Returns     :  JB_ERR_OK on success, or
  *                JB_ERR_MEMORY on out-of-memory error.
@@ -2617,6 +2635,37 @@ static jb_err server_adjust_content_encoding(struct client_state *csp, char **he
 #endif /* defined(FEATURE_ZLIB) */
 
 
+/*********************************************************************
+ *
+ * Function    :  header_adjust_content_length
+ *
+ * Description :  Replace given header with new Content-Length header.
+ *
+ * Parameters  :
+ *          1  :  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.
+ *          2  :  content_length = content length value to set
+ *
+ * Returns     :  JB_ERR_OK on success, or
+ *                JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+jb_err header_adjust_content_length(char **header, size_t content_length)
+{
+   const size_t header_length = 50;
+   freez(*header);
+   *header = malloc(header_length);
+   if (*header == NULL)
+   {
+      return JB_ERR_MEMORY;
+   }
+   create_content_length_header(content_length, *header, header_length);
+   return JB_ERR_OK;
+}
+
+
 /*********************************************************************
  *
  * Function    :  server_adjust_content_length
@@ -2640,14 +2689,10 @@ static jb_err server_adjust_content_length(struct client_state *csp, char **head
    /* Regenerate header if the content was modified. */
    if (csp->flags & CSP_FLAG_MODIFIED)
    {
-      const size_t header_length = 50;
-      freez(*header);
-      *header = malloc(header_length);
-      if (*header == NULL)
+      if (JB_ERR_OK != header_adjust_content_length(header, csp->content_length))
       {
          return JB_ERR_MEMORY;
       }
-      create_content_length_header(csp->content_length, *header, header_length);
       log_error(LOG_LEVEL_HEADER,
          "Adjusted Content-Length to %llu", csp->content_length);
    }
index 9d91022..802133f 100644 (file)
--- a/parsers.h
+++ b/parsers.h
@@ -50,7 +50,8 @@
 #define FILTER_SERVER_HEADERS 1
 
 extern long flush_iob(jb_socket fd, struct iob *iob, unsigned int delay);
-extern jb_err add_to_iob(struct iob *iob, const size_t buffer_limit, char *src, long n);
+extern int can_add_to_iob(const struct iob *iob, const size_t buffer_limit, size_t n);
+extern jb_err add_to_iob(struct iob *iob, const size_t buffer_limit, const char *src, long n);
 extern void clear_iob(struct iob *iob);
 extern jb_err decompress_iob(struct client_state *csp);
 extern char *get_header(struct iob *iob);
@@ -59,6 +60,7 @@ extern jb_err sed(struct client_state *csp, int filter_server_headers);
 #ifdef FEATURE_HTTPS_INSPECTION
 extern jb_err sed_https(struct client_state *csp);
 #endif
+extern jb_err header_adjust_content_length(char **header, size_t content_length);
 extern jb_err update_server_headers(struct client_state *csp);
 extern void get_http_time(int time_offset, char *buf, size_t buffer_size);
 extern jb_err get_destination_from_headers(const struct list *headers, struct http_request *http);
index 44b62d1..39fd39e 100644 (file)
--- a/project.h
+++ b/project.h
@@ -641,8 +641,10 @@ struct iob
 #define ACTION_MULTI_EXTERNAL_FILTER         6
 /** Index into current_action_spec::multi[] for tags to suppress. */
 #define ACTION_MULTI_SUPPRESS_TAG            7
+/** Index into current_action_spec::multi[] for client body filters to apply. */
+#define ACTION_MULTI_CLIENT_BODY_FILTER      8
 /** Number of multi-string actions. */
-#define ACTION_MULTI_COUNT                   8
+#define ACTION_MULTI_COUNT                   9
 
 
 /**
@@ -1292,17 +1294,18 @@ enum filter_type
    FT_SERVER_HEADER_FILTER = 2,
    FT_CLIENT_HEADER_TAGGER = 3,
    FT_SERVER_HEADER_TAGGER = 4,
+   FT_SUPPRESS_TAG = 5,
+   FT_CLIENT_BODY_FILTER = 6,
 #ifdef FEATURE_EXTERNAL_FILTERS
-   FT_EXTERNAL_CONTENT_FILTER = 5,
+   FT_EXTERNAL_CONTENT_FILTER = 7,
 #endif
-   FT_SUPPRESS_TAG = 6,
    FT_INVALID_FILTER       = 42,
 };
 
 #ifdef FEATURE_EXTERNAL_FILTERS
-#define MAX_FILTER_TYPES        7
+#define MAX_FILTER_TYPES        8
 #else
-#define MAX_FILTER_TYPES        6
+#define MAX_FILTER_TYPES        7
 #endif
 
 /**
index 1b9ed4f..5ecb408 100644 (file)
@@ -354,6 +354,19 @@ function show_limit_connect_opts(tf)
         id="change_x_forwarded_for_mode_add" @change-x-forwarded-for-param-add@><label
         for="change_x_forwarded_for_mode_add">Add the header.</label><br>
     </tr>
+    <tr class="bg1" align="left" valign="top">
+      <td class="en1">&nbsp;</td>
+      <td class="dis1" align="center" valign="middle"><input type="radio"
+        name="client_body_filter_all" id="client_body_filter_all_n" value="N" @client-body-filter-all-n@ ></td>
+      <td class="noc1" align="center" valign="middle"><input type="radio"
+        name="client_body_filter_all" id="client_body_filter_all_x" value="X" @client-body-filter-all-x@ ></td>
+      <td class="action"><a href="@user-manual@@actions-help-prefix@CLIENT-BODY-FILTER">client-body-filter</a> *</td>
+      <td>Filter the client request body.
+        You can use the radio buttons on this line to disable
+        all client-body filters applied by previous rules, and/or
+        you can enable or disable the filters individually below.</td>
+    </tr>
+@client-body-filter-params@
     <tr class="bg1" align="left" valign="top">
       <td class="en1">&nbsp;</td>
       <td class="dis1" align="center" valign="middle"><input type="radio"