X-Git-Url: http://www.privoxy.org/gitweb/?p=privoxy.git;a=blobdiff_plain;f=parsers.c;h=9fe2da77f6b1c5126753f1717647a08975955689;hp=4dd2a5fa46c9ce5b5cbab4595d0039b96e794a96;hb=d26129baa47eb9e558d53f463ad18269dfc99607;hpb=5e758f724e63e91c6aa7ff346dc74812e19c24aa diff --git a/parsers.c b/parsers.c index 4dd2a5fa..9fe2da77 100644 --- a/parsers.c +++ b/parsers.c @@ -1,11 +1,11 @@ -const char parsers_rcs[] = "$Id: parsers.c,v 1.241 2012/03/04 11:47:54 fabiankeil Exp $"; +const char parsers_rcs[] = "$Id: parsers.c,v 1.302 2015/12/27 12:54:12 fabiankeil Exp $"; /********************************************************************* * * File : $Source: /cvsroot/ijbswa/current/parsers.c,v $ * * Purpose : Declares functions to parse/crunch headers and pages. * - * Copyright : Written by and Copyright (C) 2001-2011 the + * Copyright : Written by and Copyright (C) 2001-2014 the * Privoxy team. http://www.privoxy.org/ * * Based on the Internet Junkbuster originally written @@ -96,6 +96,7 @@ static char *get_header_line(struct iob *iob); static jb_err scan_headers(struct client_state *csp); static jb_err header_tagger(struct client_state *csp, char *header); static jb_err parse_header_time(const char *header_time, time_t *result); +static jb_err parse_time_header(const char *header, time_t *result); static jb_err crumble (struct client_state *csp, char **header); static jb_err filter_header (struct client_state *csp, char **header); @@ -116,6 +117,7 @@ static jb_err client_if_none_match (struct client_state *csp, char **header static jb_err crunch_client_header (struct client_state *csp, char **header); static jb_err client_x_filter (struct client_state *csp, char **header); static jb_err client_range (struct client_state *csp, char **header); +static jb_err client_expect (struct client_state *csp, char **header); static jb_err server_set_cookie (struct client_state *csp, char **header); static jb_err server_connection (struct client_state *csp, char **header); static jb_err server_content_type (struct client_state *csp, char **header); @@ -137,6 +139,7 @@ static jb_err server_keep_alive(struct client_state *csp, char **header); static jb_err server_proxy_connection(struct client_state *csp, char **header); static jb_err client_keep_alive(struct client_state *csp, char **header); static jb_err client_save_content_length(struct client_state *csp, char **header); +static jb_err client_proxy_connection(struct client_state *csp, char **header); #endif /* def FEATURE_CONNECTION_KEEP_ALIVE */ static jb_err client_host_adder (struct client_state *csp); @@ -147,6 +150,7 @@ static jb_err server_connection_adder(struct client_state *csp); #ifdef FEATURE_CONNECTION_KEEP_ALIVE static jb_err server_proxy_connection_adder(struct client_state *csp); #endif /* def FEATURE_CONNECTION_KEEP_ALIVE */ +static jb_err proxy_authentication(struct client_state *csp, char **header); static jb_err create_forged_referrer(char **header, const char *hostport); static jb_err create_fake_referrer(char **header, const char *fake_referrer); @@ -184,11 +188,12 @@ static const struct parsers client_patterns[] = { #ifdef FEATURE_CONNECTION_KEEP_ALIVE { "Keep-Alive:", 11, client_keep_alive }, { "Content-Length:", 15, client_save_content_length }, + { "Proxy-Connection:", 17, client_proxy_connection }, #else { "Keep-Alive:", 11, crumble }, + { "Proxy-Connection:", 17, crumble }, #endif { "connection:", 11, client_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 }, @@ -196,6 +201,11 @@ static const struct parsers client_patterns[] = { { "Request-Range:", 14, client_range }, { "If-Range:", 9, client_range }, { "X-Filter:", 9, client_x_filter }, + { "Proxy-Authorization:", 20, proxy_authentication }, +#if 0 + { "Transfer-Encoding:", 18, client_transfer_encoding }, +#endif + { "Expect:", 7, client_expect }, { "*", 0, crunch_client_header }, { "*", 0, filter_header }, { NULL, 0, NULL } @@ -218,6 +228,7 @@ static const struct parsers server_patterns[] = { { "Transfer-Encoding:", 18, server_transfer_coding }, { "content-disposition:", 20, server_content_disposition }, { "Last-Modified:", 14, server_last_modified }, + { "Proxy-Authenticate:", 19, proxy_authentication }, { "*", 0, crunch_server_header }, { "*", 0, filter_header }, { NULL, 0, NULL } @@ -280,21 +291,21 @@ long flush_socket(jb_socket fd, struct iob *iob) * * Function : add_to_iob * - * Description : Add content to the buffered page, expanding the + * Description : Add content to the buffer, expanding the * buffer if necessary. * * Parameters : - * 1 : csp = Current client state (buffers, headers, etc...) - * 2 : buf = holds the content to be added to the page - * 3 : n = number of bytes to be added + * 1 : iob = Destination buffer. + * 2 : buffer_limit = Limit to which the destination may grow + * 3 : src = holds the content to be added + * 4 : n = number of bytes to be added * * Returns : JB_ERR_OK on success, JB_ERR_MEMORY if out-of-memory * or buffer limit reached. * *********************************************************************/ -jb_err add_to_iob(struct client_state *csp, char *buf, long n) +jb_err add_to_iob(struct iob *iob, const size_t buffer_limit, char *src, long n) { - struct iob *iob = csp->iob; size_t used, offset, need; char *p; @@ -308,24 +319,24 @@ jb_err add_to_iob(struct client_state *csp, char *buf, long n) * If the buffer can't hold the new data, extend it first. * Use the next power of two if possible, else use the actual need. */ - if (need > csp->config->buffer_limit) + if (need > buffer_limit) { log_error(LOG_LEVEL_INFO, "Buffer limit reached while extending the buffer (iob). Needed: %d. Limit: %d", - need, csp->config->buffer_limit); + need, buffer_limit); return JB_ERR_MEMORY; } if (need > iob->size) { - size_t want = csp->iob->size ? csp->iob->size : 512; + size_t want = iob->size ? iob->size : 512; while (want <= need) { want *= 2; } - if (want <= csp->config->buffer_limit && NULL != (p = (char *)realloc(iob->buf, want))) + if (want <= buffer_limit && NULL != (p = (char *)realloc(iob->buf, want))) { iob->size = want; } @@ -346,7 +357,7 @@ jb_err add_to_iob(struct client_state *csp, char *buf, long n) } /* copy the new data into the iob buffer */ - memcpy(iob->eod, buf, (size_t)n); + memcpy(iob->eod, src, (size_t)n); /* point to the end of the data */ iob->eod += n; @@ -359,6 +370,27 @@ jb_err add_to_iob(struct client_state *csp, char *buf, long n) } +/********************************************************************* + * + * Function : clear_iob + * + * Description : Frees the memory allocated for an I/O buffer and + * resets the structure. + * + * Parameters : + * 1 : iob = I/O buffer to clear. + * + * Returns : JB_ERR_OK on success, JB_ERR_MEMORY if out-of-memory + * or buffer limit reached. + * + *********************************************************************/ +void clear_iob(struct iob *iob) +{ + free(iob->buf); + memset(iob, '\0', sizeof(*iob));; +} + + #ifdef FEATURE_ZLIB /********************************************************************* * @@ -404,7 +436,9 @@ jb_err decompress_iob(struct client_state *csp) * This is to protect the parsing of gzipped data, * but it should(?) be valid for deflated data also. */ - log_error(LOG_LEVEL_ERROR, "Buffer too small decompressing iob"); + log_error(LOG_LEVEL_ERROR, + "Insufficient data to start decompression. Bytes in buffer: %d", + csp->iob->eod - csp->iob->cur); return JB_ERR_COMPRESS; } @@ -510,11 +544,6 @@ jb_err decompress_iob(struct client_state *csp) } else if (csp->content_type & CT_DEFLATE) { - /* - * XXX: The debug level should be lowered - * before the next stable release. - */ - 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 @@ -722,44 +751,16 @@ jb_err decompress_iob(struct client_state *csp) #endif /* defined(FEATURE_ZLIB) */ -/********************************************************************* - * - * Function : string_move - * - * Description : memmove wrapper to move the last part of a string - * towards the beginning, overwriting the part in - * the middle. strlcpy() can't be used here as the - * strings overlap. - * - * Parameters : - * 1 : dst = Destination to overwrite - * 2 : src = Source to move. - * - * Returns : N/A - * - *********************************************************************/ -static void string_move(char *dst, char *src) -{ - assert(dst < src); - - /* +1 to copy the terminating nul as well. */ - memmove(dst, src, strlen(src)+1); -} - - /********************************************************************* * * Function : normalize_lws * - * Description : Reduces unquoted linear white space in headers - * to a single space in accordance with RFC 2616 2.2. + * Description : Reduces unquoted linear whitespace in headers to + * a single space in accordance with RFC 7230 3.2.4. * This simplifies parsing and filtering later on. * - * XXX: Remove log messages before - * the next stable release? - * * Parameters : - * 1 : header = A header with linear white space to reduce. + * 1 : header = A header with linear whitespace to reduce. * * Returns : N/A * @@ -778,7 +779,7 @@ static void normalize_lws(char *header) { q++; } - log_error(LOG_LEVEL_HEADER, "Reducing white space in '%s'", header); + log_error(LOG_LEVEL_HEADER, "Reducing whitespace in '%s'", header); string_move(p+1, q); } @@ -1025,6 +1026,80 @@ static jb_err scan_headers(struct client_state *csp) } +/********************************************************************* + * + * Function : enforce_header_order + * + * Description : Enforces a given header order. + * + * Parameters : + * 1 : headers = List of headers to order. + * 2 : ordered_headers = List of ordered header names. + * + * Returns : N/A + * + *********************************************************************/ +static void enforce_header_order(struct list *headers, const struct list *ordered_headers) +{ + struct list_entry *sorted_header; + struct list new_headers[1]; + struct list_entry *header; + + init_list(new_headers); + + /* The request line is always the first "header" */ + + assert(NULL != headers->first->str); + enlist(new_headers, headers->first->str); + freez(headers->first->str) + + /* Enlist the specified headers in the given order */ + + for (sorted_header = ordered_headers->first; sorted_header != NULL; + sorted_header = sorted_header->next) + { + const size_t sorted_header_length = strlen(sorted_header->str); + for (header = headers->first; header != NULL; header = header->next) + { + /* Header enlisted in previous run? -> ignore */ + if (header->str == NULL) continue; + + if (0 == strncmpic(sorted_header->str, header->str, sorted_header_length) + && (header->str[sorted_header_length] == ':')) + { + log_error(LOG_LEVEL_HEADER, "Enlisting sorted header %s", header->str); + if (JB_ERR_OK != enlist(new_headers, header->str)) + { + log_error(LOG_LEVEL_HEADER, "Failed to enlist %s", header->str); + } + freez(header->str); + } + } + } + + /* Enlist the rest of the headers behind the ordered ones */ + for (header = headers->first; header != NULL; header = header->next) + { + /* Header enlisted in previous run? -> ignore */ + if (header->str == NULL) continue; + + log_error(LOG_LEVEL_HEADER, + "Enlisting left-over header %s", header->str); + if (JB_ERR_OK != enlist(new_headers, header->str)) + { + log_error(LOG_LEVEL_HEADER, "Failed to enlist %s", header->str); + } + freez(header->str); + } + + list_remove_all(headers); + list_duplicate(headers, new_headers); + list_remove_all(new_headers); + + return; +} + + /********************************************************************* * * Function : sed @@ -1043,7 +1118,8 @@ static jb_err scan_headers(struct client_state *csp) * server and header filtering. * * Returns : JB_ERR_OK in case off success, or - * JB_ERR_MEMORY on out-of-memory error. + * JB_ERR_MEMORY on some out-of-memory errors, or + * JB_ERR_PARSE in case of fatal parse errors. * *********************************************************************/ jb_err sed(struct client_state *csp, int filter_server_headers) @@ -1054,22 +1130,24 @@ jb_err sed(struct client_state *csp, int filter_server_headers) const add_header_func_ptr *f; jb_err err = JB_ERR_OK; + scan_headers(csp); + if (filter_server_headers) { v = server_patterns; f = add_server_headers; + check_negative_tag_patterns(csp, PATTERN_SPEC_NO_RESPONSE_TAG_PATTERN); } else { v = client_patterns; f = add_client_headers; + check_negative_tag_patterns(csp, PATTERN_SPEC_NO_REQUEST_TAG_PATTERN); } - scan_headers(csp); - - while ((err == JB_ERR_OK) && (v->str != NULL)) + while (v->str != NULL) { - for (p = csp->headers->first; (err == JB_ERR_OK) && (p != NULL); p = p->next) + for (p = csp->headers->first; p != NULL; p = p->next) { /* Header crunch()ed in previous run? -> ignore */ if (p->str == NULL) continue; @@ -1079,6 +1157,10 @@ jb_err sed(struct client_state *csp, int filter_server_headers) (v->len == CHECK_EVERY_HEADER_REMAINING)) { err = v->parser(csp, &(p->str)); + if (err != JB_ERR_OK) + { + return err; + } } } v++; @@ -1091,6 +1173,11 @@ jb_err sed(struct client_state *csp, int filter_server_headers) f++; } + if (!filter_server_headers && !list_is_empty(csp->config->ordered_client_headers)) + { + enforce_header_order(csp->headers, csp->config->ordered_client_headers); + } + return err; } @@ -1157,6 +1244,7 @@ jb_err update_server_headers(struct client_state *csp) log_error(LOG_LEVEL_HEADER, "Content modified with no Content-Length header set. " "Created: %s.", header); + csp->flags |= CSP_FLAG_SERVER_CONTENT_LENGTH_SET; } } #endif /* def FEATURE_CONNECTION_KEEP_ALIVE */ @@ -1196,12 +1284,10 @@ jb_err update_server_headers(struct client_state *csp) *********************************************************************/ static jb_err header_tagger(struct client_state *csp, char *header) { - int wanted_filter_type; + enum filter_type 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; @@ -1218,151 +1304,127 @@ static jb_err header_tagger(struct client_state *csp, char *header) multi_action_index = ACTION_MULTI_CLIENT_HEADER_TAGGER; } - if (filters_available(csp) == FALSE) + if (list_is_empty(csp->action->multi[multi_action_index]) + || filters_available(csp) == FALSE) { - log_error(LOG_LEVEL_ERROR, "Inconsistent configuration: " - "tagging enabled, but no taggers available."); + /* Return early if no taggers apply or if none are available. */ return JB_ERR_OK; } - for (i = 0; i < MAX_AF_FILES; i++) + /* Execute all applying taggers */ + for (tag_name = csp->action->multi[multi_action_index]->first; + NULL != tag_name; tag_name = tag_name->next) { - fl = csp->rlist[i]; - if ((NULL == fl) || (NULL == fl->f)) + char *modified_tag = NULL; + char *tag = header; + size_t size = header_length; + pcrs_job *joblist; + + b = get_filter(csp, tag_name->str, wanted_filter_type); + if (b == NULL) { - /* - * 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) + joblist = b->joblist; + + if (b->dynamic) joblist = compile_dynamic_pcrs_job_list(csp, b); + + if (NULL == joblist) { - if (b->type != wanted_filter_type) + 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) { - /* skip the ones we don't care about, */ - continue; + /* Success, continue with the modified version. */ + if (tag != header) + { + freez(tag); + } + tag = modified_tag; } - /* 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) + else { - /* that do apply, and */ - if (strcmp(b->name, tag_name->str) == 0) + /* Tagger doesn't match */ + if (0 > hits) { - char *modified_tag = NULL; - char *tag = header; - size_t size = header_length; - pcrs_job *joblist = b->joblist; + /* Regex failure, log it but continue anyway. */ + assert(NULL != header); + 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 (b->dynamic) joblist = compile_dynamic_pcrs_job_list(csp, b); + /* If this tagger matched */ + if (tag != header) + { + if (0 == size) + { + /* + * There is no 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 (NULL == joblist) + 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)) { - log_error(LOG_LEVEL_RE_FILTER, - "Tagger %s has empty joblist. Nothing to do.", b->name); - continue; + action_message = "Action bits updated accordingly."; } - - /* execute their pcrs_joblist on the header. */ - for (job = joblist; NULL != job; job = job->next) + else { - 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. */ - assert(NULL != header); - log_error(LOG_LEVEL_ERROR, - "Problems with tagger \'%s\' and header \'%s\': %s", - b->name, *header, pcrs_strerror(hits)); - } - freez(modified_tag); - } + action_message = "No action bits update necessary."; } - 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 */ + 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); + } + } return JB_ERR_OK; } @@ -1397,12 +1459,10 @@ static jb_err filter_header(struct client_state *csp, char **header) char *newheader = NULL; pcrs_job *job; - struct file_list *fl; struct re_filterfile_spec *b; struct list_entry *filtername; - int i; - int wanted_filter_type; + enum filter_type wanted_filter_type; int multi_action_index; if (csp->flags & CSP_FLAG_NO_FILTERING) @@ -1421,97 +1481,72 @@ static jb_err filter_header(struct client_state *csp, char **header) multi_action_index = ACTION_MULTI_CLIENT_HEADER_FILTER; } - if (filters_available(csp) == FALSE) + if (list_is_empty(csp->action->multi[multi_action_index]) + || filters_available(csp) == FALSE) { - log_error(LOG_LEVEL_ERROR, "Inconsistent configuration: " - "header filtering enabled, but no matching filters available."); + /* Return early if no filters apply or if none are available. */ return JB_ERR_OK; } - for (i = 0; i < MAX_AF_FILES; i++) + /* Execute all applying header filters */ + for (filtername = csp->action->multi[multi_action_index]->first; + filtername != NULL; filtername = filtername->next) { - fl = csp->rlist[i]; - if ((NULL == fl) || (NULL == fl->f)) + int current_hits = 0; + pcrs_job *joblist; + + b = get_filter(csp, filtername->str, wanted_filter_type); + if (b == NULL) { - /* - * 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 applying +filter actions, look if a filter by that - * name exists and if yes, execute its pcrs_joblist on the - * buffer. - */ - for (b = fl->f; b; b = b->next) - { - 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 (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; - } + joblist = b->joblist; - log_error(LOG_LEVEL_RE_FILTER, "filtering \'%s\' (size %d) with \'%s\' ...", - *header, size, b->name); + if (b->dynamic) joblist = compile_dynamic_pcrs_job_list(csp, b); - /* Apply all jobs from the joblist */ - for (job = joblist; NULL != job; job = job->next) - { - matches = pcrs_execute(job, *header, size, &newheader, &size); - if ( 0 < matches ) - { - current_hits += matches; - log_error(LOG_LEVEL_HEADER, "Transforming \"%s\" to \"%s\"", *header, newheader); - freez(*header); - *header = newheader; - } - else if ( 0 == matches ) - { - /* Filter doesn't change header */ - freez(newheader); - } - else - { - /* RegEx failure */ - log_error(LOG_LEVEL_ERROR, "Filtering \'%s\' with \'%s\' didn't work out: %s", - *header, b->name, pcrs_strerror(matches)); - if (newheader != NULL) - { - log_error(LOG_LEVEL_ERROR, "Freeing what's left: %s", newheader); - freez(newheader); - } - } - } + if (NULL == joblist) + { + log_error(LOG_LEVEL_RE_FILTER, "Filter %s has empty joblist. Nothing to do.", b->name); + continue; + } - if (b->dynamic) pcrs_free_joblist(joblist); + log_error(LOG_LEVEL_RE_FILTER, "filtering \'%s\' (size %d) with \'%s\' ...", + *header, size, b->name); - log_error(LOG_LEVEL_RE_FILTER, "... produced %d hits (new size %d).", current_hits, size); - hits += current_hits; + /* Apply all jobs from the joblist */ + for (job = joblist; NULL != job; job = job->next) + { + matches = pcrs_execute(job, *header, size, &newheader, &size); + if (0 < matches) + { + current_hits += matches; + log_error(LOG_LEVEL_HEADER, "Transforming \"%s\" to \"%s\"", *header, newheader); + freez(*header); + *header = newheader; + } + else if (0 == matches) + { + /* Filter doesn't change header */ + freez(newheader); + } + else + { + /* RegEx failure */ + log_error(LOG_LEVEL_ERROR, "Filtering \'%s\' with \'%s\' didn't work out: %s", + *header, b->name, pcrs_strerror(matches)); + if (newheader != NULL) + { + log_error(LOG_LEVEL_ERROR, "Freeing what's left: %s", newheader); + freez(newheader); } } } + + 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; } /* @@ -1544,8 +1579,7 @@ static jb_err filter_header(struct client_state *csp, char **header) * 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. + * Returns : JB_ERR_OK on success. * *********************************************************************/ static jb_err server_connection(struct client_state *csp, char **header) @@ -1554,7 +1588,7 @@ static jb_err server_connection(struct client_state *csp, char **header) #ifdef FEATURE_CONNECTION_KEEP_ALIVE && !(csp->flags & CSP_FLAG_SERVER_SOCKET_TAINTED) #endif - ) + ) { #ifdef FEATURE_CONNECTION_KEEP_ALIVE if ((csp->config->feature_flags & RUNTIME_FEATURE_CONNECTION_KEEP_ALIVE)) @@ -1572,11 +1606,7 @@ static jb_err server_connection(struct client_state *csp, char **header) { char *old_header = *header; - *header = strdup("Connection: close"); - if (header == NULL) - { - return JB_ERR_MEMORY; - } + *header = strdup_or_die("Connection: close"); log_error(LOG_LEVEL_HEADER, "Replaced: \'%s\' with \'%s\'", old_header, *header); freez(old_header); } @@ -1635,6 +1665,8 @@ static jb_err server_keep_alive(struct client_state *csp, char **header) csp->flags |= CSP_FLAG_SERVER_KEEP_ALIVE_TIMEOUT_SET; } + freez(*header); + return JB_ERR_OK; } @@ -1663,6 +1695,36 @@ static jb_err server_proxy_connection(struct client_state *csp, char **header) } +/********************************************************************* + * + * Function : proxy_authentication + * + * Description : Removes headers that are relevant for proxy + * authentication unless forwarding them has + * been explicitly requested. + * + * 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. + * + *********************************************************************/ +static jb_err proxy_authentication(struct client_state *csp, char **header) +{ + if ((csp->config->feature_flags & + RUNTIME_FEATURE_FORWARD_PROXY_AUTHENTICATION_HEADERS) == 0) { + log_error(LOG_LEVEL_HEADER, + "Forwarding proxy authentication headers is disabled. Crunching: %s", *header); + freez(*header); + } + return JB_ERR_OK; +} + + /********************************************************************* * * Function : client_keep_alive @@ -1682,7 +1744,7 @@ static jb_err server_proxy_connection(struct client_state *csp, char **header) static jb_err client_keep_alive(struct client_state *csp, char **header) { unsigned int keep_alive_timeout; - const char *timeout_position = strstr(*header, ": "); + char *timeout_position; if (!(csp->config->feature_flags & RUNTIME_FEATURE_CONNECTION_KEEP_ALIVE)) { @@ -1692,29 +1754,41 @@ static jb_err client_keep_alive(struct client_state *csp, char **header) return JB_ERR_OK; } + /* Check for parameter-less format "Keep-Alive: 100" */ + timeout_position = strstr(*header, ": "); if ((NULL == timeout_position) || (1 != sscanf(timeout_position, ": %u", &keep_alive_timeout))) { - log_error(LOG_LEVEL_ERROR, "Couldn't parse: %s", *header); - } - else - { - if (keep_alive_timeout < csp->config->keep_alive_timeout) + /* Assume parameter format "Keep-Alive: timeout=100" */ + timeout_position = strstr(*header, "timeout="); + if ((NULL == timeout_position) + || (1 != sscanf(timeout_position, "timeout=%u", &keep_alive_timeout))) { log_error(LOG_LEVEL_HEADER, - "Reducing keep-alive timeout from %u to %u.", - csp->config->keep_alive_timeout, keep_alive_timeout); - csp->server_connection.keep_alive_timeout = keep_alive_timeout; - } - else - { - /* XXX: Is this log worthy? */ - log_error(LOG_LEVEL_HEADER, - "Client keep-alive timeout is %u. Sticking with %u.", - keep_alive_timeout, csp->config->keep_alive_timeout); + "Couldn't parse: '%s'. Using default timeout %u", + *header, csp->config->keep_alive_timeout); + freez(*header); + + return JB_ERR_OK; } } + if (keep_alive_timeout < csp->config->keep_alive_timeout) + { + log_error(LOG_LEVEL_HEADER, + "Reducing keep-alive timeout from %u to %u.", + csp->config->keep_alive_timeout, keep_alive_timeout); + csp->server_connection.keep_alive_timeout = keep_alive_timeout; + } + else + { + /* XXX: Is this log worthy? */ + log_error(LOG_LEVEL_HEADER, + "Client keep-alive timeout is %u. Sticking with %u.", + keep_alive_timeout, csp->config->keep_alive_timeout); + freez(*header); + } + return JB_ERR_OK; } @@ -1727,22 +1801,20 @@ static jb_err client_keep_alive(struct client_state *csp, char **header) * Content-Length header. * * Parameters : - * 1 : header = The Content-Length header. + * 1 : header_value = The Content-Length header value. * 2 : length = Storage to return the value. * * Returns : JB_ERR_OK on success, or * JB_ERR_PARSE if no value is recognized. * *********************************************************************/ -static jb_err get_content_length(const char *header, unsigned long long *length) +static jb_err get_content_length(const char *header_value, unsigned long long *length) { - assert(header[14] == ':'); - #ifdef _WIN32 assert(sizeof(unsigned long long) > 4); - if (1 != sscanf(header+14, ": %I64u", length)) + if (1 != sscanf(header_value, "%I64u", length)) #else - if (1 != sscanf(header+14, ": %llu", length)) + if (1 != sscanf(header_value, "%llu", length)) #endif { return JB_ERR_PARSE; @@ -1772,10 +1844,12 @@ static jb_err get_content_length(const char *header, unsigned long long *length) static jb_err client_save_content_length(struct client_state *csp, char **header) { unsigned long long content_length = 0; + const char *header_value; assert(*(*header+14) == ':'); - if (JB_ERR_OK != get_content_length(*header, &content_length)) + header_value = *header + 15; + if (JB_ERR_OK != get_content_length(header_value, &content_length)) { log_error(LOG_LEVEL_ERROR, "Crunching invalid header: %s", *header); freez(*header); @@ -1806,8 +1880,7 @@ static jb_err client_save_content_length(struct client_state *csp, char **header * 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. + * Returns : JB_ERR_OK on success. * *********************************************************************/ static jb_err client_connection(struct client_state *csp, char **header) @@ -1817,7 +1890,8 @@ static jb_err client_connection(struct client_state *csp, char **header) if (!strcmpic(*header, connection_close)) { #ifdef FEATURE_CONNECTION_KEEP_ALIVE - if ((csp->config->feature_flags & RUNTIME_FEATURE_CONNECTION_SHARING)) + if ((csp->config->feature_flags & RUNTIME_FEATURE_CONNECTION_SHARING) + && !(csp->flags & CSP_FLAG_SERVER_SOCKET_TAINTED)) { if (!strcmpic(csp->http->ver, "HTTP/1.1")) { @@ -1834,11 +1908,7 @@ static jb_err client_connection(struct client_state *csp, char **header) { char *old_header = *header; - *header = strdup("Connection: keep-alive"); - if (header == NULL) - { - return JB_ERR_MEMORY; - } + *header = strdup_or_die("Connection: keep-alive"); log_error(LOG_LEVEL_HEADER, "Replaced: \'%s\' with \'%s\'", old_header, *header); freez(old_header); @@ -1853,7 +1923,8 @@ static jb_err client_connection(struct client_state *csp, char **header) csp->flags &= ~CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE; } } - else if ((csp->config->feature_flags & RUNTIME_FEATURE_CONNECTION_KEEP_ALIVE)) + else if ((csp->config->feature_flags & RUNTIME_FEATURE_CONNECTION_KEEP_ALIVE) + && !(csp->flags & CSP_FLAG_SERVER_SOCKET_TAINTED)) { log_error(LOG_LEVEL_HEADER, "Keeping the client header '%s' around. " @@ -1866,23 +1937,121 @@ static jb_err client_connection(struct client_state *csp, char **header) { char *old_header = *header; - *header = strdup(connection_close); - if (header == NULL) - { - return JB_ERR_MEMORY; - } + *header = strdup_or_die(connection_close); log_error(LOG_LEVEL_HEADER, "Replaced: \'%s\' with \'%s\'", old_header, *header); freez(old_header); } - /* Signal client_connection_adder() to return early. */ + /* Signal client_connection_header_adder() to return early. */ csp->flags |= CSP_FLAG_CLIENT_CONNECTION_HEADER_SET; return JB_ERR_OK; } +#ifdef FEATURE_CONNECTION_KEEP_ALIVE +/********************************************************************* + * + * Function : client_proxy_connection + * + * Description : Sets the CLIENT_CONNECTION_KEEP_ALIVE flag when + * appropriate and removes the Proxy-Connection + * header. + * + * 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 + * + *********************************************************************/ +static jb_err client_proxy_connection(struct client_state *csp, char **header) +{ + if (0 == (csp->flags & CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE) + && (csp->http->ssl == 0) + && (NULL == strstr(*header, "close"))) + { + log_error(LOG_LEVEL_HEADER, + "The client connection can be kept alive due to: %s", *header); + csp->flags |= CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE; + } + crumble(csp, header); + + return JB_ERR_OK; +} +#endif /* def FEATURE_CONNECTION_KEEP_ALIVE */ + + +/********************************************************************* + * + * Function : client_transfer_encoding + * + * Description : Raise the CSP_FLAG_CHUNKED_CLIENT_BODY flag if + * the request body is "chunked" + * + * XXX: Currently not called through sed() as we + * need the flag earlier on. Should be fixed. + * + * 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 client_transfer_encoding(struct client_state *csp, char **header) +{ + if (strstr(*header, "chunked")) + { + csp->flags |= CSP_FLAG_CHUNKED_CLIENT_BODY; + log_error(LOG_LEVEL_HEADER, "Expecting chunked client body"); + } + + return JB_ERR_OK; +} + + +/********************************************************************* + * + * Function : client_expect + * + * Description : Raise the CSP_FLAG_UNSUPPORTED_CLIENT_EXPECTATION + * if the Expect header value is unsupported. + * + * Rejecting unsupported expectations is a RFC 7231 5.1.1 + * MAY and a RFC 2616 (obsolete) MUST. + * + * 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 client_expect(struct client_state *csp, char **header) +{ + if (0 != strcmpic(*header, "Expect: 100-continue")) + { + csp->flags |= CSP_FLAG_UNSUPPORTED_CLIENT_EXPECTATION; + log_error(LOG_LEVEL_HEADER, + "Unsupported client expectaction: %s", *header); + } + + return JB_ERR_OK; + +} + + /********************************************************************* * * Function : crumble @@ -1974,17 +2143,25 @@ static 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 & CT_DECLARED)) { - /* - * Another, slightly slower, way to see if - * we already parsed another Content-Type header. - */ - assert(NULL != get_header_value(csp->headers, "Content-Type:")); - - log_error(LOG_LEVEL_ERROR, - "Multiple Content-Type headers. Removing and ignoring: \'%s\'", - *header); - freez(*header); - + if (content_filters_enabled(csp->action)) + { + /* + * Making sure the client interprets the content the same way + * Privoxy did is only relevant if Privoxy modified it. + * + * Checking for this is "hard" as it's not yet known when + * this function is called, thus go shopping and and just + * check if Privoxy could filter it. + * + * The main thing is that we don't mess with the headers + * unless the user signalled that it's acceptable. + */ + log_error(LOG_LEVEL_HEADER, + "Multiple Content-Type headers detected. " + "Removing and ignoring: %s", + *header); + freez(*header); + } return JB_ERR_OK; } @@ -2023,11 +2200,12 @@ static jb_err server_content_type(struct client_state *csp, char **header) */ if ((csp->content_type & CT_TEXT) || (csp->action->flags & ACTION_FORCE_TEXT_MODE)) { + jb_err err; freez(*header); - *header = strdup("Content-Type: "); - string_append(header, csp->action->string[ACTION_STRING_CONTENT_TYPE]); + *header = strdup_or_die("Content-Type: "); - if (header == NULL) + err = string_append(header, csp->action->string[ACTION_STRING_CONTENT_TYPE]); + if (JB_ERR_OK != err) { log_error(LOG_LEVEL_HEADER, "Insufficient memory to replace Content-Type!"); return JB_ERR_MEMORY; @@ -2149,8 +2327,7 @@ static jb_err server_content_encoding(struct client_state *csp, char **header) /* * 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]))) + if (content_filters_enabled(csp->action)) { log_error(LOG_LEVEL_INFO, "SDCH-compressed content detected, content filtering disabled. " @@ -2314,10 +2491,12 @@ static jb_err server_adjust_content_length(struct client_state *csp, char **head static jb_err server_save_content_length(struct client_state *csp, char **header) { unsigned long long content_length = 0; + const char *header_value; assert(*(*header+14) == ':'); - if (JB_ERR_OK != get_content_length(*header, &content_length)) + header_value = *header + 15; + if (JB_ERR_OK != get_content_length(header_value, &content_length)) { log_error(LOG_LEVEL_ERROR, "Crunching invalid header: %s", *header); freez(*header); @@ -2461,7 +2640,7 @@ static jb_err server_last_modified(struct client_state *csp, char **header) newval = csp->action->string[ACTION_STRING_LAST_MODIFIED]; - if (0 == strcmpic(newval, "block") ) + if (0 == strcmpic(newval, "block")) { /* * Blocking Last-Modified header. Useless but why not. @@ -2492,13 +2671,12 @@ static jb_err server_last_modified(struct client_state *csp, char **header) } else if (0 == strcmpic(newval, "randomize")) { - const char *header_time = *header + sizeof("Last-Modified:"); - log_error(LOG_LEVEL_HEADER, "Randomizing: %s", *header); - if (JB_ERR_OK != parse_header_time(header_time, &last_modified)) + if (JB_ERR_OK != parse_time_header(*header, &last_modified)) { - log_error(LOG_LEVEL_HEADER, "Couldn't parse: %s in %s (crunching!)", header_time, *header); + log_error(LOG_LEVEL_HEADER, + "Couldn't parse time in %s (crunching!)", *header); freez(*header); } else @@ -2757,7 +2935,7 @@ static jb_err client_accept_language(struct client_state *csp, char **header) newval = csp->action->string[ACTION_STRING_LANGUAGE]; - if ((newval == NULL) || (0 == strcmpic(newval, "block")) ) + if ((newval == NULL) || (0 == strcmpic(newval, "block"))) { /* * Blocking Accept-Language header @@ -2934,7 +3112,7 @@ static jb_err client_from(struct client_state *csp, char **header) /* * Are we blocking the e-mail address? */ - if ((newval == NULL) || (0 == strcmpic(newval, "block")) ) + if ((newval == NULL) || (0 == strcmpic(newval, "block"))) { log_error(LOG_LEVEL_HEADER, "crunched From!"); return JB_ERR_OK; @@ -2971,7 +3149,7 @@ static jb_err client_from(struct client_state *csp, char **header) *********************************************************************/ static jb_err client_send_cookie(struct client_state *csp, char **header) { - if (csp->action->flags & ACTION_NO_COOKIE_READ) + if (csp->action->flags & ACTION_CRUNCH_OUTGOING_COOKIES) { log_error(LOG_LEVEL_HEADER, "Crunched outgoing cookie: %s", *header); freez(*header); @@ -3094,9 +3272,6 @@ static jb_err client_max_forwards(struct client_state *csp, char **header) * port information, parse and evaluate the Host * header field. * - * Also, kill ill-formed HOST: headers as sent by - * Apple's iTunes software when used with a proxy. - * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) * 2 : header = On input, pointer to header to modify. @@ -3112,14 +3287,9 @@ static jb_err client_host(struct client_state *csp, char **header) { char *p, *q; - /* - * If the header field name is all upper-case, chances are that it's - * an ill-formed one from iTunes. BTW, killing innocent headers here is - * not a problem -- they are regenerated later. - */ - if ((*header)[1] == 'O') + if (strlen(*header) < 7) { - log_error(LOG_LEVEL_HEADER, "Killed all-caps Host header line: %s", *header); + log_error(LOG_LEVEL_HEADER, "Removing empty Host header"); freez(*header); return JB_ERR_OK; } @@ -3128,16 +3298,9 @@ static jb_err client_host(struct client_state *csp, char **header) *csp->http->hostport == ' ' || *csp->http->hostport == '\0') { - if (NULL == (p = strdup((*header)+6))) - { - return JB_ERR_MEMORY; - } + p = strdup_or_die((*header)+6); chomp(p); - if (NULL == (q = strdup(p))) - { - freez(p); - return JB_ERR_MEMORY; - } + q = strdup_or_die(p); freez(csp->http->hostport); csp->http->hostport = p; @@ -3194,7 +3357,7 @@ static jb_err client_if_modified_since(struct client_state *csp, char **header) const char *newval; char * endptr; - if ( 0 == strcmpic(*header, "If-Modified-Since: Wed, 08 Jun 1955 12:00:00 GMT")) + if (0 == strcmpic(*header, "If-Modified-Since: Wed, 08 Jun 1955 12:00:00 GMT")) { /* * The client got an error message because of a temporary problem, @@ -3217,11 +3380,10 @@ static jb_err client_if_modified_since(struct client_state *csp, char **header) } else /* add random value */ { - const char *header_time = *header + sizeof("If-Modified-Since:"); - - if (JB_ERR_OK != parse_header_time(header_time, &tm)) + if (JB_ERR_OK != parse_time_header(*header, &tm)) { - log_error(LOG_LEVEL_HEADER, "Couldn't parse: %s in %s (crunching!)", header_time, *header); + log_error(LOG_LEVEL_HEADER, + "Couldn't parse time in %s (crunching!)", *header); freez(*header); } else @@ -3341,7 +3503,7 @@ static jb_err client_if_none_match(struct client_state *csp, char **header) *********************************************************************/ jb_err client_x_filter(struct client_state *csp, char **header) { - if ( 0 == strcmpic(*header, "X-Filter: No")) + if (0 == strcmpic(*header, "X-Filter: No")) { if (!(csp->config->feature_flags & RUNTIME_FEATURE_HTTP_TOGGLE)) { @@ -3373,11 +3535,25 @@ jb_err client_x_filter(struct client_state *csp, char **header) * Function : client_range * * Description : Removes Range, Request-Range and If-Range headers if - * content filtering is enabled. If the client's version - * of the document has been altered by Privoxy, the server - * could interpret the range differently than the client - * intended in which case the user could end up with - * corrupted content. + * content filtering is enabled and the range doesn't + * start at byte 0. + * + * If the client's version of the document has been + * altered by Privoxy, the server could interpret the + * range differently than the client intended in which + * case the user could end up with corrupted content. + * + * If the range starts at byte 0 this isn't an issue + * so the header can pass. Partial requests like this + * are used to render preview images for videos without + * downloading the whole video. + * + * While HTTP doesn't require that range requests are + * honoured and the client could simply abort the download + * after receiving a sufficient amount of data, various + * clients don't handle complete responses to range + * requests gracefully and emit misleading error messages + * instead. * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) @@ -3391,7 +3567,8 @@ jb_err client_x_filter(struct client_state *csp, char **header) *********************************************************************/ static jb_err client_range(struct client_state *csp, char **header) { - if (content_filters_enabled(csp->action)) + if (content_filters_enabled(csp->action) + && (0 != strncmpic(strstr(*header, ":"), ": bytes=0-", 10))) { log_error(LOG_LEVEL_HEADER, "Content filtering is enabled." " Crunching: \'%s\' to prevent range-mismatch problems.", *header); @@ -3428,17 +3605,16 @@ static jb_err client_host_adder(struct client_state *csp) return JB_ERR_OK; } - if ( !csp->http->hostport || !*(csp->http->hostport)) + 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; + log_error(LOG_LEVEL_ERROR, "Destination host unknown."); + return JB_ERR_PARSE; } /* * remove 'user:pass@' from 'proto://user:pass@host' */ - if ( (p = strchr( csp->http->hostport, '@')) != NULL ) + if ((p = strchr( csp->http->hostport, '@')) != NULL) { p++; } @@ -3571,7 +3747,7 @@ static jb_err server_connection_adder(struct client_state *csp) #ifdef FEATURE_CONNECTION_KEEP_ALIVE && !(csp->flags & CSP_FLAG_SERVER_SOCKET_TAINTED) #endif - ) + ) { log_error(LOG_LEVEL_HEADER, "A HTTP/1.1 response " "without Connection header implies keep-alive."); @@ -3591,8 +3767,7 @@ static jb_err server_connection_adder(struct client_state *csp) * Function : server_proxy_connection_adder * * Description : Adds a "Proxy-Connection: keep-alive" header to - * csp->headers if the client asked for keep-alive. - * XXX: We should reuse existent ones. + * csp->headers when appropriate. * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) @@ -3608,7 +3783,9 @@ static jb_err server_proxy_connection_adder(struct client_state *csp) if ((csp->flags & CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE) && !(csp->flags & CSP_FLAG_SERVER_SOCKET_TAINTED) - && !(csp->flags & CSP_FLAG_SERVER_PROXY_CONNECTION_HEADER_SET)) + && !(csp->flags & CSP_FLAG_SERVER_PROXY_CONNECTION_HEADER_SET) + && ((csp->flags & CSP_FLAG_SERVER_CONTENT_LENGTH_SET) + || (csp->flags & CSP_FLAG_CHUNKED))) { log_error(LOG_LEVEL_HEADER, "Adding: %s", proxy_connection_header); err = enlist(csp->headers, proxy_connection_header); @@ -3645,6 +3822,7 @@ static jb_err client_connection_header_adder(struct client_state *csp) #ifdef FEATURE_CONNECTION_KEEP_ALIVE if ((csp->config->feature_flags & RUNTIME_FEATURE_CONNECTION_KEEP_ALIVE) + && !(csp->flags & CSP_FLAG_SERVER_SOCKET_TAINTED) && (csp->http->ssl == 0) && !strcmpic(csp->http->ver, "HTTP/1.1")) { @@ -3668,6 +3846,7 @@ static jb_err client_connection_header_adder(struct client_state *csp) * is a partial range (HTTP status 206) * - Rewrite HTTP/1.1 answers to HTTP/1.0 if +downgrade * action applies. + * - Normalize the HTTP-version. * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) @@ -3677,39 +3856,131 @@ static jb_err client_connection_header_adder(struct client_state *csp) * original string if necessary. * * Returns : JB_ERR_OK on success, or - * JB_ERR_MEMORY on out-of-memory error. + * JB_ERR_PARSE on fatal parse errors. * *********************************************************************/ static jb_err server_http(struct client_state *csp, char **header) { - sscanf(*header, "HTTP/%*d.%*d %d", &(csp->http->status)); + char *reason_phrase = NULL; + char *new_response_line; + char *p; + size_t length; + unsigned int major_version; + unsigned int minor_version; + + /* Get the reason phrase which start after the second whitespace */ + p = strchr(*header, ' '); + if (NULL != p) + { + p++; + reason_phrase = strchr(p, ' '); + } + + if (reason_phrase != NULL) + { + reason_phrase++; + } + else + { + log_error(LOG_LEVEL_ERROR, + "Response line lacks reason phrase: %s", *header); + reason_phrase=""; + } + + if (3 != sscanf(*header, "HTTP/%u.%u %d", &major_version, + &minor_version, &(csp->http->status))) + { + log_error(LOG_LEVEL_ERROR, + "Failed to parse the response line: %s", *header); + return JB_ERR_PARSE; + } + if (csp->http->status == 206) { csp->content_type = CT_TABOO; } - if ((csp->action->flags & ACTION_DOWNGRADE) != 0) + if (major_version != 1 || (minor_version != 0 && minor_version != 1)) { - /* XXX: Should we do a real validity check here? */ - if (strlen(*header) > 8) - { - (*header)[7] = '0'; - log_error(LOG_LEVEL_HEADER, "Downgraded answer to HTTP/1.0"); - } - else - { - /* - * XXX: Should we block the request or - * enlist a valid status code line here? - */ - log_error(LOG_LEVEL_INFO, "Malformed server response detected. " - "Downgrading to HTTP/1.0 impossible."); - } + /* + * According to RFC 7230 2.6 intermediaries MUST send + * their own HTTP-version in forwarded messages. + */ + log_error(LOG_LEVEL_ERROR, + "Unsupported HTTP version. Downgrading to 1.1."); + major_version = 1; + minor_version = 1; } + if (((csp->action->flags & ACTION_DOWNGRADE) != 0) && (minor_version == 1)) + { + log_error(LOG_LEVEL_HEADER, "Downgrading answer to HTTP/1.0"); + minor_version = 0; + } + + /* Rebuild response line. */ + length = sizeof("HTTP/1.1 200 ") + strlen(reason_phrase) + 1; + new_response_line = malloc_or_die(length); + + snprintf(new_response_line, length, "HTTP/%u.%u %d %s", + major_version, minor_version, csp->http->status, reason_phrase); + + if (0 != strcmp(*header, new_response_line)) + { + log_error(LOG_LEVEL_HEADER, "Response line '%s' changed to '%s'", + *header, new_response_line); + } + + freez(*header); + *header = new_response_line; + return JB_ERR_OK; } +/********************************************************************* + * + * Function : add_cooky_expiry_date + * + * Description : Adds a cookie expiry date to a string. + * + * Parameters : + * 1 : cookie = On input, pointer to cookie to modify. + * On output, pointer to the modified header. + * The original string is freed. + * 2 : lifetime = Seconds the cookie should be valid + * + * Returns : N/A + * + *********************************************************************/ +static void add_cookie_expiry_date(char **cookie, time_t lifetime) +{ + char tmp[50]; + struct tm *timeptr = NULL; + time_t expiry_date = time(NULL) + lifetime; +#ifdef HAVE_GMTIME_R + struct tm gmt; + + timeptr = gmtime_r(&expiry_date, &gmt); +#elif defined(MUTEX_LOCKS_AVAILABLE) + privoxy_mutex_lock(&gmtime_mutex); + timeptr = gmtime(&expiry_date); + privoxy_mutex_unlock(&gmtime_mutex); +#else + timeptr = gmtime(&expiry_date); +#endif + + if (NULL == timeptr) + { + log_error(LOG_LEVEL_FATAL, + "Failed to get the time in add_cooky_expiry_date()"); + } + strftime(tmp, sizeof(tmp), "; expires=%a, %d-%b-%Y %H:%M:%S GMT", timeptr); + if (JB_ERR_OK != string_append(cookie, tmp)) + { + log_error(LOG_LEVEL_FATAL, "Out of memory in add_cooky_expiry()"); + } +} + /********************************************************************* * @@ -3719,10 +3990,6 @@ static jb_err server_http(struct client_state *csp, char **header) * Crunch, accept or rewrite it to a session cookie. * Called from `sed'. * - * TODO: Allow the user to specify a new expiration - * time to cause the cookie to expire even before the - * browser is closed. - * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) * 2 : header = On input, pointer to header to modify. @@ -3736,20 +4003,23 @@ static jb_err server_http(struct client_state *csp, char **header) *********************************************************************/ static jb_err server_set_cookie(struct client_state *csp, char **header) { - time_t now; - time_t cookie_time; - - time(&now); - - if ((csp->action->flags & ACTION_NO_COOKIE_SET) != 0) + if ((csp->action->flags & ACTION_CRUNCH_INCOMING_COOKIES) != 0) { log_error(LOG_LEVEL_HEADER, "Crunching incoming cookie: %s", *header); freez(*header); } - else if ((csp->action->flags & ACTION_NO_COOKIE_KEEP) != 0) + else if ((0 != (csp->action->flags & ACTION_SESSION_COOKIES_ONLY)) + || (0 != (csp->action->flags & ACTION_LIMIT_COOKIE_LIFETIME))) { - /* Flag whether or not to log a message */ - int changed = 0; + time_t now; + time_t cookie_time; + long cookie_lifetime = 0; + enum + { + NO_EXPIRY_DATE_SPECIFIED, + EXPIRY_DATE_ACCEPTABLE, + EXPIRY_DATE_UNACCEPTABLE + } expiry_date_status = NO_EXPIRY_DATE_SPECIFIED; /* A variable to store the tag we're working on */ char *cur_tag; @@ -3763,6 +4033,20 @@ static jb_err server_set_cookie(struct client_state *csp, char **header) cur_tag++; } + time(&now); + + if ((csp->action->flags & ACTION_LIMIT_COOKIE_LIFETIME) != 0) + { + const char *param = csp->action->string[ACTION_STRING_LIMIT_COOKIE_LIFETIME]; + + cookie_lifetime = strtol(param, NULL, 0); + if (cookie_lifetime < 0) + { + log_error(LOG_LEVEL_FATAL, "Invalid cookie lifetime limit: %s", param); + } + cookie_lifetime *= 60; + } + /* Loop through each tag in the cookie */ while (*cur_tag) { @@ -3816,7 +4100,7 @@ static jb_err server_set_cookie(struct client_state *csp, char **header) log_error(LOG_LEVEL_ERROR, "Can't parse \'%s\', send by %s. Unsupported time format?", cur_tag, csp->http->url); string_move(cur_tag, next_tag); - changed = 1; + expiry_date_status = EXPIRY_DATE_UNACCEPTABLE; } else { @@ -3854,12 +4138,21 @@ static jb_err server_set_cookie(struct client_state *csp, char **header) * anyway, which in many cases will be shorter * than a browser session. */ - if (cookie_time - now < 0) + if (cookie_time < now) { log_error(LOG_LEVEL_HEADER, "Cookie \'%s\' is already expired and can pass unmodified.", *header); /* Just in case some clown sets more then one expiration date */ cur_tag = next_tag; + expiry_date_status = EXPIRY_DATE_ACCEPTABLE; + } + else if ((cookie_lifetime != 0) && (cookie_time < (now + cookie_lifetime))) + { + log_error(LOG_LEVEL_HEADER, "Cookie \'%s\' can pass unmodified. " + "Its lifetime is below the limit.", *header); + /* Just in case some clown sets more then one expiration date */ + cur_tag = next_tag; + expiry_date_status = EXPIRY_DATE_ACCEPTABLE; } else { @@ -3870,7 +4163,7 @@ static jb_err server_set_cookie(struct client_state *csp, char **header) string_move(cur_tag, next_tag); /* That changed the header, need to issue a log message */ - changed = 1; + expiry_date_status = EXPIRY_DATE_UNACCEPTABLE; /* * Note that the next tag has now been moved to *cur_tag, @@ -3887,11 +4180,19 @@ static jb_err server_set_cookie(struct client_state *csp, char **header) } } - if (changed) + if (expiry_date_status != EXPIRY_DATE_ACCEPTABLE) { assert(NULL != *header); - log_error(LOG_LEVEL_HEADER, "Cookie rewritten to a temporary one: %s", - *header); + if (cookie_lifetime != 0) + { + add_cookie_expiry_date(header, cookie_lifetime); + log_error(LOG_LEVEL_HEADER, "Cookie rewritten to: %s", *header); + } + else if (expiry_date_status != NO_EXPIRY_DATE_SPECIFIED) + { + log_error(LOG_LEVEL_HEADER, + "Cookie rewritten to a temporary one: %s", *header); + } } } @@ -3962,7 +4263,7 @@ static jb_err parse_header_time(const char *header_time, time_t *result) * attempt to work around GNU libc's strptime() * reporting negative year values when using %Y. */ - static const char * const time_formats[] = { + static const char time_formats[][22] = { /* Tue, 02-Jun-37 20:00:00 */ "%a, %d-%b-%y %H:%M:%S", /* Tue, 02 Jun 2037 20:00:00 */ @@ -3995,6 +4296,44 @@ static jb_err parse_header_time(const char *header_time, time_t *result) continue; } *result = timegm(&gmt); + +#ifdef FEATURE_STRPTIME_SANITY_CHECKS + /* + * Verify that parsing the date recreated from the first + * parse operation gets the previous result. If it doesn't, + * either strptime() or strftime() are malfunctioning. + * + * We could string-compare the recreated date with the original + * header date, but this leads to false positives as strptime() + * may let %a accept all day formats while strftime() will only + * create one. + */ + { + char recreated_date[100]; + struct tm *tm; + time_t result2; + + tm = gmtime(result); + strftime(recreated_date, sizeof(recreated_date), time_formats[i], tm); + memset(&gmt, 0, sizeof(gmt)); + if (NULL == strptime(recreated_date, time_formats[i], &gmt)) + { + log_error(LOG_LEVEL_ERROR, + "Failed to parse '%s' generated with '%s' to recreate '%s'.", + recreated_date, time_formats[i], header_time); + continue; + } + result2 = timegm(&gmt); + if (*result != result2) + { + log_error(LOG_LEVEL_ERROR, "strftime() and strptime() disagree. " + "Format: '%s'. In: '%s', out: '%s'. %d != %d. Rejecting.", + time_formats[i], header_time, recreated_date, *result, result2); + continue; + } + } +#endif + return JB_ERR_OK; } } @@ -4003,6 +4342,44 @@ static jb_err parse_header_time(const char *header_time, time_t *result) } +/********************************************************************* + * + * Function : parse_time_header + * + * Description : Parses the time in an HTTP time header to get + * the numerical respresentation. + * + * Parameters : + * 1 : header = HTTP header with a time value + * 2 : result = storage for header_time in seconds + * + * Returns : JB_ERR_OK if the time format was recognized, or + * JB_ERR_PARSE otherwise. + * + *********************************************************************/ +static jb_err parse_time_header(const char *header, time_t *result) +{ + const char *header_time; + + header_time = strchr(header, ':'); + + /* + * Currently this can't happen as all callers are called + * through sed() which requires a header name followed by + * a colon. + */ + assert(header_time != NULL); + + header_time++; + if (*header_time == ' ') + { + header_time++; + } + + return parse_header_time(header_time, result); + +} + /********************************************************************* * @@ -4020,7 +4397,7 @@ static jb_err parse_header_time(const char *header_time, time_t *result) * the "Host:" header) * 2 : http = storage for the result (host, port and hostport). * - * Returns : JB_ERR_MEMORY in case of memory problems, + * Returns : JB_ERR_MEMORY (or terminates) in case of memory problems, * JB_ERR_PARSE if the host header couldn't be found, * JB_ERR_OK otherwise. * @@ -4031,6 +4408,8 @@ jb_err get_destination_from_headers(const struct list *headers, struct http_requ char *p; char *host; + assert(!http->ssl); + host = get_header_value(headers, "Host:"); if (NULL == host) @@ -4039,19 +4418,9 @@ jb_err get_destination_from_headers(const struct list *headers, struct http_requ return JB_ERR_PARSE; } - p = strdup(host); - if (NULL == p) - { - log_error(LOG_LEVEL_ERROR, "Out of memory while parsing \"Host:\" header"); - return JB_ERR_MEMORY; - } + p = strdup_or_die(host); chomp(p); - if (NULL == (q = strdup(p))) - { - freez(p); - log_error(LOG_LEVEL_ERROR, "Out of memory while parsing \"Host:\" header"); - return JB_ERR_MEMORY; - } + q = strdup_or_die(p); freez(http->hostport); http->hostport = p; @@ -4066,12 +4435,12 @@ jb_err get_destination_from_headers(const struct list *headers, struct http_requ } else { - http->port = http->ssl ? 443 : 80; + http->port = 80; } /* Rebuild request URL */ freez(http->url); - http->url = strdup(http->ssl ? "https://" : "http://"); + http->url = strdup("http://"); string_append(&http->url, http->hostport); string_append(&http->url, http->path); if (http->url == NULL) @@ -4082,6 +4451,26 @@ jb_err get_destination_from_headers(const struct list *headers, struct http_requ log_error(LOG_LEVEL_HEADER, "Destination extracted from \"Host:\" header. New request URL: %s", http->url); + /* + * Regenerate request line in "proxy format" + * to make rewrites more convenient. + * XXX: Code duplication. + */ + assert(http->cmd != NULL); + freez(http->cmd); + http->cmd = strdup_or_die(http->gpc); + string_append(&http->cmd, " "); + string_append(&http->cmd, http->url); + string_append(&http->cmd, " "); + string_append(&http->cmd, http->ver); + if (http->cmd == NULL) + { + return JB_ERR_MEMORY; + } + + log_error(LOG_LEVEL_HEADER, "Faked request-Line: %s", + http->cmd); + return JB_ERR_OK; } @@ -4183,16 +4572,10 @@ static jb_err create_fake_referrer(char **header, const char *fake_referrer) static jb_err handle_conditional_hide_referrer_parameter(char **header, const char *host, const int parameter_conditional_block) { - char *referer = strdup(*header); + char *referer = strdup_or_die(*header); const size_t hostlength = strlen(host); const char *referer_url = NULL; - if (NULL == referer) - { - freez(*header); - return JB_ERR_MEMORY; - } - /* referer begins with 'Referer: http[s]://' */ if ((hostlength+17) < strlen(referer)) { @@ -4247,6 +4630,40 @@ static void create_content_length_header(unsigned long long content_length, } +#ifdef FEATURE_CONNECTION_KEEP_ALIVE +/********************************************************************* + * + * Function : get_expected_content_length + * + * Description : Figures out the content length from a list of headers. + * + * Parameters : + * 1 : headers = List of headers + * + * Returns : Number of bytes to expect + * + *********************************************************************/ +unsigned long long get_expected_content_length(struct list *headers) +{ + const char *content_length_header; + unsigned long long content_length = 0; + + content_length_header = get_header_value(headers, "Content-Length:"); + if (content_length_header != NULL) + { + if (JB_ERR_OK != get_content_length(content_length_header, &content_length)) + { + log_error(LOG_LEVEL_ERROR, + "Failed to get the Content-Length in %s", content_length_header); + /* XXX: The header will be removed later on */ + return 0; + } + } + + return content_length; +} +#endif + /* Local Variables: tab-width: 3