X-Git-Url: http://www.privoxy.org/gitweb/?p=privoxy.git;a=blobdiff_plain;f=parsers.c;h=14aa99ea8cbbb3a603e8501a706fd281fab97fed;hp=9fe2da77f6b1c5126753f1717647a08975955689;hb=a2f8296e90e9bc3c4e4fa50c9b42e3f12bce24ca;hpb=d26129baa47eb9e558d53f463ad18269dfc99607 diff --git a/parsers.c b/parsers.c index 9fe2da77..14aa99ea 100644 --- a/parsers.c +++ b/parsers.c @@ -1,12 +1,11 @@ -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-2014 the - * Privoxy team. http://www.privoxy.org/ + * Copyright : Written by and Copyright (C) 2001-2021 the + * Privoxy team. https://www.privoxy.org/ * * Based on the Internet Junkbuster originally written * by and Copyright (C) 1997 Anonymous Coders and @@ -65,8 +64,11 @@ const char parsers_rcs[] = "$Id: parsers.c,v 1.302 2015/12/27 12:54:12 fabiankei #define GZIP_FLAG_COMMENT 0x10 #define GZIP_FLAG_RESERVED_BITS 0xe0 #endif +#ifdef FEATURE_BROTLI +#include +#endif -#if !defined(_WIN32) && !defined(__OS2__) +#if !defined(_WIN32) #include #endif @@ -85,13 +87,14 @@ const char parsers_rcs[] = "$Id: parsers.c,v 1.302 2015/12/27 12:54:12 fabiankei #include "list.h" #include "actions.h" #include "filters.h" +#ifdef FEATURE_HTTPS_INSPECTION +#include "ssl.h" +#endif #ifndef HAVE_STRPTIME #include "strptime.h" #endif -const char parsers_h_rcs[] = PARSERS_H_VERSION; - 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); @@ -138,10 +141,10 @@ static jb_err server_save_content_length(struct client_state *csp, char **header 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_save_content_length(struct client_state *csp, char **header); static jb_err client_host_adder (struct client_state *csp); static jb_err client_xtra_adder (struct client_state *csp); static jb_err client_x_forwarded_for_adder(struct client_state *csp); @@ -185,9 +188,9 @@ static const struct parsers client_patterns[] = { { "TE:", 3, client_te }, { "Host:", 5, client_host }, { "if-modified-since:", 18, client_if_modified_since }, + { "Content-Length:", 15, client_save_content_length }, #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 }, @@ -252,13 +255,14 @@ static const add_header_func_ptr add_server_headers[] = { /********************************************************************* * - * Function : flush_socket + * Function : flush_iob * * Description : Write any pending "buffered" content. * * Parameters : * 1 : fd = file descriptor of the socket to read * 2 : iob = The I/O buffer to flush, usually csp->iob. + * 3 : delay = Number of milliseconds to delay the writes * * Returns : On success, the number of bytes written are returned (zero * indicates nothing was written). On error, -1 is returned, @@ -268,7 +272,7 @@ static const add_header_func_ptr add_server_headers[] = { * file, the results are not portable. * *********************************************************************/ -long flush_socket(jb_socket fd, struct iob *iob) +long flush_iob(jb_socket fd, struct iob *iob, unsigned int delay) { long len = iob->eod - iob->cur; @@ -277,7 +281,7 @@ long flush_socket(jb_socket fd, struct iob *iob) return(0); } - if (write_socket(fd, iob->cur, (size_t)len)) + if (write_socket_delayed(fd, iob->cur, (size_t)len, delay)) { return(-1); } @@ -287,6 +291,27 @@ long flush_socket(jb_socket fd, struct iob *iob) } +/********************************************************************* + * + * Function : can_add_to_iob + * + * Description : Checks if the given number of bytes can be added to the given iob + * without exceeding the given buffer limit. + * + * Parameters : + * 1 : iob = Destination buffer. + * 2 : buffer_limit = Limit to which the destination may grow + * 3 : n = number of bytes to be added + * + * Returns : TRUE if the given iob can handle given number of bytes + * FALSE buffer limit will be exceeded + * + *********************************************************************/ +int can_add_to_iob(const struct iob *iob, const size_t buffer_limit, size_t n) +{ + return ((size_t)(iob->eod - iob->buf) + n + 1) > buffer_limit ? FALSE : TRUE; +} + /********************************************************************* * * Function : add_to_iob @@ -304,7 +329,7 @@ long flush_socket(jb_socket fd, struct iob *iob) * or buffer limit reached. * *********************************************************************/ -jb_err add_to_iob(struct iob *iob, const size_t buffer_limit, char *src, long n) +jb_err add_to_iob(struct iob *iob, const size_t buffer_limit, const char *src, long n) { size_t used, offset, need; char *p; @@ -322,7 +347,7 @@ jb_err add_to_iob(struct iob *iob, const size_t buffer_limit, char *src, long n) if (need > buffer_limit) { log_error(LOG_LEVEL_INFO, - "Buffer limit reached while extending the buffer (iob). Needed: %d. Limit: %d", + "Buffer limit reached while extending the buffer (iob). Needed: %lu. Limit: %lu", need, buffer_limit); return JB_ERR_MEMORY; } @@ -380,18 +405,98 @@ jb_err add_to_iob(struct iob *iob, const size_t buffer_limit, char *src, long n) * 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. + * Returns : N/A * *********************************************************************/ void clear_iob(struct iob *iob) { free(iob->buf); - memset(iob, '\0', sizeof(*iob));; + memset(iob, '\0', sizeof(*iob)); } #ifdef FEATURE_ZLIB +#ifdef FEATURE_BROTLI +/********************************************************************* + * + * Function : decompress_iob_with_brotli + * + * Description : Decompress buffered page using Brotli. + * + * Parameters : + * 1 : csp = Current client state (buffers, headers, etc...) + * + * Returns : JB_ERR_OK on success, + * JB_ERR_MEMORY if out-of-memory limit reached, and + * JB_ERR_COMPRESS if error decompressing buffer. + * + *********************************************************************/ +static jb_err decompress_iob_with_brotli(struct client_state *csp) +{ + BrotliDecoderResult result; + char *decoded_buffer; + size_t decoded_size; + size_t decoded_buffer_size; + size_t encoded_size; + enum { MAX_COMPRESSION_FACTOR = 15 }; + + encoded_size = (size_t)(csp->iob->eod - csp->iob->cur); + /* + * The BrotliDecoderDecompress() api is a bit unfortunate + * and requires the caller to reserve enough memory for + * the decompressed content. Hopefully reserving + * MAX_COMPRESSION_FACTOR times the original size is + * sufficient. If not, BrotliDecoderDecompress() will fail. + */ + decoded_buffer_size = encoded_size * MAX_COMPRESSION_FACTOR; + + if (decoded_buffer_size > csp->config->buffer_limit) + { + log_error(LOG_LEVEL_ERROR, + "Buffer limit reached before decompressing iob with Brotli"); + return JB_ERR_MEMORY; + } + + decoded_buffer = malloc(decoded_buffer_size); + if (decoded_buffer == NULL) + { + log_error(LOG_LEVEL_ERROR, + "Failed to allocate %lu bytes for Brotli decompression", + decoded_buffer_size); + return JB_ERR_MEMORY; + } + + decoded_size = decoded_buffer_size; + result = BrotliDecoderDecompress(encoded_size, + (const uint8_t *)csp->iob->cur, &decoded_size, + (uint8_t *)decoded_buffer); + if (result == BROTLI_DECODER_RESULT_SUCCESS) + { + /* + * Update the iob, since the decompression was successful. + */ + freez(csp->iob->buf); + csp->iob->buf = decoded_buffer; + csp->iob->cur = csp->iob->buf; + csp->iob->eod = csp->iob->cur + decoded_size; + csp->iob->size = decoded_buffer_size; + + log_error(LOG_LEVEL_RE_FILTER, + "Decompression successful. Old size: %lu, new size: %lu.", + encoded_size, decoded_size); + + return JB_ERR_OK; + } + else + { + log_error(LOG_LEVEL_ERROR, "Failed to decompress buffer with Brotli"); + freez(decoded_buffer); + + return JB_ERR_COMPRESS; + } +} +#endif + /********************************************************************* * * Function : decompress_iob @@ -421,8 +526,13 @@ jb_err decompress_iob(struct client_state *csp) int status; /* return status of the inflate() call */ z_stream zstr; /* used by calls to zlib */ +#ifdef FUZZ + assert(csp->iob->cur - csp->iob->buf >= 0); + assert(csp->iob->eod - csp->iob->cur >= 0); +#else assert(csp->iob->cur - csp->iob->buf > 0); assert(csp->iob->eod - csp->iob->cur > 0); +#endif bufsize = csp->iob->size; skip_size = (size_t)(csp->iob->cur - csp->iob->buf); @@ -437,11 +547,18 @@ jb_err decompress_iob(struct client_state *csp) * but it should(?) be valid for deflated data also. */ log_error(LOG_LEVEL_ERROR, - "Insufficient data to start decompression. Bytes in buffer: %d", + "Insufficient data to start decompression. Bytes in buffer: %ld", csp->iob->eod - csp->iob->cur); return JB_ERR_COMPRESS; } +#ifdef FEATURE_BROTLI + if (csp->content_type & CT_BROTLI) + { + return decompress_iob_with_brotli(csp); + } +#endif + if (csp->content_type & CT_GZIP) { /* @@ -630,6 +747,8 @@ jb_err decompress_iob(struct client_state *csp) if (bufsize >= csp->config->buffer_limit) { log_error(LOG_LEVEL_ERROR, "Buffer limit reached while decompressing iob"); + freez(buf); + inflateEnd(&zstr); return JB_ERR_MEMORY; } @@ -648,6 +767,7 @@ jb_err decompress_iob(struct client_state *csp) { log_error(LOG_LEVEL_ERROR, "Out of memory decompressing iob"); freez(buf); + inflateEnd(&zstr); return JB_ERR_MEMORY; } else @@ -718,7 +838,7 @@ jb_err decompress_iob(struct client_state *csp) * Make sure the new uncompressed iob obeys some minimal * consistency conditions. */ - if ((csp->iob->buf < csp->iob->cur) + if ((csp->iob->buf <= csp->iob->cur) && (csp->iob->cur <= csp->iob->eod) && (csp->iob->eod <= csp->iob->buf + csp->iob->size)) { @@ -726,22 +846,20 @@ jb_err decompress_iob(struct client_state *csp) if (new_size > (size_t)0) { log_error(LOG_LEVEL_RE_FILTER, - "Decompression successful. Old size: %d, new size: %d.", + "Decompression successful. Old size: %lu, new size: %lu.", old_size, new_size); } else { - /* zlib thinks this is OK, so lets do the same. */ - log_error(LOG_LEVEL_INFO, "Decompression didn't result in any content."); + /* zlib thinks this is OK, so let's do the same. */ + log_error(LOG_LEVEL_RE_FILTER, + "Decompression didn't result in any content."); } } else { /* It seems that zlib did something weird. */ - log_error(LOG_LEVEL_ERROR, - "Unexpected error decompressing the buffer (iob): %d==%d, %d>%d, %d<%d", - csp->iob->cur, csp->iob->buf + skip_size, csp->iob->eod, csp->iob->buf, - csp->iob->eod, csp->iob->buf + csp->iob->size); + log_error(LOG_LEVEL_ERROR, "Inconsistent buffer after decompression"); return JB_ERR_COMPRESS; } @@ -1093,8 +1211,8 @@ static void enforce_header_order(struct list *headers, const struct list *ordere } list_remove_all(headers); - list_duplicate(headers, new_headers); - list_remove_all(new_headers); + headers->first = new_headers->first; + headers->last = new_headers->last; return; } @@ -1173,7 +1291,9 @@ jb_err sed(struct client_state *csp, int filter_server_headers) f++; } - if (!filter_server_headers && !list_is_empty(csp->config->ordered_client_headers)) + if (!filter_server_headers && + !list_is_empty(csp->config->ordered_client_headers) && + csp->headers->first->str != NULL) { enforce_header_order(csp->headers, csp->config->ordered_client_headers); } @@ -1182,6 +1302,67 @@ jb_err sed(struct client_state *csp, int filter_server_headers) } +#ifdef FEATURE_HTTPS_INSPECTION +/********************************************************************* + * + * Function : sed_https + * + * Description : add, delete or modify lines in the HTTPS client + * header streams. Wrapper around sed(). + * + * Parameters : + * 1 : csp = Current client state (buffers, headers, etc...) + * + * Returns : JB_ERR_OK in case off success, or + * JB_ERR_MEMORY on some out-of-memory errors, or + * JB_ERR_PARSE in case of fatal parse errors. + * + *********************************************************************/ +jb_err sed_https(struct client_state *csp) +{ + jb_err err; + struct list headers; + + /* + * Temporarily replace csp->headers with csp->https_headers + * to trick sed() into filtering the https headers. + */ + headers.first = csp->headers->first; + headers.last = csp->headers->last; + csp->headers->first = csp->https_headers->first; + csp->headers->last = csp->https_headers->last; + + /* + * Start with fresh tags. Already existing tags may + * be set again. This is necessary to overrule + * URL-based patterns. + */ + destroy_list(csp->tags); + + /* + * We want client header filters and taggers + * so temporarily remove the flag. + */ + csp->flags &= ~CSP_FLAG_CLIENT_HEADER_PARSING_DONE; + err = sed(csp, FILTER_CLIENT_HEADERS); + csp->flags |= CSP_FLAG_CLIENT_HEADER_PARSING_DONE; + + /* + * Update the https headers list which may have + * been modified due to header additions or header + * reordering. + */ + csp->https_headers->first = csp->headers->first; + csp->https_headers->last = csp->headers->last; + + csp->headers->first = headers.first; + csp->headers->last = headers.last; + + return err; +} +#endif /* def FEATURE_HTTPS_INSPECTION */ + + /********************************************************************* * * Function : update_server_headers @@ -1332,7 +1513,7 @@ static jb_err header_tagger(struct client_state *csp, char *header) if (NULL == joblist) { - log_error(LOG_LEVEL_RE_FILTER, + log_error(LOG_LEVEL_TAGGING, "Tagger %s has empty joblist. Nothing to do.", b->name); continue; } @@ -1360,7 +1541,7 @@ static jb_err header_tagger(struct client_state *csp, char *header) assert(NULL != header); log_error(LOG_LEVEL_ERROR, "Problems with tagger \'%s\' and header \'%s\': %s", - b->name, *header, pcrs_strerror(hits)); + b->name, header, pcrs_strerror(hits)); } freez(modified_tag); } @@ -1379,11 +1560,20 @@ static jb_err header_tagger(struct client_state *csp, char *header) * no one would do it intentionally. */ freez(tag); - log_error(LOG_LEVEL_INFO, + log_error(LOG_LEVEL_TAGGING, "Tagger \'%s\' created an empty tag. Ignored.", b->name); continue; } + if (list_contains_item(csp->action->multi[ACTION_MULTI_SUPPRESS_TAG], tag)) + { + log_error(LOG_LEVEL_TAGGING, + "Tagger \'%s\' didn't add tag \'%s\': suppressed", + b->name, tag); + freez(tag); + continue; + } + if (!list_contains_item(csp->tags, tag)) { if (JB_ERR_OK != enlist(csp->tags, tag)) @@ -1391,7 +1581,7 @@ static jb_err header_tagger(struct client_state *csp, char *header) log_error(LOG_LEVEL_ERROR, "Insufficient memory to add tag \'%s\', " "based on tagger \'%s\' and header \'%s\'", - tag, b->name, *header); + tag, b->name, header); } else { @@ -1410,7 +1600,7 @@ static jb_err header_tagger(struct client_state *csp, char *header) action_message = "No action bits update necessary."; } - log_error(LOG_LEVEL_HEADER, + log_error(LOG_LEVEL_TAGGING, "Tagger \'%s\' added tag \'%s\'. %s", b->name, tag, action_message); } @@ -1418,7 +1608,7 @@ static jb_err header_tagger(struct client_state *csp, char *header) else { /* XXX: Is this log-worthy? */ - log_error(LOG_LEVEL_HEADER, + log_error(LOG_LEVEL_TAGGING, "Tagger \'%s\' didn't add tag \'%s\'. Tag already present", b->name, tag); } @@ -1507,11 +1697,12 @@ static jb_err filter_header(struct client_state *csp, char **header) if (NULL == joblist) { - log_error(LOG_LEVEL_RE_FILTER, "Filter %s has empty joblist. Nothing to do.", b->name); + log_error(LOG_LEVEL_RE_FILTER, + "Filter %s has empty joblist. Nothing to do.", b->name); continue; } - log_error(LOG_LEVEL_RE_FILTER, "filtering \'%s\' (size %d) with \'%s\' ...", + log_error(LOG_LEVEL_RE_FILTER, "filtering \'%s\' (size %lu) with \'%s\' ...", *header, size, b->name); /* Apply all jobs from the joblist */ @@ -1521,7 +1712,8 @@ static jb_err filter_header(struct client_state *csp, char **header) if (0 < matches) { current_hits += matches; - log_error(LOG_LEVEL_HEADER, "Transforming \"%s\" to \"%s\"", *header, newheader); + log_error(LOG_LEVEL_HEADER, + "Transforming \"%s\" to \"%s\"", *header, newheader); freez(*header); *header = newheader; } @@ -1533,7 +1725,8 @@ static jb_err filter_header(struct client_state *csp, char **header) else { /* RegEx failure */ - log_error(LOG_LEVEL_ERROR, "Filtering \'%s\' with \'%s\' didn't work out: %s", + log_error(LOG_LEVEL_ERROR, + "Filtering \'%s\' with \'%s\' didn't work out: %s", *header, b->name, pcrs_strerror(matches)); if (newheader != NULL) { @@ -1545,7 +1738,8 @@ static jb_err filter_header(struct client_state *csp, char **header) if (b->dynamic) pcrs_free_joblist(joblist); - log_error(LOG_LEVEL_RE_FILTER, "... produced %d hits (new size %d).", current_hits, size); + log_error(LOG_LEVEL_RE_FILTER, + "... produced %d hits (new size %lu).", current_hits, size); hits += current_hits; } @@ -1693,6 +1887,7 @@ static jb_err server_proxy_connection(struct client_state *csp, char **header) csp->flags |= CSP_FLAG_SERVER_PROXY_CONNECTION_HEADER_SET; return JB_ERR_OK; } +#endif /* def FEATURE_CONNECTION_KEEP_ALIVE */ /********************************************************************* @@ -1725,6 +1920,7 @@ static jb_err proxy_authentication(struct client_state *csp, char **header) } +#ifdef FEATURE_CONNECTION_KEEP_ALIVE /********************************************************************* * * Function : client_keep_alive @@ -1791,6 +1987,7 @@ static jb_err client_keep_alive(struct client_state *csp, char **header) return JB_ERR_OK; } +#endif /* def FEATURE_CONNECTION_KEEP_ALIVE */ /********************************************************************* @@ -1811,7 +2008,9 @@ static jb_err client_keep_alive(struct client_state *csp, char **header) static jb_err get_content_length(const char *header_value, unsigned long long *length) { #ifdef _WIN32 - assert(sizeof(unsigned long long) > 4); +#if SIZEOF_LONG_LONG < 8 +#error sizeof(unsigned long long) too small +#endif if (1 != sscanf(header_value, "%I64u", length)) #else if (1 != sscanf(header_value, "%llu", length)) @@ -1832,10 +2031,7 @@ static jb_err get_content_length(const char *header_value, unsigned long long *l * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) - * 2 : header = On input, pointer to header to modify. - * On output, pointer to the modified header, or NULL - * to remove the header. This function frees the - * original string if necessary. + * 2 : header = pointer to the Content-Length header * * Returns : JB_ERR_OK on success, or * JB_ERR_MEMORY on out-of-memory error. @@ -1861,8 +2057,6 @@ static jb_err client_save_content_length(struct client_state *csp, char **header return JB_ERR_OK; } -#endif /* def FEATURE_CONNECTION_KEEP_ALIVE */ - /********************************************************************* @@ -1891,9 +2085,13 @@ static jb_err client_connection(struct client_state *csp, char **header) { #ifdef FEATURE_CONNECTION_KEEP_ALIVE if ((csp->config->feature_flags & RUNTIME_FEATURE_CONNECTION_SHARING) - && !(csp->flags & CSP_FLAG_SERVER_SOCKET_TAINTED)) + && !(csp->flags & CSP_FLAG_SERVER_SOCKET_TAINTED) +#ifdef FEATURE_HTTPS_INSPECTION + && !client_use_ssl(csp) +#endif + ) { - if (!strcmpic(csp->http->ver, "HTTP/1.1")) + if (!strcmpic(csp->http->version, "HTTP/1.1")) { log_error(LOG_LEVEL_HEADER, "Removing \'%s\' to imply keep-alive.", *header); @@ -2344,6 +2542,15 @@ static jb_err server_content_encoding(struct client_state *csp, char **header) /* Mark for zlib decompression */ csp->content_type |= CT_DEFLATE; } + else if (strstr(*header, "br")) + { +#ifdef FEATURE_BROTLI + /* Mark for Brotli decompression */ + csp->content_type |= CT_BROTLI; +#else + csp->content_type |= CT_TABOO; +#endif + } else if (strstr(*header, "compress")) { /* @@ -2372,8 +2579,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, "Compressed content detected, content filtering disabled. " @@ -2395,7 +2601,7 @@ static jb_err server_content_encoding(struct client_state *csp, char **header) * * Description : Remove the Content-Encoding header if the * decompression was successful and the content - * has been modifed. + * has been modified. * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) @@ -2411,7 +2617,12 @@ static jb_err server_content_encoding(struct client_state *csp, char **header) static jb_err server_adjust_content_encoding(struct client_state *csp, char **header) { if ((csp->flags & CSP_FLAG_MODIFIED) - && (csp->content_type & (CT_GZIP | CT_DEFLATE))) + && ((csp->content_type & (CT_GZIP | CT_DEFLATE)) +#ifdef FEATURE_BROTLI + || (csp->content_type & CT_BROTLI) +#endif + ) + ) { /* * We successfully decompressed the content, @@ -2431,6 +2642,37 @@ static jb_err server_adjust_content_encoding(struct client_state *csp, char **he #endif /* defined(FEATURE_ZLIB) */ +/********************************************************************* + * + * Function : header_adjust_content_length + * + * Description : Replace given header with new Content-Length header. + * + * Parameters : + * 1 : header = On input, pointer to header to modify. + * On output, pointer to the modified header, or NULL + * to remove the header. This function frees the + * original string if necessary. + * 2 : content_length = content length value to set + * + * Returns : JB_ERR_OK on success, or + * JB_ERR_MEMORY on out-of-memory error. + * + *********************************************************************/ +jb_err header_adjust_content_length(char **header, size_t content_length) +{ + const size_t header_length = 50; + freez(*header); + *header = malloc(header_length); + if (*header == NULL) + { + return JB_ERR_MEMORY; + } + create_content_length_header(content_length, *header, header_length); + return JB_ERR_OK; +} + + /********************************************************************* * * Function : server_adjust_content_length @@ -2454,14 +2696,10 @@ static jb_err server_adjust_content_length(struct client_state *csp, char **head /* Regenerate header if the content was modified. */ if (csp->flags & CSP_FLAG_MODIFIED) { - const size_t header_length = 50; - freez(*header); - *header = malloc(header_length); - if (*header == NULL) + if (JB_ERR_OK != header_adjust_content_length(header, csp->content_length)) { return JB_ERR_MEMORY; } - create_content_length_header(csp->content_length, *header, header_length); log_error(LOG_LEVEL_HEADER, "Adjusted Content-Length to %llu", csp->content_length); } @@ -2684,9 +2922,8 @@ static jb_err server_last_modified(struct client_state *csp, char **header) time_t now; struct tm *timeptr = NULL; long int rtime; -#ifdef HAVE_GMTIME_R struct tm gmt; -#endif + now = time(NULL); rtime = (long int)difftime(now, last_modified); if (rtime) @@ -2705,15 +2942,7 @@ static jb_err server_last_modified(struct client_state *csp, char **header) rtime *= -1; } last_modified += rtime; -#ifdef HAVE_GMTIME_R - timeptr = gmtime_r(&last_modified, &gmt); -#elif defined(MUTEX_LOCKS_AVAILABLE) - privoxy_mutex_lock(&gmtime_mutex); - timeptr = gmtime(&last_modified); - privoxy_mutex_unlock(&gmtime_mutex); -#else - timeptr = gmtime(&last_modified); -#endif + timeptr = privoxy_gmtime_r(&last_modified, &gmt); if ((NULL == timeptr) || !strftime(newheader, sizeof(newheader), "%a, %d %b %Y %H:%M:%S GMT", timeptr)) { @@ -2723,7 +2952,6 @@ static jb_err server_last_modified(struct client_state *csp, char **header) freez(*header); return JB_ERR_OK; } - freez(*header); *header = strdup("Last-Modified: "); string_append(header, newheader); @@ -2740,7 +2968,7 @@ static jb_err server_last_modified(struct client_state *csp, char **header) seconds = rtime % 60; log_error(LOG_LEVEL_HEADER, - "Randomized: %s (added %d da%s %d hou%s %d minut%s %d second%s", + "Randomized: %s (added %ld da%s %ld hou%s %ld minut%s %ld second%s", *header, days, (days == 1) ? "y" : "ys", hours, (hours == 1) ? "r" : "rs", minutes, (minutes == 1) ? "e" : "es", seconds, (seconds == 1) ? ")" : "s)"); } @@ -3349,9 +3577,7 @@ static jb_err client_host(struct client_state *csp, char **header) static jb_err client_if_modified_since(struct client_state *csp, char **header) { char newheader[50]; -#ifdef HAVE_GMTIME_R struct tm gmt; -#endif struct tm *timeptr = NULL; time_t tm = 0; const char *newval; @@ -3394,7 +3620,7 @@ static jb_err client_if_modified_since(struct client_state *csp, char **header) if (rtime) { - log_error(LOG_LEVEL_HEADER, "Randomizing: %s (random range: %d minut%s)", + log_error(LOG_LEVEL_HEADER, "Randomizing: %s (random range: %ld minut%s)", *header, rtime, (rtime == 1 || rtime == -1) ? "e": "es"); if (negative_range) { @@ -3405,19 +3631,11 @@ static jb_err client_if_modified_since(struct client_state *csp, char **header) } else { - log_error(LOG_LEVEL_ERROR, "Random range is 0. Assuming time transformation test.", - *header); + log_error(LOG_LEVEL_ERROR, + "Random range is 0. Assuming time transformation test."); } tm += rtime * (negative_range ? -1 : 1); -#ifdef HAVE_GMTIME_R - timeptr = gmtime_r(&tm, &gmt); -#elif defined(MUTEX_LOCKS_AVAILABLE) - privoxy_mutex_lock(&gmtime_mutex); - timeptr = gmtime(&tm); - privoxy_mutex_unlock(&gmtime_mutex); -#else - timeptr = gmtime(&tm); -#endif + timeptr = privoxy_gmtime_r(&tm, &gmt); if ((NULL == timeptr) || !strftime(newheader, sizeof(newheader), "%a, %d %b %Y %H:%M:%S GMT", timeptr)) { @@ -3427,7 +3645,6 @@ static jb_err client_if_modified_since(struct client_state *csp, char **header) freez(*header); return JB_ERR_OK; } - freez(*header); *header = strdup("If-Modified-Since: "); string_append(header, newheader); @@ -3443,7 +3660,7 @@ static jb_err client_if_modified_since(struct client_state *csp, char **header) seconds = rtime % 60; log_error(LOG_LEVEL_HEADER, - "Randomized: %s (%s %d hou%s %d minut%s %d second%s", + "Randomized: %s (%s %ld hou%s %ld minut%s %ld second%s", *header, (negative_range) ? "subtracted" : "added", hours, (hours == 1) ? "r" : "rs", minutes, (minutes == 1) ? "e" : "es", seconds, (seconds == 1) ? ")" : "s)"); @@ -3785,7 +4002,12 @@ static jb_err server_proxy_connection_adder(struct client_state *csp) && !(csp->flags & CSP_FLAG_SERVER_SOCKET_TAINTED) && !(csp->flags & CSP_FLAG_SERVER_PROXY_CONNECTION_HEADER_SET) && ((csp->flags & CSP_FLAG_SERVER_CONTENT_LENGTH_SET) - || (csp->flags & CSP_FLAG_CHUNKED))) + || (csp->flags & CSP_FLAG_CHUNKED)) +#ifdef FEATURE_HTTPS_INSPECTION + && !client_use_ssl(csp) + && !csp->http->ssl +#endif + ) { log_error(LOG_LEVEL_HEADER, "Adding: %s", proxy_connection_header); err = enlist(csp->headers, proxy_connection_header); @@ -3801,7 +4023,8 @@ static jb_err server_proxy_connection_adder(struct client_state *csp) * Function : client_connection_header_adder * * Description : Adds a proper "Connection:" header to csp->headers - * unless the header was already present. Called from `sed'. + * unless the header was already present or it's a + * CONNECT request. Called from `sed'. * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) @@ -3820,11 +4043,21 @@ static jb_err client_connection_header_adder(struct client_state *csp) return JB_ERR_OK; } + /* + * In case of CONNECT requests "Connection: close" is implied, + * but actually setting the header has been reported to cause + * problems with some forwarding proxies that close the + * connection prematurely. + */ + if (csp->http->ssl != 0) + { + return JB_ERR_OK; + } + #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")) + && !strcmpic(csp->http->version, "HTTP/1.1")) { csp->flags |= CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE; return JB_ERR_OK; @@ -3895,7 +4128,8 @@ static jb_err server_http(struct client_state *csp, char **header) return JB_ERR_PARSE; } - if (csp->http->status == 206) + if (csp->http->status == 101 || + csp->http->status == 206) { csp->content_type = CT_TABOO; } @@ -3957,18 +4191,9 @@ 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 - + timeptr = privoxy_gmtime_r(&expiry_date, &gmt); if (NULL == timeptr) { log_error(LOG_LEVEL_FATAL, @@ -4311,10 +4536,17 @@ static jb_err parse_header_time(const char *header_time, time_t *result) { char recreated_date[100]; struct tm *tm; + struct tm storage; time_t result2; - tm = gmtime(result); - strftime(recreated_date, sizeof(recreated_date), time_formats[i], tm); + tm = privoxy_gmtime_r(result, &storage); + if (!strftime(recreated_date, sizeof(recreated_date), + time_formats[i], tm)) + { + log_error(LOG_LEVEL_ERROR, "Failed to recreate date '%s' with '%s'.", + header_time, time_formats[i]); + continue; + } memset(&gmt, 0, sizeof(gmt)); if (NULL == strptime(recreated_date, time_formats[i], &gmt)) { @@ -4327,7 +4559,7 @@ static jb_err parse_header_time(const char *header_time, time_t *result) if (*result != result2) { log_error(LOG_LEVEL_ERROR, "strftime() and strptime() disagree. " - "Format: '%s'. In: '%s', out: '%s'. %d != %d. Rejecting.", + "Format: '%s'. In: '%s', out: '%s'. %ld != %ld. Rejecting.", time_formats[i], header_time, recreated_date, *result, result2); continue; } @@ -4408,8 +4640,6 @@ 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) @@ -4418,7 +4648,11 @@ jb_err get_destination_from_headers(const struct list *headers, struct http_requ return JB_ERR_PARSE; } - p = strdup_or_die(host); + p = string_tolower(host); + if (p == NULL) + { + return JB_ERR_MEMORY; + } chomp(p); q = strdup_or_die(p); @@ -4448,13 +4682,13 @@ jb_err get_destination_from_headers(const struct list *headers, struct http_requ return JB_ERR_MEMORY; } - log_error(LOG_LEVEL_HEADER, "Destination extracted from \"Host:\" header. New request URL: %s", + 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); @@ -4462,18 +4696,101 @@ jb_err get_destination_from_headers(const struct list *headers, struct http_requ string_append(&http->cmd, " "); string_append(&http->cmd, http->url); string_append(&http->cmd, " "); - string_append(&http->cmd, http->ver); + string_append(&http->cmd, http->version); if (http->cmd == NULL) { return JB_ERR_MEMORY; } - log_error(LOG_LEVEL_HEADER, "Faked request-Line: %s", - http->cmd); + return JB_ERR_OK; + +} + + +#ifdef FEATURE_HTTPS_INSPECTION +/********************************************************************* + * + * Function : get_destination_from_https_headers + * + * Description : Parse the previously encrypted "Host:" header to + * get the request's destination. + * + * Parameters : + * 1 : headers = List of headers (one of them hopefully being + * the "Host:" header) + * 2 : http = storage for the result (host, port and hostport). + * + * 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. + * + *********************************************************************/ +jb_err get_destination_from_https_headers(const struct list *headers, struct http_request *http) +{ + char *q; + char *p; + char *host; + + host = get_header_value(headers, "Host:"); + + if (NULL == host) + { + log_error(LOG_LEVEL_ERROR, "No \"Host:\" header found."); + return JB_ERR_PARSE; + } + + p = string_tolower(host); + if (p == NULL) + { + return JB_ERR_MEMORY; + } + chomp(p); + q = strdup_or_die(p); + + freez(http->hostport); + http->hostport = p; + freez(http->host); + http->host = q; + q = strchr(http->host, ':'); + if (q != NULL) + { + /* Terminate hostname and evaluate port string */ + *q++ = '\0'; + http->port = atoi(q); + } + else + { + http->port = 443; + } + + /* Rebuild request URL */ + freez(http->url); + http->url = strdup_or_die(http->path); + + 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. + */ + 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->version); + if (http->cmd == NULL) + { + return JB_ERR_MEMORY; + } return JB_ERR_OK; } +#endif /* def FEATURE_HTTPS_INSPECTION */ /********************************************************************* @@ -4587,6 +4904,10 @@ static jb_err handle_conditional_hide_referrer_parameter(char **header, referer[hostlength+17] = '\0'; } referer_url = strstr(referer, "http://"); + if (NULL == referer_url) + { + referer_url = strstr(referer, "https://"); + } if ((NULL == referer_url) || (NULL == strstr(referer_url, host))) { /* Host has changed, Referer is invalid or a https URL. */ @@ -4626,11 +4947,17 @@ static jb_err handle_conditional_hide_referrer_parameter(char **header, static void create_content_length_header(unsigned long long content_length, char *header, size_t buffer_length) { +#ifdef _WIN32 +#if SIZEOF_LONG_LONG < 8 +#error sizeof(unsigned long long) too small +#endif + snprintf(header, buffer_length, "Content-Length: %I64u", content_length); +#else snprintf(header, buffer_length, "Content-Length: %llu", content_length); +#endif } -#ifdef FEATURE_CONNECTION_KEEP_ALIVE /********************************************************************* * * Function : get_expected_content_length @@ -4662,7 +4989,7 @@ unsigned long long get_expected_content_length(struct list *headers) return content_length; } -#endif + /* Local Variables: