[Privoxy-commits] [privoxy] 01/03: Add support for filering client request bodies
User Git
git at git.privoxy.org
Wed Dec 30 11:51:10 UTC 2020
This is an automated email from the git hooks/post-receive script.
git pushed a commit to branch master
in repository privoxy.
commit 73b7af6379688bc06717396e36bfdf55a994af95
Author: Maxim Antonov <mantonov at gmail.com>
AuthorDate: Thu Dec 17 15:05:23 2020 +0700
Add support for filering client request bodies
... by using CLIENT-BODY-FILTER filters which can
be enabled with the client-body-filter action.
---
actionlist.h | 1 +
actions.c | 5 +-
cgiedit.c | 21 ++-
default.filter | 14 ++
doc/source/user-manual.sgml | 88 ++++++++++++-
filters.c | 181 +++++++++++++++++++++----
filters.h | 2 +
jcc.c | 290 +++++++++++++++++++++++++++++++++++++++--
loaders.c | 8 ++
parsers.c | 65 +++++++--
parsers.h | 4 +-
project.h | 13 +-
templates/edit-actions-for-url | 13 ++
13 files changed, 648 insertions(+), 57 deletions(-)
diff --git a/actionlist.h b/actionlist.h
index 6129bb00..fc7f5142 100644
--- a/actionlist.h
+++ b/actionlist.h
@@ -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)
diff --git a/actions.c b/actions.c
index 7905231f..6a30577c 100644
--- 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;
diff --git a/cgiedit.c b/cgiedit.c
index e979ebc3..4f73db2b 100644
--- 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);
diff --git a/default.filter b/default.filter
index f266e154..901bb687 100644
--- a/default.filter
+++ b/default.filter
@@ -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 at test@@g
+
+CLIENT-BODY-FILTER: overwrite-test-value Overwrites the value of the "test" variable with blafasel
+s@(test=)[^&\s]*@$1blafasel at g
diff --git a/doc/source/user-manual.sgml b/doc/source/user-manual.sgml
index cb1243d8..d7477d71 100644
--- a/doc/source/user-manual.sgml
+++ b/doc/source/user-manual.sgml
@@ -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
diff --git a/filters.c b/filters.c
index 748042c6..95fbe8d7 100644
--- 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
diff --git a/filters.h b/filters.h
index 6b603fc1..e16a3ea8 100644
--- 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 d8e25d5b..44e75610 100644
--- 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))
diff --git a/loaders.c b/loaders.c
index 47122954..61d27637 100644
--- 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);
diff --git a/parsers.c b/parsers.c
index bef3ca9e..69a8fb4b 100644
--- 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);
}
diff --git a/parsers.h b/parsers.h
index 9d910226..802133f0 100644
--- 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);
diff --git a/project.h b/project.h
index 44b62d19..39fd39ec 100644
--- 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
/**
diff --git a/templates/edit-actions-for-url b/templates/edit-actions-for-url
index 1b9ed4f9..5ecb4089 100644
--- a/templates/edit-actions-for-url
+++ b/templates/edit-actions-for-url
@@ -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"> </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 at 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>
+ at client-body-filter-params@
<tr class="bg1" align="left" valign="top">
<td class="en1"> </td>
<td class="dis1" align="center" valign="middle"><input type="radio"
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the Privoxy-commits
mailing list