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)
#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";
}
{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;
"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,
"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 */
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);
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
</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">
<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
<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>
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
/*********************************************************************
*
- * 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;
/*
* Sanity first
*/
- if (csp->iob->cur >= csp->iob->eod)
+ if (*data_len == 0)
{
return(NULL);
}
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 */
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;
* input for the next one.
*/
current_hits += job_hits;
- if (old != csp->iob->cur)
+ if (old != data)
{
freez(old);
}
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
/*
* 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);
}
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;
}
#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
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))
}
+/*********************************************************************
+ *
+ * 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
}
+/*********************************************************************
+ *
+ * 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
* 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);
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);
/*
}
+/*********************************************************************
+ *
+ * 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
{
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)
{
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 :
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);
/* 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)
}
}
- log_error(LOG_LEVEL_CONNECT, "Done forwarding encrypted POST data");
+ log_error(LOG_LEVEL_CONNECT, "Done forwarding encrypted request body");
return 0;
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)
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))
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
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);
}
+/*********************************************************************
+ *
+ * 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
* 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;
*
* 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.
#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
/* 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);
}
#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);
#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);
#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
/**
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
/**
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@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"> </td>
<td class="dis1" align="center" valign="middle"><input type="radio"