-const char parsers_rcs[] = "$Id: parsers.c,v 1.85 2007/01/26 15:33:46 fabiankeil Exp $";
+const char parsers_rcs[] = "$Id: parsers.c,v 1.103 2007/06/01 16:31:54 fabiankeil Exp $";
/*********************************************************************
*
* File : $Source: /cvsroot/ijbswa/current/parsers.c,v $
* `client_uagent', `client_x_forwarded',
* `client_x_forwarded_adder', `client_xtra_adder',
* `content_type', `crumble', `destroy_list', `enlist',
- * `flush_socket', ``get_header', `sed', `filter_server_header'
- * `filter_client_header', `filter_header', `crunch_server_header',
+ * `flush_socket', ``get_header', `sed', `filter_header'
* `server_content_encoding', `server_content_disposition',
* `server_last_modified', `client_accept_language',
* `crunch_client_header', `client_if_modified_since',
* `client_if_none_match', `get_destination_from_headers',
- * `parse_header_time' and `server_set_cookie'.
+ * `parse_header_time', `decompress_iob' and `server_set_cookie'.
*
* Copyright : Written by and Copyright (C) 2001-2007 the SourceForge
* Privoxy team. http://www.privoxy.org/
*
* Revisions :
* $Log: parsers.c,v $
+ * Revision 1.103 2007/06/01 16:31:54 fabiankeil
+ * Change sed() to return a jb_err in preparation for forward-override{}.
+ *
+ * Revision 1.102 2007/05/27 12:39:32 fabiankeil
+ * Adjust "X-Filter: No" to disable dedicated header filters.
+ *
+ * Revision 1.101 2007/05/14 10:16:41 fabiankeil
+ * Streamline client_cookie_adder().
+ *
+ * Revision 1.100 2007/04/30 15:53:11 fabiankeil
+ * Make sure filters with dynamic jobs actually use them.
+ *
+ * Revision 1.99 2007/04/30 15:06:26 fabiankeil
+ * - Introduce dynamic pcrs jobs that can resolve variables.
+ * - Remove unnecessary update_action_bits_for_all_tags() call.
+ *
+ * Revision 1.98 2007/04/17 18:32:10 fabiankeil
+ * - Make tagging based on tags set by earlier taggers
+ * of the same kind possible.
+ * - Log whether or not new tags cause action bits updates
+ * (in which case a matching tag-pattern section exists).
+ * - Log if the user tries to set a tag that is already set.
+ *
+ * Revision 1.97 2007/04/15 16:39:21 fabiankeil
+ * Introduce tags as alternative way to specify which
+ * actions apply to a request. At the moment tags can be
+ * created based on client and server headers.
+ *
+ * Revision 1.96 2007/04/12 12:53:58 fabiankeil
+ * Log a warning if the content is compressed, filtering is
+ * enabled and Privoxy was compiled without zlib support.
+ * Closes FR#1673938.
+ *
+ * Revision 1.95 2007/03/25 14:26:40 fabiankeil
+ * - Fix warnings when compiled with glibc.
+ * - Don't use crumble() for cookie crunching.
+ * - Move cookie time parsing into parse_header_time().
+ * - Let parse_header_time() return a jb_err code
+ * instead of a pointer that can only be used to
+ * check for NULL anyway.
+ *
+ * Revision 1.94 2007/03/21 12:23:53 fabiankeil
+ * - Add better protection against malicious gzip headers.
+ * - Stop logging the first hundred bytes of decompressed content.
+ * It looks like it's working and there is always debug 16.
+ * - Log the content size after decompression in decompress_iob()
+ * instead of pcrs_filter_response().
+ *
+ * Revision 1.93 2007/03/20 15:21:44 fabiankeil
+ * - Use dedicated header filter actions instead of abusing "filter".
+ * Replace "filter-client-headers" and "filter-client-headers"
+ * with "server-header-filter" and "client-header-filter".
+ * - Remove filter_client_header() and filter_client_header(),
+ * filter_header() now checks the shiny new
+ * CSP_FLAG_CLIENT_HEADER_PARSING_DONE flag instead.
+ *
+ * Revision 1.92 2007/03/05 13:25:32 fabiankeil
+ * - Cosmetical changes for LOG_LEVEL_RE_FILTER messages.
+ * - Handle "Cookie:" and "Connection:" headers a bit smarter
+ * (don't crunch them just to recreate them later on).
+ * - Add another non-standard time format for the cookie
+ * expiration date detection.
+ * - Fix a valgrind warning.
+ *
+ * Revision 1.91 2007/02/24 12:27:32 fabiankeil
+ * Improve cookie expiration date detection.
+ *
+ * Revision 1.90 2007/02/08 19:12:35 fabiankeil
+ * Don't run server_content_length() the first time
+ * sed() parses server headers; only adjust the
+ * Content-Length header if the page was modified.
+ *
+ * Revision 1.89 2007/02/07 16:52:11 fabiankeil
+ * Fix log messages regarding the cookie time format
+ * (cookie and request URL were mixed up).
+ *
+ * Revision 1.88 2007/02/07 11:27:12 fabiankeil
+ * - Let decompress_iob()
+ * - not corrupt the content if decompression fails
+ * early. (the first byte(s) were lost).
+ * - use pointer arithmetics with defined outcome for
+ * a change.
+ * - Use a different kludge to remember a failed decompression.
+ *
+ * Revision 1.87 2007/01/31 16:21:38 fabiankeil
+ * Search for Max-Forwards headers case-insensitive,
+ * don't generate the "501 unsupported" message for invalid
+ * Max-Forwards values and don't increase negative ones.
+ *
+ * Revision 1.86 2007/01/30 13:05:26 fabiankeil
+ * - Let server_set_cookie() check the expiration date
+ * of cookies and don't touch the ones that are already
+ * expired. Fixes problems with low quality web applications
+ * as described in BR 932612.
+ *
+ * - Adjust comment in client_max_forwards to reality;
+ * remove invalid Max-Forwards headers.
+ *
* Revision 1.85 2007/01/26 15:33:46 fabiankeil
* Stop filter_header() from unintentionally removing
* empty header lines that were enlisted by the continue
#include <ctype.h>
#include <assert.h>
#include <string.h>
+
+#ifdef __GLIBC__
+/*
+ * Convince GNU's libc to provide a strptime prototype.
+ */
+#define __USE_XOPEN
+#endif /*__GLIBC__ */
#include <time.h>
#ifdef FEATURE_ZLIB
#include "jbsockets.h"
#include "miscutil.h"
#include "list.h"
+#include "actions.h"
+#include "filters.h"
#ifndef HAVE_STRPTIME
#include "strptime.h"
/* Fix a problem with Solaris. There should be no effect on other
* platforms.
- * Solaris's isspace() is a macro which uses it's argument directly
+ * Solaris's isspace() is a macro which uses its argument directly
* as an array index. Therefore we need to make sure that high-bit
* characters generate +ve values, and ideally we also want to make
* the argument match the declared parameter type of "int".
#define ijb_isupper(__X) isupper((int)(unsigned char)(__X))
#define ijb_tolower(__X) tolower((int)(unsigned char)(__X))
+jb_err header_tagger(struct client_state *csp, char *header);
+jb_err scan_headers(struct client_state *csp);
const struct parsers client_patterns[] = {
{ "referer:", 8, client_referrer },
{ "Host:", 5, client_host },
{ "if-modified-since:", 18, client_if_modified_since },
{ "Keep-Alive:", 11, crumble },
- { "connection:", 11, crumble },
+ { "connection:", 11, connection },
{ "proxy-connection:", 17, crumble },
{ "max-forwards:", 13, client_max_forwards },
{ "Accept-Language:", 16, client_accept_language },
{ "if-none-match:", 14, client_if_none_match },
{ "X-Filter:", 9, client_x_filter },
{ "*", 0, crunch_client_header },
- { "*", 0, filter_client_header },
+ { "*", 0, filter_header },
{ NULL, 0, NULL }
};
const struct parsers server_patterns[] = {
{ "HTTP", 4, server_http },
{ "set-cookie:", 11, server_set_cookie },
- { "connection:", 11, crumble },
+ { "connection:", 11, connection },
{ "Content-Type:", 13, server_content_type },
- { "Content-Length:", 15, server_content_length },
{ "Content-MD5:", 12, server_content_md5 },
{ "Content-Encoding:", 17, server_content_encoding },
{ "Transfer-Encoding:", 18, server_transfer_coding },
{ "content-disposition:", 20, server_content_disposition },
{ "Last-Modified:", 14, server_last_modified },
{ "*", 0, crunch_server_header },
- { "*", 0, filter_server_header },
+ { "*", 0, filter_header },
{ NULL, 0, NULL }
};
jb_err decompress_iob(struct client_state *csp)
{
char *buf; /* new, uncompressed buffer */
+ char *cur; /* Current iob position (to keep the original
+ * iob->cur unmodified if we return early) */
size_t bufsize; /* allocated size of the new buffer */
+ size_t old_size; /* Content size before decompression */
size_t skip_size; /* Number of bytes at the beginning of the iob
that we should NOT decompress. */
int status; /* return status of the inflate() call */
z_stream zstr; /* used by calls to zlib */
+ assert(csp->iob->cur - csp->iob->buf > 0);
+ assert(csp->iob->eod - csp->iob->cur > 0);
+
bufsize = csp->iob->size;
skip_size = (size_t)(csp->iob->cur - csp->iob->buf);
+ old_size = (size_t)(csp->iob->eod - csp->iob->cur);
+
+ cur = csp->iob->cur;
if (bufsize < 10)
{
* Strip off the gzip header. Please see RFC 1952 for more
* explanation of the appropriate fields.
*/
- if ((*csp->iob->cur++ != (char)0x1f)
- || (*csp->iob->cur++ != (char)0x8b)
- || (*csp->iob->cur++ != Z_DEFLATED))
+ if ((*cur++ != (char)0x1f)
+ || (*cur++ != (char)0x8b)
+ || (*cur++ != Z_DEFLATED))
{
log_error (LOG_LEVEL_ERROR, "Invalid gzip header when decompressing");
return JB_ERR_COMPRESS;
}
else
{
- int flags = *csp->iob->cur++;
+ int flags = *cur++;
/*
* XXX: These magic numbers should be replaced
* with macros to give a better idea what they do.
if (flags & 0xe0)
{
/* The gzip header has reserved bits set; bail out. */
- log_error (LOG_LEVEL_ERROR, "Invalid gzip header when decompressing");
+ log_error (LOG_LEVEL_ERROR, "Invalid gzip header flags when decompressing");
return JB_ERR_COMPRESS;
}
- csp->iob->cur += 6;
+ cur += 6;
/* Skip extra fields if necessary. */
if (flags & 0x04)
* Skip a given number of bytes, specified
* as a 16-bit little-endian value.
*/
- csp->iob->cur += *csp->iob->cur++ + (*csp->iob->cur++ << 8);
+ /*
+ * XXX: This code used to be:
+ *
+ * csp->iob->cur += *csp->iob->cur++ + (*csp->iob->cur++ << 8);
+ *
+ * which I had to change into:
+ *
+ * cur += *cur++ + (*cur++ << 8);
+ *
+ * at which point gcc43 finally noticed that the value
+ * of cur is undefined (it depends on which of the
+ * summands is evaluated first).
+ *
+ * I haven't come across a site where this
+ * code is actually executed yet, but I hope
+ * it works anyway.
+ */
+ int skip_bytes;
+ skip_bytes = *cur++;
+ skip_bytes = *cur++ << 8;
+
+ assert(skip_bytes == *csp->iob->cur - 2 + ((*csp->iob->cur - 1) << 8));
+
+ /*
+ * The number of bytes to skip should be positive
+ * and we'd like to stay in the buffer.
+ */
+ if((skip_bytes < 0) || (skip_bytes >= (csp->iob->eod - cur)))
+ {
+ log_error (LOG_LEVEL_ERROR,
+ "Unreasonable amount of bytes to skip (%d). Stopping decompression",
+ skip_bytes);
+ return JB_ERR_COMPRESS;
+ }
+ log_error (LOG_LEVEL_INFO,
+ "Skipping %d bytes for gzip compression. Does this sound right?",
+ skip_bytes);
+ cur += skip_bytes;
}
/* Skip the filename if necessary. */
if (flags & 0x08)
{
- /* A null-terminated string follows. */
- while (*csp->iob->cur++);
+ /* A null-terminated string is supposed to follow. */
+ while (*cur++ && (cur < csp->iob->eod));
+
}
/* Skip the comment if necessary. */
if (flags & 0x10)
{
- while (*csp->iob->cur++);
+ /* A null-terminated string is supposed to follow. */
+ while (*cur++ && (cur < csp->iob->eod));
}
/* Skip the CRC if necessary. */
if (flags & 0x02)
{
- csp->iob->cur += 2;
+ cur += 2;
+ }
+
+ if (cur >= csp->iob->eod)
+ {
+ /*
+ * If the current position pointer reached or passed
+ * the buffer end, we were obviously tricked to skip
+ * too much.
+ */
+ log_error (LOG_LEVEL_ERROR,
+ "Malformed gzip header detected. Aborting decompression.");
+ return JB_ERR_COMPRESS;
}
}
}
* XXX: The debug level should be lowered
* before the next stable release.
*/
- log_error (LOG_LEVEL_INFO, "Decompressing deflated iob: %d", *csp->iob->cur);
+ log_error (LOG_LEVEL_INFO, "Decompressing deflated iob: %d", *cur);
/*
* In theory (that is, according to RFC 1950), deflate-compressed
* data should begin with a two-byte zlib header and have an
}
/* Set up the fields required by zlib. */
- zstr.next_in = (Bytef *)csp->iob->cur;
- zstr.avail_in = (unsigned int)(csp->iob->eod - csp->iob->cur);
+ zstr.next_in = (Bytef *)cur;
+ zstr.avail_in = (unsigned int)(csp->iob->eod - cur);
zstr.zalloc = Z_NULL;
zstr.zfree = Z_NULL;
zstr.opaque = Z_NULL;
&& (csp->iob->cur <= csp->iob->eod)
&& (csp->iob->eod <= csp->iob->buf + csp->iob->size))
{
- char t = csp->iob->cur[100];
- csp->iob->cur[100] = '\0';
- /*
- * XXX: The debug level should be lowered
- * before the next stable release.
- */
- log_error(LOG_LEVEL_INFO, "Sucessfully decompressed: %s", csp->iob->cur);
- csp->iob->cur[100] = t;
- return JB_ERR_OK;
+ const size_t new_size = (size_t)(csp->iob->eod - csp->iob->cur);
+ if (new_size > 0)
+ {
+ log_error(LOG_LEVEL_RE_FILTER,
+ "Decompression successful. Old size: %d, new size: %d.",
+ old_size, new_size);
+ }
+ else
+ {
+ /* zlib thinks this is OK, so lets do the same. */
+ log_error(LOG_LEVEL_INFO, "Decompression didn't result in any content.");
+ }
}
else
{
return JB_ERR_COMPRESS;
}
+ return JB_ERR_OK;
+
}
#endif /* defined(FEATURE_ZLIB) */
}
+
+/*********************************************************************
+ *
+ * Function : scan_headers
+ *
+ * Description : Scans headers, applies tags and updates action bits.
+ *
+ * Parameters :
+ * 1 : csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns : JB_ERR_OK
+ *
+ *********************************************************************/
+jb_err scan_headers(struct client_state *csp)
+{
+ struct list_entry *h; /* Header */
+ jb_err err = JB_ERR_OK;
+
+ log_error(LOG_LEVEL_HEADER, "scanning headers for: %s", csp->http->url);
+
+ for (h = csp->headers->first; (err == JB_ERR_OK) && (h != NULL) ; h = h->next)
+ {
+ /* Header crunch()ed in previous run? -> ignore */
+ if (h->str == NULL) continue;
+ log_error(LOG_LEVEL_HEADER, "scan: %s", h->str);
+ err = header_tagger(csp, h->str);
+ }
+
+ return err;
+}
+
+
/*********************************************************************
*
* Function : sed
* headers (client or server)
* 3 : csp = Current client state (buffers, headers, etc...)
*
- * Returns : Single pointer to a fully formed header, or NULL
- * on out-of-memory error.
+ * Returns : JB_ERR_OK in case off success, or
+ * JB_ERR_MEMORY on out-of-memory error.
*
*********************************************************************/
-char *sed(const struct parsers pats[],
- const add_header_func_ptr more_headers[],
- struct client_state *csp)
+jb_err sed(const struct parsers pats[],
+ const add_header_func_ptr more_headers[],
+ struct client_state *csp)
{
struct list_entry *p;
const struct parsers *v;
if (first_run) /* Parse and print */
{
- log_error(LOG_LEVEL_HEADER, "scanning headers for: %s", csp->http->url);
+ scan_headers(csp);
+
for (v = pats; (err == JB_ERR_OK) && (v->str != NULL) ; v++)
{
for (p = csp->headers->first; (err == JB_ERR_OK) && (p != NULL) ; p = p->next)
/* Header crunch()ed in previous run? -> ignore */
if (p->str == NULL) continue;
- if (v == pats) log_error(LOG_LEVEL_HEADER, "scan: %s", p->str);
-
/* Does the current parser handle this header? */
if ((strncmpic(p->str, v->str, v->len) == 0) || (v->len == CHECK_EVERY_HEADER_REMAINING))
{
}
}
- if (err != JB_ERR_OK)
- {
- return NULL;
- }
-
- return list_to_text(csp->headers);
+ return err;
}
-/* here begins the family of parser functions that reformat header lines */
/*********************************************************************
*
- * Function : filter_server_header
+ * Function : header_tagger
+ *
+ * Description : Executes all text substitutions from applying
+ * tag actions and saves the result as tag.
*
- * Description : Checks if server header filtering is enabled.
- * If it is, filter_header is called to do the work.
+ * XXX: Shares enough code with filter_header() and
+ * pcrs_filter_response() to warrant some helper functions.
*
* 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 = Header that is used as tagger input
*
* Returns : JB_ERR_OK on success and always succeeds
*
*********************************************************************/
-jb_err filter_server_header(struct client_state *csp, char **header)
+jb_err header_tagger(struct client_state *csp, char *header)
{
- if (csp->action->flags & ACTION_FILTER_SERVER_HEADERS)
+ int wanted_filter_type;
+ int multi_action_index;
+ int i;
+ pcrs_job *job;
+
+ struct file_list *fl;
+ struct re_filterfile_spec *b;
+ struct list_entry *tag_name;
+
+ int found_filters = 0;
+ const size_t header_length = strlen(header);
+
+ if (csp->flags & CSP_FLAG_CLIENT_HEADER_PARSING_DONE)
{
- filter_header(csp, header);
+ wanted_filter_type = FT_SERVER_HEADER_TAGGER;
+ multi_action_index = ACTION_MULTI_SERVER_HEADER_TAGGER;
+ }
+ else
+ {
+ wanted_filter_type = FT_CLIENT_HEADER_TAGGER;
+ multi_action_index = ACTION_MULTI_CLIENT_HEADER_TAGGER;
}
- return(JB_ERR_OK);
-}
-/*********************************************************************
- *
- * Function : filter_client_header
- *
- * Description : Checks if client header filtering is enabled.
- * If it is, filter_header is called to do the work.
- *
- * 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.
- *
- * Returns : JB_ERR_OK on success and always succeeds
- *
- *********************************************************************/
-jb_err filter_client_header(struct client_state *csp, char **header)
-{
- if (csp->action->flags & ACTION_FILTER_CLIENT_HEADERS)
+ /* Check if there are any filters */
+ for (i = 0; i < MAX_AF_FILES; i++)
+ {
+ fl = csp->rlist[i];
+ if (NULL != fl)
+ {
+ if (NULL != fl->f)
+ {
+ found_filters = 1;
+ break;
+ }
+ }
+ }
+
+ if (0 == found_filters)
{
- filter_header(csp, header);
+ log_error(LOG_LEVEL_ERROR, "Unable to get current state of regex tagging.");
+ return(JB_ERR_OK);
}
- return(JB_ERR_OK);
+
+ for (i = 0; i < MAX_AF_FILES; i++)
+ {
+ fl = csp->rlist[i];
+ if ((NULL == fl) || (NULL == fl->f))
+ {
+ /*
+ * Either there are no filter files
+ * left, or this filter file just
+ * contains no valid filters.
+ *
+ * Continue to be sure we don't miss
+ * valid filter files that are chained
+ * after empty or invalid ones.
+ */
+ continue;
+ }
+
+ /* For all filters, */
+ for (b = fl->f; b; b = b->next)
+ {
+ if (b->type != wanted_filter_type)
+ {
+ /* skip the ones we don't care about, */
+ continue;
+ }
+ /* leaving only taggers that could apply, of which we use the ones, */
+ for (tag_name = csp->action->multi[multi_action_index]->first;
+ NULL != tag_name; tag_name = tag_name->next)
+ {
+ /* that do apply, and */
+ if (strcmp(b->name, tag_name->str) == 0)
+ {
+ char *modified_tag = NULL;
+ char *tag = header;
+ size_t size = header_length;
+ pcrs_job *joblist = b->joblist;
+
+ if (b->dynamic) joblist = compile_dynamic_pcrs_job_list(csp, b);
+
+ if (NULL == joblist)
+ {
+ log_error(LOG_LEVEL_RE_FILTER,
+ "Tagger %s has empty joblist. Nothing to do.", b->name);
+ continue;
+ }
+
+ /* execute their pcrs_joblist on the header. */
+ for (job = joblist; NULL != job; job = job->next)
+ {
+ const int hits = pcrs_execute(job, tag, size, &modified_tag, &size);
+
+ if (0 < hits)
+ {
+ /* Success, continue with the modified version. */
+ if (tag != header)
+ {
+ freez(tag);
+ }
+ tag = modified_tag;
+ }
+ else
+ {
+ /* Tagger doesn't match */
+ if (0 > hits)
+ {
+ /* Regex failure, log it but continue anyway. */
+ log_error(LOG_LEVEL_ERROR,
+ "Problems with tagger \'%s\' and header \'%s\': %s",
+ b->name, *header, pcrs_strerror(hits));
+ }
+ freez(modified_tag);
+ }
+ }
+
+ if (b->dynamic) pcrs_free_joblist(joblist);
+
+ /* If this tagger matched */
+ if (tag != header)
+ {
+ if (0 == size)
+ {
+ /*
+ * There is to technical limitation which makes
+ * it impossible to use empty tags, but I assume
+ * no one would do it intentionally.
+ */
+ freez(tag);
+ log_error(LOG_LEVEL_INFO,
+ "Tagger \'%s\' created an empty tag. Ignored.",
+ b->name);
+ continue;
+ }
+
+ if (!list_contains_item(csp->tags, tag))
+ {
+ if (JB_ERR_OK != enlist(csp->tags, tag))
+ {
+ log_error(LOG_LEVEL_ERROR,
+ "Insufficient memory to add tag \'%s\', "
+ "based on tagger \'%s\' and header \'%s\'",
+ tag, b->name, *header);
+ }
+ else
+ {
+ char *action_message;
+ /*
+ * update the action bits right away, to make
+ * tagging based on tags set by earlier taggers
+ * of the same kind possible.
+ */
+ if (update_action_bits_for_tag(csp, tag))
+ {
+ action_message = "Action bits updated accordingly.";
+ }
+ else
+ {
+ action_message = "No action bits update necessary.";
+ }
+
+ log_error(LOG_LEVEL_HEADER,
+ "Tagger \'%s\' added tag \'%s\'. %s",
+ b->name, tag, action_message);
+ }
+ }
+ else
+ {
+ /* XXX: Is this log-worthy? */
+ log_error(LOG_LEVEL_HEADER,
+ "Tagger \'%s\' didn't add tag \'%s\'. "
+ "Tag already present", b->name, tag);
+ }
+ freez(tag);
+ } /* if the tagger matched */
+ } /* if the tagger applies */
+ } /* for every tagger that could apply */
+ } /* for all filters */
+ } /* for all filter files */
+
+ return JB_ERR_OK;
}
+/* here begins the family of parser functions that reformat header lines */
+
/*********************************************************************
*
* Function : filter_header
*
* Description : Executes all text substitutions from all applying
- * +filter actions on the header.
+ * +(server|client)-header-filter actions on the header.
* Most of the code was copied from pcrs_filter_response,
* including the rather short variable names
*
struct list_entry *filtername;
int i, found_filters = 0;
+ int wanted_filter_type;
+ int multi_action_index;
+
+ if (csp->flags & CSP_FLAG_NO_FILTERING)
+ {
+ return JB_ERR_OK;
+ }
+
+ if (csp->flags & CSP_FLAG_CLIENT_HEADER_PARSING_DONE)
+ {
+ wanted_filter_type = FT_SERVER_HEADER_FILTER;
+ multi_action_index = ACTION_MULTI_SERVER_HEADER_FILTER;
+ }
+ else
+ {
+ wanted_filter_type = FT_CLIENT_HEADER_FILTER;
+ multi_action_index = ACTION_MULTI_CLIENT_HEADER_FILTER;
+ }
/*
* Need to check the set of re_filterfiles...
*/
for (b = fl->f; b; b = b->next)
{
- for (filtername = csp->action->multi[ACTION_MULTI_FILTER]->first;
+ if (b->type != wanted_filter_type)
+ {
+ /* Skip other filter types */
+ continue;
+ }
+
+ for (filtername = csp->action->multi[multi_action_index]->first;
filtername ; filtername = filtername->next)
{
if (strcmp(b->name, filtername->str) == 0)
{
int current_hits = 0;
+ pcrs_job *joblist = b->joblist;
- if ( NULL == b->joblist )
+ if (b->dynamic) joblist = compile_dynamic_pcrs_job_list(csp, b);
+
+ if (NULL == joblist)
{
log_error(LOG_LEVEL_RE_FILTER, "Filter %s has empty joblist. Nothing to do.", b->name);
continue;
}
- log_error(LOG_LEVEL_RE_FILTER, "re_filtering %s (size %d) with filter %s...",
+ log_error(LOG_LEVEL_RE_FILTER, "filtering \'%s\' (size %d) with \'%s\' ...",
*header, size, b->name);
/* Apply all jobs from the joblist */
- for (job = b->joblist; NULL != job; job = job->next)
+ for (job = joblist; NULL != job; job = job->next)
{
matches = pcrs_execute(job, *header, size, &newheader, &size);
if ( 0 < matches )
}
}
}
- log_error(LOG_LEVEL_RE_FILTER, " ...produced %d hits (new size %d).", current_hits, size);
+
+ if (b->dynamic) pcrs_free_joblist(joblist);
+
+ log_error(LOG_LEVEL_RE_FILTER, "... produced %d hits (new size %d).", current_hits, size);
hits += current_hits;
}
}
}
+/*********************************************************************
+ *
+ * Function : connection
+ *
+ * Description : Makes sure that the value of the Connection: header
+ * is "close" and signals connection_close_adder
+ * to do nothing.
+ *
+ * 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.
+ *
+ * Returns : JB_ERR_OK on success, or
+ * JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+jb_err connection(struct client_state *csp, char **header)
+{
+ char *old_header = *header;
+
+ /* Do we have a 'Connection: close' header? */
+ if (strcmpic(*header, "Connection: close"))
+ {
+ /* No, create one */
+ *header = strdup("Connection: close");
+ if (header == NULL)
+ {
+ return JB_ERR_MEMORY;
+ }
+ log_error(LOG_LEVEL_HEADER, "Replaced: \'%s\' with \'%s\'", old_header, *header);
+ freez(old_header);
+ }
+
+ /* Signal connection_close_adder() to return early. */
+ if (csp->flags & CSP_FLAG_CLIENT_HEADER_PARSING_DONE)
+ {
+ csp->flags |= CSP_FLAG_SERVER_CONNECTION_CLOSE_SET;
+ }
+ else
+ {
+ csp->flags |= CSP_FLAG_CLIENT_CONNECTION_CLOSE_SET;
+ }
+
+ return JB_ERR_OK;
+}
+
+
/*********************************************************************
*
* Function : crumble
jb_err crunch_server_header(struct client_state *csp, char **header)
{
const char *crunch_pattern;
- /*Is there a header to crunch*/
+ /* Do we feel like crunching? */
if ((csp->action->flags & ACTION_CRUNCH_SERVER_HEADER))
{
crunch_pattern = csp->action->string[ACTION_STRING_SERVER_HEADER];
- /*Is the current header the lucky one?*/
+ /* Is the current header the lucky one? */
if (strstr(*header, crunch_pattern))
{
log_error(LOG_LEVEL_HEADER, "Crunching server header: %s (contains: %s)", *header, crunch_pattern);
jb_err server_content_type(struct client_state *csp, char **header)
{
/* Remove header if it isn't the first Content-Type header */
- if(csp->content_type && (csp->content_type != CT_TABOO))
+ if ((csp->content_type & CT_DECLARED))
{
/*
* Another, slightly slower, way to see if
return JB_ERR_OK;
}
+ /*
+ * Signal that the Content-Type has been set.
+ */
+ csp->content_type |= CT_DECLARED;
+
if (!(csp->content_type & CT_TABOO))
{
if ((strstr(*header, " text/") && !strstr(*header, "plain"))
{
csp->content_type |= CT_JPEG;
}
- else
- {
- csp->content_type = 0;
- }
- }
- /*
- * Are we enabling text mode by force?
- */
- if (csp->action->flags & ACTION_FORCE_TEXT_MODE)
- {
- /*
- * Do we really have to?
- */
- if (csp->content_type & CT_TEXT)
- {
- log_error(LOG_LEVEL_HEADER, "Text mode is already enabled.");
- }
- else
- {
- csp->content_type |= CT_TEXT;
- log_error(LOG_LEVEL_HEADER, "Text mode enabled by force. Take cover!");
- }
}
+
/*
* Are we messing with the content type?
*/
"Enable force-text-mode if you know what you're doing.", *header);
}
}
+
return JB_ERR_OK;
}
jb_err server_content_encoding(struct client_state *csp, char **header)
{
#ifdef FEATURE_ZLIB
- /* XXX: Why would we modify the content if it was taboo? */
- if ((csp->flags & CSP_FLAG_MODIFIED) && !(csp->content_type & CT_TABOO))
+ if ((csp->flags & CSP_FLAG_MODIFIED)
+ && (csp->content_type & (CT_GZIP | CT_DEFLATE)))
{
/*
* We successfully decompressed the content,
* Body is compressed, turn off pcrs and gif filtering.
*/
csp->content_type |= CT_TABOO;
+
+ /*
+ * Log a warning if the user expects the content to be filtered.
+ */
+ if ((csp->rlist != NULL) &&
+ (!list_is_empty(csp->action->multi[ACTION_MULTI_FILTER])))
+ {
+ log_error(LOG_LEVEL_INFO,
+ "Compressed content detected, content filtering disabled. "
+ "Consider recompiling Privoxy with zlib support or "
+ "enable the prevent-compression action.");
+ }
}
-#endif /* !defined(FEATURE_ZLIB) */
+#endif /* defined(FEATURE_ZLIB) */
return JB_ERR_OK;
jb_err server_content_length(struct client_state *csp, char **header)
{
const size_t max_header_length = 80;
- if (csp->content_length != 0) /* Content length could have been modified */
+
+ /* Regenerate header if the content was modified. */
+ if (csp->flags & CSP_FLAG_MODIFIED)
{
- /*
- * XXX: Shouldn't we check if csp->content_length
- * is different than the original value?
- */
freez(*header);
*header = (char *) zalloc(max_header_length);
if (*header == NULL)
}
else if (0 == strcmpic(newval, "randomize"))
{
+ const char *header_time = *header + sizeof("Last-Modified:");
+
log_error(LOG_LEVEL_HEADER, "Randomizing: %s", *header);
now = time(NULL);
#ifdef HAVE_GMTIME_R
#else
timeptr = gmtime(&now);
#endif
- if ((timeptr = parse_header_time(*header, &last_modified)) == NULL)
+ if (JB_ERR_OK != parse_header_time(header_time, &last_modified))
{
- log_error(LOG_LEVEL_HEADER, "Couldn't parse: %s (crunching!)", *header);
+ log_error(LOG_LEVEL_HEADER, "Couldn't parse: %s in %s (crunching!)", header_time, *header);
freez(*header);
}
else
return JB_ERR_OK;
}
+
/*********************************************************************
*
* Function : client_accept_encoding
jb_err crunch_client_header(struct client_state *csp, char **header)
{
const char *crunch_pattern;
- /*Is there a header to crunch*/
-
+
+ /* Do we feel like crunching? */
if ((csp->action->flags & ACTION_CRUNCH_CLIENT_HEADER))
{
crunch_pattern = csp->action->string[ACTION_STRING_CLIENT_HEADER];
- /*Is the current header the lucky one?*/
+ /* Is the current header the lucky one? */
if (strstr(*header, crunch_pattern))
{
log_error(LOG_LEVEL_HEADER, "Crunching client header: %s (contains: %s)", *header, crunch_pattern);
*
* Function : client_send_cookie
*
- * Description : Handle the "cookie" header properly. Called from `sed'.
- * If cookie is accepted, add it to the cookie_list,
- * else we crunch it. Mmmmmmmmmmm ... cookie ......
+ * Description : Crunches the "cookie" header if necessary.
+ * Called from `sed'.
+ *
+ * XXX: Stupid name, doesn't send squat.
*
* Parameters :
* 1 : csp = Current client state (buffers, headers, etc...)
*********************************************************************/
jb_err client_send_cookie(struct client_state *csp, char **header)
{
- jb_err result = JB_ERR_OK;
-
- if ((csp->action->flags & ACTION_NO_COOKIE_READ) == 0)
+ if (csp->action->flags & ACTION_NO_COOKIE_READ)
{
- /* strlen("cookie: ") == 8 */
- result = enlist(csp->cookie_list, *header + 8);
- }
- else
- {
- log_error(LOG_LEVEL_HEADER, "Crunched outgoing cookie -- yum!");
+ log_error(LOG_LEVEL_HEADER, "Crunched outgoing cookie: %s", *header);
+ freez(*header);
}
- /*
- * Always remove the cookie here. The cookie header
- * will be sent at the end of the header.
- */
- freez(*header);
-
- return result;
+ return JB_ERR_OK;
}
*********************************************************************/
jb_err client_max_forwards(struct client_state *csp, char **header)
{
- unsigned int max_forwards;
+ int max_forwards;
if ((0 == strcmpic(csp->http->gpc, "trace")) ||
(0 == strcmpic(csp->http->gpc, "options")))
{
- if (1 == sscanf(*header, "Max-Forwards: %u", &max_forwards))
+ assert(*(*header+12) == ':');
+ if (1 == sscanf(*header+12, ": %u", &max_forwards))
{
if (max_forwards > 0)
{
snprintf(*header, strlen(*header)+1, "Max-Forwards: %u", --max_forwards);
- log_error(LOG_LEVEL_HEADER, "Max-Forwards header for %s request replaced with: %s",
- csp->http->gpc, *header);
+ log_error(LOG_LEVEL_HEADER, "Max-Forwards value for %s request reduced to %u.",
+ csp->http->gpc, max_forwards);
+ }
+ else if (max_forwards < 0)
+ {
+ log_error(LOG_LEVEL_ERROR, "Crunching invalid header: %s", *header);
+ freez(*header);
}
else
{
/*
- * direct_response() which was called earlier
- * in chat() should prevent that we ever get
- * here.
+ * Not supposed to be reached. direct_response() which
+ * was already called earlier in chat() should have
+ * intercepted the request.
*/
log_error(LOG_LEVEL_ERROR,
"Non-intercepted %s request with Max-Forwards zero!", csp->http->gpc);
csp->http->hostport, csp->http->host, csp->http->port);
}
+ /* Signal client_host_adder() to return right away */
+ csp->flags |= CSP_FLAG_HOST_HEADER_IS_SET;
+
return JB_ERR_OK;
}
}
else /* add random value */
{
- if ((timeptr = parse_header_time(*header, &tm)) == NULL)
+ const char *header_time = *header + sizeof("If-Modified-Since:");
+
+ if (JB_ERR_OK != parse_header_time(header_time, &tm))
{
- log_error(LOG_LEVEL_HEADER, "Couldn't parse: %s (crunching!)", *header);
+ log_error(LOG_LEVEL_HEADER, "Couldn't parse: %s in %s (crunching!)", header_time, *header);
freez(*header);
}
else
}
else
{
- csp->content_type = CT_TABOO;
- csp->action->flags &= ~ACTION_FILTER_SERVER_HEADERS;
- csp->action->flags &= ~ACTION_FILTER_CLIENT_HEADERS;
+ csp->content_type = CT_TABOO; /* XXX: This hack shouldn't be necessary */
+ csp->flags |= CSP_FLAG_NO_FILTERING;
log_error(LOG_LEVEL_HEADER, "Accepted the client's request to fetch without filtering.");
}
log_error(LOG_LEVEL_HEADER, "Crunching %s", *header);
char *p;
jb_err err;
+ if (csp->flags & CSP_FLAG_HOST_HEADER_IS_SET)
+ {
+ /* Header already set by the client, nothing to do. */
+ return JB_ERR_OK;
+ }
+
if ( !csp->http->hostport || !*(csp->http->hostport))
{
+ /* XXX: When does this happen and why is it OK? */
+ log_error(LOG_LEVEL_INFO, "Weirdness in client_host_adder detected and ignored.");
return JB_ERR_OK;
}
p = csp->http->hostport;
}
+ /* XXX: Just add it, we already made sure that it will be unique */
log_error(LOG_LEVEL_HEADER, "addh-unique: Host: %s", p);
err = enlist_unique_header(csp->headers, "Host", p);
return err;
*
* Function : client_cookie_adder
*
- * Description : Used in the add_client_headers list. Called from `sed'.
+ * Description : Used in the add_client_headers list to add "wafers".
+ * Called from `sed'.
*
* Parameters :
* 1 : csp = Current client state (buffers, headers, etc...)
*********************************************************************/
jb_err client_cookie_adder(struct client_state *csp)
{
- struct list_entry *lst;
char *tmp;
- struct list_entry *list1 = csp->cookie_list->first;
- struct list_entry *list2 = csp->action->multi[ACTION_MULTI_WAFER]->first;
- int first_cookie = 1;
+ struct list_entry *wafer;
+ struct list_entry *wafer_list = csp->action->multi[ACTION_MULTI_WAFER]->first;
jb_err err;
- if ((list1 == NULL) && (list2 == NULL))
+ if (NULL == wafer_list)
{
/* Nothing to do */
return JB_ERR_OK;
tmp = strdup("Cookie: ");
- for (lst = list1; lst ; lst = lst->next)
+ for (wafer = wafer_list; (NULL != tmp) && (NULL != wafer); wafer = wafer->next)
{
- if (first_cookie)
- {
- first_cookie = 0;
- }
- else
+ if (wafer != wafer_list)
{
+ /* As this isn't the first wafer, we need a delimiter. */
string_append(&tmp, "; ");
}
- string_append(&tmp, lst->str);
- }
-
- for (lst = list2; lst ; lst = lst->next)
- {
- if (first_cookie)
- {
- first_cookie = 0;
- }
- else
- {
- string_append(&tmp, "; ");
- }
- string_join(&tmp, cookie_encode(lst->str));
+ string_join(&tmp, cookie_encode(wafer->str));
}
if (tmp == NULL)
*********************************************************************/
jb_err client_accept_encoding_adder(struct client_state *csp)
{
+ assert(0); /* Not in use */
+
if ( ((csp->action->flags & ACTION_NO_COMPRESSION) != 0)
&& (!strcmpic(csp->http->ver, "HTTP/1.1")) )
{
*
* Function : connection_close_adder
*
- * Description : Adds a "Connection: close" header to csp->headers
- * as a temporary fix for the needed but missing HTTP/1.1
- * support. Called from `sed'.
+ * Description : "Temporary" fix for the needed but missing HTTP/1.1
+ * support. Adds a "Connection: close" header to csp->headers
+ * unless the header was already present. Called from `sed'.
+ *
* FIXME: This whole function shouldn't be neccessary!
*
* Parameters :
*********************************************************************/
jb_err connection_close_adder(struct client_state *csp)
{
+ const unsigned int flags = csp->flags;
+
+ /*
+ * Return right away if
+ *
+ * - we're parsing server headers and the server header
+ * "Connection: close" is already set, or if
+ *
+ * - we're parsing client headers and the client header
+ * "Connection: close" is already set.
+ */
+ if ((flags & CSP_FLAG_CLIENT_HEADER_PARSING_DONE
+ && flags & CSP_FLAG_SERVER_CONNECTION_CLOSE_SET)
+ ||(!(flags & CSP_FLAG_CLIENT_HEADER_PARSING_DONE)
+ && flags & CSP_FLAG_CLIENT_CONNECTION_CLOSE_SET))
+ {
+ return JB_ERR_OK;
+ }
+
log_error(LOG_LEVEL_HEADER, "Adding: Connection: close");
+
return enlist(csp->headers, "Connection: close");
}
time_t now;
time_t cookie_time;
struct tm tm_now;
- struct tm tm_cookie;
time(&now);
#ifdef FEATURE_COOKIE_JAR
tm_now = *localtime_r(&now, &tm_now);
#elif FEATURE_PTHREAD
pthread_mutex_lock(&localtime_mutex);
- tm_now = *localtime (&now);
+ tm_now = *localtime (&now);
pthread_mutex_unlock(&localtime_mutex);
#else
- tm_now = *localtime (&now);
+ tm_now = *localtime (&now);
#endif
strftime(tempbuf, BUFFER_SIZE-6, "%b %d %H:%M:%S ", &tm_now);
if ((csp->action->flags & ACTION_NO_COOKIE_SET) != 0)
{
- log_error(LOG_LEVEL_HEADER, "Crunched incoming cookie -- yum!");
- return crumble(csp, header);
+ log_error(LOG_LEVEL_HEADER, "Crunching incoming cookie: %s", *header);
+ freez(*header);
}
else if ((csp->action->flags & ACTION_NO_COOKIE_KEEP) != 0)
{
* if the cookie is still valid, if yes,
* rewrite it to a session cookie.
*/
- if (strncmpic(cur_tag, "expires=", 8) == 0)
+ if ((strncmpic(cur_tag, "expires=", 8) == 0) && *(cur_tag + 8))
{
- char *match;
- /*
- * Try the valid time formats we know about.
- *
- * XXX: Maybe the log messages should be removed
- * for the next stable release. They just exist to
- * see which time format gets the most hits and
- * should be checked for first.
- */
- if (NULL != (match = strptime(cur_tag, "expires=%a, %e-%b-%y %H:%M:%S ", &tm_cookie)))
- {
- log_error(LOG_LEVEL_HEADER,
- "cookie \'%s\' send by %s appears to be using time format 1.",
- csp->http->url, *header);
- }
- else if (NULL != (match = strptime(cur_tag, "expires=%A, %e-%b-%Y %H:%M:%S ", &tm_cookie)))
- {
- log_error(LOG_LEVEL_HEADER,
- "cookie \'%s\' send by %s appears to be using time format 2.",
- csp->http->url, *header);
+ char *expiration_date = cur_tag + 8; /* Skip "[Ee]xpires=" */
- }
- else if (NULL != (match = strptime(cur_tag, "expires=%a, %e-%b-%Y %H:%M:%S ", &tm_cookie)))
- {
- log_error(LOG_LEVEL_HEADER,
- "cookie \'%s\' send by %s appears to be using time format 3.",
- csp->http->url, *header);
- }
-
- /* Did any of them match? */
- if (NULL == match)
+ /* Did we detect the date properly? */
+ if (JB_ERR_OK != parse_header_time(expiration_date, &cookie_time))
{
/*
* Nope, treat it as if it was still valid.
* XXX: Should we remove the whole cookie instead?
*/
log_error(LOG_LEVEL_ERROR,
- "Can't parse %s. Unsupported time format?", cur_tag);
+ "Can't parse \'%s\', send by %s. Unsupported time format?", cur_tag, csp->http->url);
memmove(cur_tag, next_tag, strlen(next_tag) + 1);
changed = 1;
}
* anyway, which in many cases will be shorter
* than a browser session.
*/
- cookie_time = timegm(&tm_cookie);
if (cookie_time - now < 0)
{
log_error(LOG_LEVEL_HEADER,
}
else
{
- log_error(LOG_LEVEL_HEADER,
- "Cookie \'%s\' is still valid and has to be rewritten.", *header);
-
/*
- * Delete the tag by copying the rest of the string over it.
+ * Still valid, delete expiration date by copying
+ * the rest of the string over it.
+ *
* (Note that we cannot just use "strcpy(cur_tag, next_tag)",
* since the behaviour of strcpy is undefined for overlapping
* strings.)
*
* Function : parse_header_time
*
- * Description : Transforms time inside a HTTP header into
- * the usual time format.
+ * Description : Parses time formats used in HTTP header strings
+ * to get the numerical respresentation.
*
* Parameters :
- * 1 : header = header to parse
- * 2 : tm = storage for the resulting time in seconds
+ * 1 : header_time = HTTP header time as string.
+ * 2 : result = storage for header_time in seconds
*
- * Returns : Time struct containing the header time, or
- * NULL in case of a parsing problems.
+ * Returns : JB_ERR_OK if the time format was recognized, or
+ * JB_ERR_PARSE otherwise.
*
*********************************************************************/
-struct tm *parse_header_time(char *header, time_t *tm) {
-
- char * timestring;
+jb_err parse_header_time(const char *header_time, time_t *result)
+{
struct tm gmt;
- struct tm * timeptr;
/*
- * Initializing gmt to prevent time zone offsets.
+ * Zero out gmt to prevent time zone offsets.
*
* While this is only necessary on some platforms
* (mingw32 for example), I don't know how to
* detect these automatically and doing it everywhere
* shouldn't hurt.
*/
- time(tm);
-#ifdef HAVE_LOCALTIME_R
- gmt = *localtime_r(tm, &gmt);
-#elif FEATURE_PTHREAD
- pthread_mutex_lock(&localtime_mutex);
- gmt = *localtime(tm);
- pthread_mutex_unlock(&localtime_mutex);
-#else
- gmt = *localtime(tm);
-#endif
-
- /* Skipping header name */
- timestring = strstr(header, ": ");
- if (strptime(timestring, ": %a, %d %b %Y %H:%M:%S", &gmt) == NULL)
- {
- timeptr = NULL;
- }
- else
+ memset(&gmt, 0, sizeof(gmt));
+
+ /* Tue, 02 Jun 2037 20:00:00 */
+ if ((NULL == strptime(header_time, "%a, %d %b %Y %H:%M:%S", &gmt))
+ /* Tue, 02-Jun-2037 20:00:00 */
+ && (NULL == strptime(header_time, "%a, %d-%b-%Y %H:%M:%S", &gmt))
+ /* Tue, 02-Jun-37 20:00:00 */
+ && (NULL == strptime(header_time, "%a, %d-%b-%y %H:%M:%S", &gmt))
+ /* Tuesday, 02-Jun-2037 20:00:00 */
+ && (NULL == strptime(header_time, "%A, %d-%b-%Y %H:%M:%S", &gmt))
+ /* Tuesday Jun 02 20:00:00 2037 */
+ && (NULL == strptime(header_time, "%A %b %d %H:%M:%S %Y", &gmt)))
{
- *tm = timegm(&gmt);
- timeptr = &gmt;
+ return JB_ERR_PARSE;
}
- return(timeptr);
+
+ *result = timegm(&gmt);
+
+ return JB_ERR_OK;
}