X-Git-Url: http://www.privoxy.org/gitweb/?p=privoxy.git;a=blobdiff_plain;f=parsers.c;h=5489288e230c5a1bff96cffa6b2191e7048f7670;hp=e7a22e247164f19fec9365a3c15bbefde7862db3;hb=cdce239fb2f8a54963856a048976898f0003df20;hpb=1b2cc4af84a3bc3ba01ba913ca30012c9efa31d8 diff --git a/parsers.c b/parsers.c index e7a22e24..5489288e 100644 --- a/parsers.c +++ b/parsers.c @@ -1,23 +1,11 @@ -const char parsers_rcs[] = "$Id: parsers.c,v 1.221 2011/03/27 14:01:46 fabiankeil Exp $"; +const char parsers_rcs[] = "$Id: parsers.c,v 1.255 2012/10/17 18:19:29 fabiankeil Exp $"; /********************************************************************* * * File : $Source: /cvsroot/ijbswa/current/parsers.c,v $ * * Purpose : Declares functions to parse/crunch headers and pages. - * Functions declared include: - * `add_to_iob', `client_cookie_adder', `client_from', - * `client_referrer', `client_send_cookie', `client_ua', - * `client_uagent', `client_x_forwarded', - * `client_x_forwarded_adder', `client_xtra_adder', - * `content_type', `crumble', `destroy_list', `enlist', - * `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', `decompress_iob' and `server_set_cookie'. - * - * Copyright : Written by and Copyright (C) 2001-2009 the + * + * Copyright : Written by and Copyright (C) 2001-2012 the * Privoxy team. http://www.privoxy.org/ * * Based on the Internet Junkbuster originally written @@ -104,19 +92,6 @@ const char parsers_rcs[] = "$Id: parsers.c,v 1.221 2011/03/27 14:01:46 fabiankei const char parsers_h_rcs[] = PARSERS_H_VERSION; -/* Fix a problem with Solaris. There should be no effect on other - * platforms. - * 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". - * - * Why did they write a character function that can't take a simple - * "char" argument? Doh! - */ -#define ijb_isupper(__X) isupper((int)(unsigned char)(__X)) -#define ijb_tolower(__X) tolower((int)(unsigned char)(__X)) - 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); @@ -187,10 +162,10 @@ struct parsers { /** The header prefix to match */ const char *str; - + /** The length of the prefix to match */ const size_t len; - + /** The function to apply to this line */ const parser_func_ptr parser; }; @@ -252,7 +227,6 @@ static const add_header_func_ptr add_client_headers[] = { client_host_adder, client_x_forwarded_for_adder, client_xtra_adder, - /* Temporarily disabled: client_accept_encoding_adder, */ client_connection_header_adder, NULL }; @@ -350,7 +324,7 @@ jb_err add_to_iob(struct client_state *csp, char *buf, long n) { want *= 2; } - + if (want <= csp->config->buffer_limit && NULL != (p = (char *)realloc(iob->buf, want))) { iob->size = want; @@ -406,7 +380,7 @@ jb_err add_to_iob(struct client_state *csp, char *buf, long n) jb_err decompress_iob(struct client_state *csp) { char *buf; /* new, uncompressed buffer */ - char *cur; /* Current iob position (to keep the original + 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 */ @@ -554,7 +528,7 @@ jb_err decompress_iob(struct client_state *csp) * * Fortunately, add_to_iob() has thoughtfully null-terminated * the buffer; we can just increment the end pointer to include - * the dummy byte. + * the dummy byte. */ csp->iob->eod++; } @@ -619,10 +593,12 @@ jb_err decompress_iob(struct client_state *csp) } /* - * If we tried the limit and still didn't have enough - * memory, just give up. + * If we reached the buffer limit and still didn't have enough + * memory, just give up. Due to the ceiling enforced by the next + * if block we could actually check for equality here, but as it + * can be easily mistaken for a bug we don't. */ - if (bufsize == csp->config->buffer_limit) + if (bufsize >= csp->config->buffer_limit) { log_error(LOG_LEVEL_ERROR, "Buffer limit reached while decompressing iob"); return JB_ERR_MEMORY; @@ -636,7 +612,7 @@ jb_err decompress_iob(struct client_state *csp) { bufsize = csp->config->buffer_limit; } - + /* Try to allocate the new buffer. */ tmpbuf = realloc(buf, bufsize); if (NULL == tmpbuf) @@ -708,7 +684,7 @@ jb_err decompress_iob(struct client_state *csp) csp->iob->cur = csp->iob->buf + skip_size; csp->iob->eod = (char *)zstr.next_out; csp->iob->size = bufsize; - + /* * Make sure the new uncompressed iob obeys some minimal * consistency conditions. @@ -794,11 +770,11 @@ static void normalize_lws(char *header) while (*p != '\0') { - if (ijb_isspace(*p) && ijb_isspace(*(p+1))) + if (privoxy_isspace(*p) && privoxy_isspace(*(p+1))) { char *q = p+1; - while (ijb_isspace(*q)) + while (privoxy_isspace(*q)) { q++; } @@ -831,7 +807,7 @@ static void normalize_lws(char *header) } p = strchr(header, ':'); - if ((p != NULL) && (p != header) && ijb_isspace(*(p-1))) + if ((p != NULL) && (p != header) && privoxy_isspace(*(p-1))) { /* * There's still space before the colon. @@ -880,7 +856,7 @@ char *get_header(struct iob *iob) * Header spans multiple lines, append the next one. */ char *continued_header; - + continued_header = get_header_line(iob); if ((continued_header == NULL) || (*continued_header == '\0')) { @@ -1006,13 +982,13 @@ char *get_header_value(const struct list *header_list, const char *header_name) * Found: return pointer to start of value */ ret = cur_entry->str + length; - while (*ret && ijb_isspace(*ret)) ret++; + while (*ret && privoxy_isspace(*ret)) ret++; return ret; } } } - /* + /* * Not found */ return NULL; @@ -1024,7 +1000,7 @@ char *get_header_value(const struct list *header_list, const char *header_name) * * Function : scan_headers * - * Description : Scans headers, applies tags and updates action bits. + * Description : Scans headers, applies tags and updates action bits. * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) @@ -1049,6 +1025,79 @@ 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 @@ -1115,6 +1164,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; } @@ -1185,6 +1239,18 @@ jb_err update_server_headers(struct client_state *csp) } #endif /* def FEATURE_CONNECTION_KEEP_ALIVE */ +#ifdef FEATURE_COMPRESSION + if ((JB_ERR_OK == err) + && (csp->flags & CSP_FLAG_BUFFERED_CONTENT_DEFLATED)) + { + err = enlist_unique_header(csp->headers, "Content-Encoding", "deflate"); + if (JB_ERR_OK == err) + { + log_error(LOG_LEVEL_HEADER, "Added header: Content-Encoding: deflate"); + } + } +#endif + return err; } @@ -1330,7 +1396,7 @@ static jb_err header_tagger(struct client_state *csp, char *header) b->name); continue; } - + if (!list_contains_item(csp->tags, tag)) { if (JB_ERR_OK != enlist(csp->tags, tag)) @@ -1492,14 +1558,14 @@ static jb_err filter_header(struct client_state *csp, char **header) for (job = joblist; NULL != job; job = job->next) { matches = pcrs_execute(job, *header, size, &newheader, &size); - if ( 0 < matches ) + if (0 < matches) { - current_hits += matches; + current_hits += matches; log_error(LOG_LEVEL_HEADER, "Transforming \"%s\" to \"%s\"", *header, newheader); freez(*header); *header = newheader; } - else if ( 0 == matches ) + else if (0 == matches) { /* Filter doesn't change header */ freez(newheader); @@ -1556,8 +1622,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) @@ -1566,7 +1631,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)) @@ -1584,11 +1649,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); } @@ -1739,22 +1800,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; @@ -1784,10 +1843,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); @@ -1808,7 +1869,7 @@ static jb_err client_save_content_length(struct client_state *csp, char **header * Function : client_connection * * Description : Makes sure a proper "Connection:" header is - * set and signals connection_header_adder + * set and signals connection_header_adder * to do nothing. * * Parameters : @@ -1818,8 +1879,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) @@ -1829,23 +1889,25 @@ 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")) { log_error(LOG_LEVEL_HEADER, "Removing \'%s\' to imply keep-alive.", *header); freez(*header); + /* + * While we imply keep-alive to the server, + * we have to remember that the client didn't. + */ + csp->flags &= ~CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE; } else { 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); @@ -1860,7 +1922,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. " @@ -1873,17 +1936,13 @@ 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; @@ -1945,7 +2004,7 @@ static jb_err crunch_server_header(struct client_state *csp, char **header) /* 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); + log_error(LOG_LEVEL_HEADER, "Crunching server header: %s (contains: %s)", *header, crunch_pattern); freez(*header); } } @@ -2026,12 +2085,12 @@ static jb_err server_content_type(struct client_state *csp, char **header) { /* * Make sure the user doesn't accidentally - * change the content type of binary documents. + * change the content type of binary documents. */ if ((csp->content_type & CT_TEXT) || (csp->action->flags & ACTION_FORCE_TEXT_MODE)) { freez(*header); - *header = strdup("Content-Type: "); + *header = strdup_or_die("Content-Type: "); string_append(header, csp->action->string[ACTION_STRING_CONTENT_TYPE]); if (header == NULL) @@ -2125,7 +2184,7 @@ static jb_err server_transfer_coding(struct client_state *csp, char **header) * * If FEATURE_ZLIB is enabled and the compression type * supported, the content is marked for decompression. - * + * * XXX: Doesn't properly deal with multiple or with * unsupported but unknown encodings. * Is case-sensitive but shouldn't be. @@ -2246,7 +2305,7 @@ static jb_err server_adjust_content_encoding(struct client_state *csp, char **he /* * We successfully decompressed the content, * and have to clean the header now, so the - * client no longer expects compressed data.. + * client no longer expects compressed data. * * XXX: There is a difference between cleaning * and removing it completely. @@ -2321,10 +2380,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); @@ -2414,7 +2475,7 @@ static jb_err server_content_disposition(struct client_state *csp, char **header return JB_ERR_OK; } else - { + { /* * Replacing Content-Disposition header */ @@ -2454,7 +2515,6 @@ static jb_err server_content_disposition(struct client_state *csp, char **header static jb_err server_last_modified(struct client_state *csp, char **header) { const char *newval; - char buf[BUFFER_SIZE]; time_t last_modified; char newheader[50]; @@ -2469,7 +2529,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. @@ -2479,18 +2539,19 @@ static jb_err server_last_modified(struct client_state *csp, char **header) return JB_ERR_OK; } else if (0 == strcmpic(newval, "reset-to-request-time")) - { + { /* * Setting Last-Modified Header to now. */ + char buf[30]; get_http_time(0, buf, sizeof(buf)); freez(*header); *header = strdup("Last-Modified: "); - string_append(header, buf); + string_append(header, buf); if (*header == NULL) { - log_error(LOG_LEVEL_HEADER, "Insufficient memory. Last-Modified header got lost, boohoo."); + log_error(LOG_LEVEL_HEADER, "Insufficient memory. Last-Modified header got lost, boohoo."); } else { @@ -2525,7 +2586,7 @@ static jb_err server_last_modified(struct client_state *csp, char **header) if (negative_delta) { - rtime *= -1; + rtime *= -1; log_error(LOG_LEVEL_HEADER, "Server time in the future."); } rtime = pick_from_range(rtime); @@ -2560,7 +2621,7 @@ static jb_err server_last_modified(struct client_state *csp, char **header) if (*header == NULL) { log_error(LOG_LEVEL_ERROR, "Insufficient memory, header crunched without replacement."); - return JB_ERR_MEMORY; + return JB_ERR_MEMORY; } days = rtime / (3600 * 24); @@ -2605,6 +2666,13 @@ static jb_err server_last_modified(struct client_state *csp, char **header) *********************************************************************/ static jb_err client_accept_encoding(struct client_state *csp, char **header) { +#ifdef FEATURE_COMPRESSION + if ((csp->config->feature_flags & RUNTIME_FEATURE_COMPRESSION) + && strstr(*header, "deflate")) + { + csp->flags |= CSP_FLAG_CLIENT_SUPPORTS_DEFLATE; + } +#endif if ((csp->action->flags & ACTION_NO_COMPRESSION) != 0) { log_error(LOG_LEVEL_HEADER, "Suppressed offer to compress content"); @@ -2669,7 +2737,7 @@ static jb_err client_referrer(struct client_state *csp, char **header) /* booleans for parameters we have to check multiple times */ int parameter_conditional_block; int parameter_conditional_forge; - + #ifdef FEATURE_FORCE_LOAD /* * Since the referrer can include the prefix even @@ -2757,7 +2825,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 @@ -2767,18 +2835,18 @@ static jb_err client_accept_language(struct client_state *csp, char **header) return JB_ERR_OK; } else - { + { /* * Replacing Accept-Language header */ freez(*header); *header = strdup("Accept-Language: "); - string_append(header, newval); + string_append(header, newval); if (*header == NULL) { log_error(LOG_LEVEL_ERROR, - "Insufficient memory. Accept-Language header crunched without replacement."); + "Insufficient memory. Accept-Language header crunched without replacement."); } else { @@ -2819,7 +2887,7 @@ static jb_err crunch_client_header(struct client_state *csp, char **header) /* 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); + log_error(LOG_LEVEL_HEADER, "Crunching client header: %s (contains: %s)", *header, crunch_pattern); freez(*header); } } @@ -2934,7 +3002,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 +3039,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); @@ -3124,20 +3192,13 @@ static jb_err client_host(struct client_state *csp, char **header) return JB_ERR_OK; } - if (!csp->http->hostport || (*csp->http->hostport == '*') || + if (!csp->http->hostport || (*csp->http->hostport == '*') || *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; @@ -3190,13 +3251,13 @@ static jb_err client_if_modified_since(struct client_state *csp, char **header) struct tm gmt; #endif struct tm *timeptr = NULL; - time_t tm = 0; + time_t tm = 0; 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, * the problem is gone and the client now tries to revalidate our * error message on the real server. The revalidation would always @@ -3236,7 +3297,7 @@ static jb_err client_if_modified_since(struct client_state *csp, char **header) *header, rtime, (rtime == 1 || rtime == -1) ? "e": "es"); if (negative_range) { - rtime *= -1; + rtime *= -1; } rtime *= 60; rtime = pick_from_range(rtime); @@ -3273,7 +3334,7 @@ static jb_err client_if_modified_since(struct client_state *csp, char **header) if (*header == NULL) { log_error(LOG_LEVEL_HEADER, "Insufficient memory, header crunched without replacement."); - return JB_ERR_MEMORY; + return JB_ERR_MEMORY; } hours = rtime / 3600; @@ -3313,7 +3374,7 @@ static jb_err client_if_modified_since(struct client_state *csp, char **header) static jb_err client_if_none_match(struct client_state *csp, char **header) { if (csp->action->flags & ACTION_CRUNCH_IF_NONE_MATCH) - { + { log_error(LOG_LEVEL_HEADER, "Crunching %s", *header); freez(*header); } @@ -3341,7 +3402,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)) { @@ -3355,7 +3416,7 @@ jb_err client_x_filter(struct client_state *csp, char **header) "force-text-mode overruled the client's request to fetch without filtering!"); } else - { + { 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."); @@ -3364,7 +3425,7 @@ jb_err client_x_filter(struct client_state *csp, char **header) freez(*header); } } - return JB_ERR_OK; + return JB_ERR_OK; } @@ -3373,11 +3434,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,14 +3466,15 @@ 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); freez(*header); } - return JB_ERR_OK; + return JB_ERR_OK; } /* the following functions add headers directly to the header list */ @@ -3428,7 +3504,7 @@ 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."); @@ -3438,7 +3514,7 @@ static jb_err client_host_adder(struct client_state *csp) /* * remove 'user:pass@' from 'proto://user:pass@host' */ - if ( (p = strchr( csp->http->hostport, '@')) != NULL ) + if ((p = strchr( csp->http->hostport, '@')) != NULL) { p++; } @@ -3455,36 +3531,6 @@ static jb_err client_host_adder(struct client_state *csp) } -#if 0 -/********************************************************************* - * - * Function : client_accept_encoding_adder - * - * Description : Add an Accept-Encoding header to the client's request - * that disables compression if the action applies, and - * the header is not already there. Called from `sed'. - * Note: For HTTP/1.0, the absence of the header is enough. - * - * Parameters : - * 1 : csp = Current client state (buffers, headers, etc...) - * - * Returns : JB_ERR_OK on success, or - * JB_ERR_MEMORY on out-of-memory error. - * - *********************************************************************/ -static jb_err client_accept_encoding_adder(struct client_state *csp) -{ - if ( ((csp->action->flags & ACTION_NO_COMPRESSION) != 0) - && (!strcmpic(csp->http->ver, "HTTP/1.1")) ) - { - return enlist_unique(csp->headers, "Accept-Encoding: identity;q=1.0, *;q=0", 16); - } - - return JB_ERR_OK; -} -#endif - - /********************************************************************* * * Function : client_xtra_adder @@ -3601,7 +3647,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."); @@ -3621,8 +3667,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...) @@ -3675,6 +3720,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")) { @@ -3767,16 +3813,16 @@ 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_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 ((csp->action->flags & ACTION_SESSION_COOKIES_ONLY) != 0) { /* Flag whether or not to log a message */ int changed = 0; @@ -3788,7 +3834,7 @@ static jb_err server_set_cookie(struct client_state *csp, char **header) cur_tag = *header + 11; /* skip whitespace between "Set-Cookie:" and value */ - while (*cur_tag && ijb_isspace(*cur_tag)) + while (*cur_tag && privoxy_isspace(*cur_tag)) { cur_tag++; } @@ -3804,7 +3850,7 @@ static jb_err server_set_cookie(struct client_state *csp, char **header) next_tag++; /* skip whitespace ";" and start of tag */ - while (*next_tag && ijb_isspace(*next_tag)) + while (*next_tag && privoxy_isspace(*next_tag)) { next_tag++; } @@ -3977,7 +4023,7 @@ int strclean(char *string, const char *substring) * to get the numerical respresentation. * * Parameters : - * 1 : header_time = HTTP header time as string. + * 1 : header_time = HTTP header time as string. * 2 : result = storage for header_time in seconds * * Returns : JB_ERR_OK if the time format was recognized, or @@ -3987,34 +4033,49 @@ int strclean(char *string, const char *substring) static jb_err parse_header_time(const char *header_time, time_t *result) { struct tm gmt; - /* - * 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. + * Checking for two-digit years first in an + * attempt to work around GNU libc's strptime() + * reporting negative year values when using %Y. */ - 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))) + static const char * const time_formats[] = { + /* Tue, 02-Jun-37 20:00:00 */ + "%a, %d-%b-%y %H:%M:%S", + /* Tue, 02 Jun 2037 20:00:00 */ + "%a, %d %b %Y %H:%M:%S", + /* Tue, 02-Jun-2037 20:00:00 */ + "%a, %d-%b-%Y %H:%M:%S", + /* Tuesday, 02-Jun-2037 20:00:00 */ + "%A, %d-%b-%Y %H:%M:%S", + /* Tuesday Jun 02 20:00:00 2037 */ + "%A %b %d %H:%M:%S %Y" + }; + unsigned int i; + + for (i = 0; i < SZ(time_formats); i++) { - return JB_ERR_PARSE; - } + /* + * Zero out gmt to prevent time zone offsets. + * Documented to be required for GNU libc. + */ + memset(&gmt, 0, sizeof(gmt)); - *result = timegm(&gmt); + if (NULL != strptime(header_time, time_formats[i], &gmt)) + { + /* Sanity check for GNU libc. */ + if (gmt.tm_year < 0) + { + log_error(LOG_LEVEL_HEADER, + "Failed to parse '%s' using '%s'. Moving on.", + header_time, time_formats[i]); + continue; + } + *result = timegm(&gmt); + return JB_ERR_OK; + } + } - return JB_ERR_OK; + return JB_ERR_PARSE; } @@ -4033,9 +4094,9 @@ static jb_err parse_header_time(const char *header_time, time_t *result) * Parameters : * 1 : headers = List of headers (one of them hopefully being * the "Host:" header) - * 2 : http = storage for the result (host, port and hostport). + * 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. * @@ -4054,19 +4115,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; @@ -4107,8 +4158,8 @@ jb_err get_destination_from_headers(const struct list *headers, struct http_requ * Function : create_forged_referrer * * Description : Helper for client_referrer to forge a referer as - * 'http://[hostname:port/' to fool stupid - * checks for in-site links + * 'http://hostname[:port]/' to fool stupid + * checks for in-site links * * Parameters : * 1 : header = Pointer to header pointer @@ -4198,16 +4249,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)) { @@ -4262,6 +4307,39 @@ static void create_content_length_header(unsigned long long content_length, } +/********************************************************************* + * + * 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; +} + + /* Local Variables: tab-width: 3