#include <assert.h>
#ifndef _WIN32
-#ifndef __OS2__
#include <unistd.h>
-#endif /* ndef __OS2__ */
#include <netinet/in.h>
#else
#include <winsock2.h>
#endif /* ndef _WIN32 */
-#ifdef __OS2__
-#include <utils.h>
-#endif /* def __OS2__ */
-
#include "project.h"
#include "filters.h"
#include "encode.h"
#ifdef FEATURE_CLIENT_TAGS
#include "client-tags.h"
#endif
+#ifdef FEATURE_HTTPS_INSPECTION
+#include "ssl.h"
+#endif
#ifdef _WIN32
#include "win32.h"
#endif
struct url_actions *b);
+#ifdef FEATURE_EXTENDED_STATISTICS
+static void increment_block_reason_counter(const char *block_reason);
+#endif
+
#ifdef FEATURE_ACL
#ifdef HAVE_RFC2553
/*********************************************************************
return cgi_error_memory();
}
+#ifdef FEATURE_EXTENDED_STATISTICS
+ if (csp->action->string[ACTION_STRING_BLOCK] != NULL)
+ {
+ increment_block_reason_counter(csp->action->string[ACTION_STRING_BLOCK]);
+ }
+#endif
+
/*
* If it's an image-url, send back an image or redirect
* as specified by the relevant +image action
* the last URL found.
*
*********************************************************************/
-char *get_last_url(char *subject, const char *redirect_mode)
+static char *get_last_url(char *subject, const char *redirect_mode)
{
char *new_url = NULL;
char *tmp;
*/
char * redirect_mode;
#endif /* def FEATURE_FAST_REDIRECTS */
- char *old_url = NULL;
char *new_url = NULL;
char *redirection_string;
if (*redirection_string == 's')
{
- old_url = csp->http->url;
- new_url = rewrite_url(old_url, redirection_string);
+ char *requested_url;
+
+#ifdef FEATURE_HTTPS_INSPECTION
+ if (client_use_ssl(csp))
+ {
+ jb_err err;
+
+ requested_url = strdup_or_die("https://");
+ err = string_append(&requested_url, csp->http->hostport);
+ if (!err) err = string_append(&requested_url, csp->http->path);
+ if (err)
+ {
+ log_error(LOG_LEVEL_FATAL,
+ "Failed to rebuild URL 'https://%s%s'",
+ csp->http->hostport, csp->http->path);
+ }
+ }
+ else
+#endif
+ {
+ requested_url = csp->http->url;
+ }
+ new_url = rewrite_url(requested_url, redirection_string);
+#ifdef FEATURE_HTTPS_INSPECTION
+ if (requested_url != csp->http->url)
+ {
+ assert(client_use_ssl(csp));
+ freez(requested_url);
+ }
+#endif
}
else
{
#ifdef FEATURE_FAST_REDIRECTS
if ((csp->action->flags & ACTION_FAST_REDIRECTS))
{
+ char *old_url;
+
redirect_mode = csp->action->string[ACTION_STRING_FAST_REDIRECTS];
/*
/*********************************************************************
*
- * 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 %d) with \'%s\' produced %d hits (new size %d).",
- 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 request body from client %s "
+ "(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
hits += current_hits;
}
/*
* 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 != data && old != new)
+ {
+ freez(old);
+ }
freez(new);
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;
}
*/
if ((*size != 0) && fwrite(content, *size, 1, fp) != 1)
{
- log_error(LOG_LEVEL_ERROR, "fwrite(..., %d, 1, ..) failed: %E", *size);
+ log_error(LOG_LEVEL_ERROR, "fwrite(..., %lu, 1, ..) failed: %E", *size);
unlink(file_name);
fclose(fp);
return NULL;
{
log_error(LOG_LEVEL_RE_FILTER,
"Executing '%s' resulted in return value %d. "
- "Read %d of up to %d bytes.", name, (ret >> 8), new_size, *size);
+ "Read %lu of up to %lu bytes.", name, (ret >> 8), new_size, *size);
}
unlink(file_name);
#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
}
else
{
- log_error(LOG_LEVEL_DEANIMATE, "Success! GIF shrunk from %d bytes to %d.", size, out->offset);
+ log_error(LOG_LEVEL_DEANIMATE,
+ "Success! GIF shrunk from %lu bytes to %lu.", size, out->offset);
}
csp->content_length = out->offset;
csp->flags |= CSP_FLAG_MODIFIED;
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))
{
log_error(LOG_LEVEL_ERROR,
"Chunk size %u exceeds buffered data left. "
- "Already digested %u of %u buffered bytes.",
- chunksize, (unsigned int)newsize, (unsigned int)*size);
+ "Already digested %lu of %lu buffered bytes.",
+ chunksize, newsize, *size);
return JB_ERR_PARSE;
}
}
/* XXX: Should get its own loglevel. */
- log_error(LOG_LEVEL_RE_FILTER, "De-chunking successful. Shrunk from %d to %d", *size, newsize);
+ log_error(LOG_LEVEL_RE_FILTER,
+ "De-chunking successful. Shrunk from %lu to %lu", *size, newsize);
*size = newsize;
}
+/*********************************************************************
+ *
+ * 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
struct url_actions *b;
int i;
-#ifdef FEATURE_HTTPS_INSPECTION
- if (!csp->http->client_ssl)
-#endif
- {
- /*
- * When filtering TLS traffic this function gets called a
- * second time after the encrypted headers have been received.
- *
- * Only initialize the first time. The second time we apply
- * the newly set actions on top of the ones that were set
- * the first time.
- */
- init_current_action(csp->action);
- }
+ init_current_action(csp->action);
for (i = 0; i < MAX_AF_FILES; i++)
{
* The server didn't bother to declare a MIME-Type.
* Assume it's text that can be filtered.
*
- * This also regulary happens with 304 responses,
+ * This also regularly happens with 304 responses,
* therefore logging anything here would cause
* too much noise.
*/
}
+/*********************************************************************
+ *
+ * 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
return FALSE;
}
+#ifdef FEATURE_EXTENDED_STATISTICS
+
+struct filter_statistics_entry
+{
+ char *filter;
+ unsigned long long executions;
+ unsigned long long response_bodies_modified;
+ unsigned long long hits;
+
+ struct filter_statistics_entry *next;
+};
+
+static struct filter_statistics_entry *filter_statistics = NULL;
+
+
+/*********************************************************************
+ *
+ * Function : register_filter_for_statistics
+ *
+ * Description : Registers a filter so we can gather statistics for
+ * it unless the filter has already been registered
+ * before.
+ *
+ * Parameters :
+ * 1 : filter = Name of the filter to register
+ *
+ * Returns : void
+ *
+ *********************************************************************/
+void register_filter_for_statistics(const char *filter)
+{
+ struct filter_statistics_entry *entry;
+
+ privoxy_mutex_lock(&filter_statistics_mutex);
+
+ if (filter_statistics == NULL)
+ {
+ filter_statistics = zalloc_or_die(sizeof(struct filter_statistics_entry));
+ entry = filter_statistics;
+ entry->filter = strdup_or_die(filter);
+ privoxy_mutex_unlock(&filter_statistics_mutex);
+ return;
+ }
+ entry = filter_statistics;
+ while (entry != NULL)
+ {
+ if (!strcmp(entry->filter, filter))
+ {
+ /* Already registered, nothing to do. */
+ break;
+ }
+ if (entry->next == NULL)
+ {
+ entry->next = zalloc_or_die(sizeof(struct filter_statistics_entry));
+ entry->next->filter = strdup_or_die(filter);
+ break;
+ }
+ entry = entry->next;
+ }
+
+ privoxy_mutex_unlock(&filter_statistics_mutex);
+
+}
+
+
+/*********************************************************************
+ *
+ * Function : update_filter_statistics
+ *
+ * Description : Updates the statistics for a filter.
+ *
+ * Parameters :
+ * 1 : filter = Name of the filter to update
+ * 2 : hits = Hit count.
+ *
+ * Returns : void
+ *
+ *********************************************************************/
+void update_filter_statistics(const char *filter, int hits)
+{
+ struct filter_statistics_entry *entry;
+
+ privoxy_mutex_lock(&filter_statistics_mutex);
+
+ entry = filter_statistics;
+ while (entry != NULL)
+ {
+ if (!strcmp(entry->filter, filter))
+ {
+ entry->executions++;
+ if (hits != 0)
+ {
+ entry->response_bodies_modified++;
+ entry->hits += (unsigned)hits;
+ }
+ break;
+ }
+ entry = entry->next;
+ }
+
+ privoxy_mutex_unlock(&filter_statistics_mutex);
+
+}
+
+
+/*********************************************************************
+ *
+ * Function : get_filter_statistics
+ *
+ * Description : Gets the statistics for a filter.
+ *
+ * Parameters :
+ * 1 : filter = Name of the filter to get statistics for.
+ * 2 : executions = Storage for the execution count.
+ * 3 : response_bodies_modified = Storage for the number
+ * of modified response bodies.
+ * 4 : hits = Storage for the number of hits.
+ *
+ * Returns : void
+ *
+ *********************************************************************/
+void get_filter_statistics(const char *filter, unsigned long long *executions,
+ unsigned long long *response_bodies_modified,
+ unsigned long long *hits)
+{
+ struct filter_statistics_entry *entry;
+
+ privoxy_mutex_lock(&filter_statistics_mutex);
+
+ entry = filter_statistics;
+ while (entry != NULL)
+ {
+ if (!strcmp(entry->filter, filter))
+ {
+ *executions = entry->executions;
+ *response_bodies_modified = entry->response_bodies_modified;
+ *hits = entry->hits;
+ break;
+ }
+ entry = entry->next;
+ }
+
+ privoxy_mutex_unlock(&filter_statistics_mutex);
+
+}
+
+
+struct block_statistics_entry
+{
+ char *block_reason;
+ unsigned long long count;
+
+ struct block_statistics_entry *next;
+};
+
+static struct block_statistics_entry *block_statistics = NULL;
+
+/*********************************************************************
+ *
+ * Function : register_block_reason_for_statistics
+ *
+ * Description : Registers a block reason so we can gather statistics
+ * for it unless the block reason has already been
+ * registered before.
+ *
+ * Parameters :
+ * 1 : block_reason = Block reason to register
+ *
+ * Returns : void
+ *
+ *********************************************************************/
+void register_block_reason_for_statistics(const char *block_reason)
+{
+ struct block_statistics_entry *entry;
+
+ privoxy_mutex_lock(&block_statistics_mutex);
+
+ if (block_statistics == NULL)
+ {
+ block_statistics = zalloc_or_die(sizeof(struct block_statistics_entry));
+ entry = block_statistics;
+ entry->block_reason = strdup_or_die(block_reason);
+ privoxy_mutex_unlock(&block_statistics_mutex);
+ return;
+ }
+ entry = block_statistics;
+ while (entry != NULL)
+ {
+ if (!strcmp(entry->block_reason, block_reason))
+ {
+ /* Already registered, nothing to do. */
+ break;
+ }
+ if (entry->next == NULL)
+ {
+ entry->next = zalloc_or_die(sizeof(struct block_statistics_entry));
+ entry->next->block_reason = strdup_or_die(block_reason);
+ break;
+ }
+ entry = entry->next;
+ }
+
+ privoxy_mutex_unlock(&block_statistics_mutex);
+
+}
+
+
+/*********************************************************************
+ *
+ * Function : increment_block_reason_counter
+ *
+ * Description : Updates the counter for a block reason.
+ *
+ * Parameters :
+ * 1 : block_reason = Block reason to count
+ *
+ * Returns : void
+ *
+ *********************************************************************/
+static void increment_block_reason_counter(const char *block_reason)
+{
+ struct block_statistics_entry *entry;
+
+ privoxy_mutex_lock(&block_statistics_mutex);
+
+ entry = block_statistics;
+ while (entry != NULL)
+ {
+ if (!strcmp(entry->block_reason, block_reason))
+ {
+ entry->count++;
+ break;
+ }
+ entry = entry->next;
+ }
+
+ privoxy_mutex_unlock(&block_statistics_mutex);
+
+}
+
+
+/*********************************************************************
+ *
+ * Function : get_block_reason_count
+ *
+ * Description : Gets number of times a block reason was used.
+ *
+ * Parameters :
+ * 1 : block_reason = Block reason to get statistics for.
+ * 2 : count = Storage for the number of times the block
+ * reason was used.
+ *
+ * Returns : void
+ *
+ *********************************************************************/
+void get_block_reason_count(const char *block_reason, unsigned long long *count)
+{
+ struct block_statistics_entry *entry;
+
+ privoxy_mutex_lock(&block_statistics_mutex);
+
+ entry = block_statistics;
+ while (entry != NULL)
+ {
+ if (!strcmp(entry->block_reason, block_reason))
+ {
+ *count = entry->count;
+ break;
+ }
+ entry = entry->next;
+ }
+
+ privoxy_mutex_unlock(&block_statistics_mutex);
+
+}
+
+#endif /* def FEATURE_EXTENDED_STATISTICS */
/*
Local Variables: