X-Git-Url: http://www.privoxy.org/gitweb/?p=privoxy.git;a=blobdiff_plain;f=parsers.c;h=debda5bbc830013fa9409d7d37617c9e9c80bc56;hp=61f514dff704b9b9d284e94d61fc74d4a73a4176;hb=d26129baa47eb9e558d53f463ad18269dfc99607;hpb=2c4d197931326f9bbaef5258a0b6267f9e395310 diff --git a/parsers.c b/parsers.c index 61f514df..9fe2da77 100644 --- a/parsers.c +++ b/parsers.c @@ -1,23 +1,11 @@ -const char parsers_rcs[] = "$Id: parsers.c,v 1.110 2007/09/29 10:42:37 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. - * 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-2007 the SourceForge + * + * Copyright : Written by and Copyright (C) 2001-2014 the * Privoxy team. http://www.privoxy.org/ * * Based on the Internet Junkbuster originally written @@ -42,669 +30,8 @@ const char parsers_rcs[] = "$Id: parsers.c,v 1.110 2007/09/29 10:42:37 fabiankei * or write to the Free Software Foundation, Inc., 59 * Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - * Revisions : - * $Log: parsers.c,v $ - * Revision 1.110 2007/09/29 10:42:37 fabiankeil - * - Remove "scanning headers for" log message again. - * - Some more whitespace fixes. - * - * Revision 1.109 2007/09/08 14:25:48 fabiankeil - * Refactor client_referrer() and add conditional-forge parameter. - * - * Revision 1.108 2007/08/28 18:21:03 fabiankeil - * A bunch of whitespace fixes, pointy hat to me. - * - * Revision 1.107 2007/08/28 18:16:32 fabiankeil - * Fix possible memory corruption in server_http, make sure it's not - * executed for ordinary server headers and mark some problems for later. - * - * Revision 1.106 2007/08/18 14:30:32 fabiankeil - * Let content-type-overwrite{} honour force-text-mode again. - * - * Revision 1.105 2007/08/11 14:49:49 fabiankeil - * - Add prototpyes for the header parsers and make them static. - * - Comment out client_accept_encoding_adder() which isn't used right now. - * - * Revision 1.104 2007/07/14 07:38:19 fabiankeil - * Move the ACTION_FORCE_TEXT_MODE check out of - * server_content_type(). Signal other functions - * whether or not a content type has been declared. - * Part of the fix for BR#1750917. - * - * Revision 1.103 2007/06/01 16:31:54 fabiankeil - * Change sed() to return a jb_err in preparation for forward-override{}. - * - * Revision 1.102 2007/05/27 12:39:32 fabiankeil - * Adjust "X-Filter: No" to disable dedicated header filters. - * - * Revision 1.101 2007/05/14 10:16:41 fabiankeil - * Streamline client_cookie_adder(). - * - * Revision 1.100 2007/04/30 15:53:11 fabiankeil - * Make sure filters with dynamic jobs actually use them. - * - * Revision 1.99 2007/04/30 15:06:26 fabiankeil - * - Introduce dynamic pcrs jobs that can resolve variables. - * - Remove unnecessary update_action_bits_for_all_tags() call. - * - * Revision 1.98 2007/04/17 18:32:10 fabiankeil - * - Make tagging based on tags set by earlier taggers - * of the same kind possible. - * - Log whether or not new tags cause action bits updates - * (in which case a matching tag-pattern section exists). - * - Log if the user tries to set a tag that is already set. - * - * Revision 1.97 2007/04/15 16:39:21 fabiankeil - * Introduce tags as alternative way to specify which - * actions apply to a request. At the moment tags can be - * created based on client and server headers. - * - * Revision 1.96 2007/04/12 12:53:58 fabiankeil - * Log a warning if the content is compressed, filtering is - * enabled and Privoxy was compiled without zlib support. - * Closes FR#1673938. - * - * Revision 1.95 2007/03/25 14:26:40 fabiankeil - * - Fix warnings when compiled with glibc. - * - Don't use crumble() for cookie crunching. - * - Move cookie time parsing into parse_header_time(). - * - Let parse_header_time() return a jb_err code - * instead of a pointer that can only be used to - * check for NULL anyway. - * - * Revision 1.94 2007/03/21 12:23:53 fabiankeil - * - Add better protection against malicious gzip headers. - * - Stop logging the first hundred bytes of decompressed content. - * It looks like it's working and there is always debug 16. - * - Log the content size after decompression in decompress_iob() - * instead of pcrs_filter_response(). - * - * Revision 1.93 2007/03/20 15:21:44 fabiankeil - * - Use dedicated header filter actions instead of abusing "filter". - * Replace "filter-client-headers" and "filter-client-headers" - * with "server-header-filter" and "client-header-filter". - * - Remove filter_client_header() and filter_client_header(), - * filter_header() now checks the shiny new - * CSP_FLAG_CLIENT_HEADER_PARSING_DONE flag instead. - * - * Revision 1.92 2007/03/05 13:25:32 fabiankeil - * - Cosmetical changes for LOG_LEVEL_RE_FILTER messages. - * - Handle "Cookie:" and "Connection:" headers a bit smarter - * (don't crunch them just to recreate them later on). - * - Add another non-standard time format for the cookie - * expiration date detection. - * - Fix a valgrind warning. - * - * Revision 1.91 2007/02/24 12:27:32 fabiankeil - * Improve cookie expiration date detection. - * - * Revision 1.90 2007/02/08 19:12:35 fabiankeil - * Don't run server_content_length() the first time - * sed() parses server headers; only adjust the - * Content-Length header if the page was modified. - * - * Revision 1.89 2007/02/07 16:52:11 fabiankeil - * Fix log messages regarding the cookie time format - * (cookie and request URL were mixed up). - * - * Revision 1.88 2007/02/07 11:27:12 fabiankeil - * - Let decompress_iob() - * - not corrupt the content if decompression fails - * early. (the first byte(s) were lost). - * - use pointer arithmetics with defined outcome for - * a change. - * - Use a different kludge to remember a failed decompression. - * - * Revision 1.87 2007/01/31 16:21:38 fabiankeil - * Search for Max-Forwards headers case-insensitive, - * don't generate the "501 unsupported" message for invalid - * Max-Forwards values and don't increase negative ones. - * - * Revision 1.86 2007/01/30 13:05:26 fabiankeil - * - Let server_set_cookie() check the expiration date - * of cookies and don't touch the ones that are already - * expired. Fixes problems with low quality web applications - * as described in BR 932612. - * - * - Adjust comment in client_max_forwards to reality; - * remove invalid Max-Forwards headers. - * - * Revision 1.85 2007/01/26 15:33:46 fabiankeil - * Stop filter_header() from unintentionally removing - * empty header lines that were enlisted by the continue - * hack. - * - * Revision 1.84 2007/01/24 12:56:52 fabiankeil - * - Repeat the request URL before logging any headers. - * Makes reading the log easier in case of simultaneous requests. - * - If there are more than one Content-Type headers in one request, - * use the first one and remove the others. - * - Remove "newval" variable in server_content_type(). - * It's only used once. - * - * Revision 1.83 2007/01/12 15:03:02 fabiankeil - * Correct a cast, check inflateEnd() exit code - * to see if we have to, replace sprintf calls - * with snprintf. - * - * Revision 1.82 2007/01/01 19:36:37 fabiankeil - * Integrate a modified version of Wil Mahan's - * zlib patch (PR #895531). - * - * Revision 1.81 2006/12/31 22:21:33 fabiankeil - * Skip empty filter files in filter_header() - * but don't ignore the ones that come afterwards. - * Fixes BR 1619208, this time for real. - * - * Revision 1.80 2006/12/29 19:08:22 fabiankeil - * Reverted parts of my last commit - * to keep error handling working. - * - * Revision 1.79 2006/12/29 18:04:40 fabiankeil - * Fixed gcc43 conversion warnings. - * - * Revision 1.78 2006/12/26 17:19:20 fabiankeil - * Bringing back the "useless" localtime() call - * I removed in revision 1.67. On some platforms - * it's necessary to prevent time zone offsets. - * - * Revision 1.77 2006/12/07 18:44:26 fabiankeil - * Rebuild request URL in get_destination_from_headers() - * to make sure redirect{pcrs command} works as expected - * for intercepted requests. - * - * Revision 1.76 2006/12/06 19:52:25 fabiankeil - * Added get_destination_from_headers(). - * - * Revision 1.75 2006/11/13 19:05:51 fabiankeil - * Make pthread mutex locking more generic. Instead of - * checking for OSX and OpenBSD, check for FEATURE_PTHREAD - * and use mutex locking unless there is an _r function - * available. Better safe than sorry. - * - * Fixes "./configure --disable-pthread" and should result - * in less threading-related problems on pthread-using platforms, - * but it still doesn't fix BR#1122404. - * - * Revision 1.74 2006/10/02 16:59:12 fabiankeil - * The special header "X-Filter: No" now disables - * header filtering as well. - * - * Revision 1.73 2006/09/23 13:26:38 roro - * Replace TABs by spaces in source code. - * - * Revision 1.72 2006/09/23 12:37:21 fabiankeil - * Don't print a log message every time filter_headers is - * entered or left. It only creates noise without any real - * information. - * - * Revision 1.71 2006/09/21 19:55:17 fabiankeil - * Fix +hide-if-modified-since{-n}. - * - * Revision 1.70 2006/09/08 12:06:34 fabiankeil - * Have hide-if-modified-since interpret the random - * range value as minutes instead of hours. Allows - * more fine-grained configuration. - * - * Revision 1.69 2006/09/06 16:25:51 fabiankeil - * Always have parse_header_time return a pointer - * that actual makes sense, even though we currently - * only need it to detect problems. - * - * Revision 1.68 2006/09/06 10:43:32 fabiankeil - * Added config option enable-remote-http-toggle - * to specify if Privoxy should recognize special - * headers (currently only X-Filter) to change its - * behaviour. Disabled by default. - * - * Revision 1.67 2006/09/04 11:01:26 fabiankeil - * After filtering de-chunked instances, remove - * "Transfer-Encoding" header entirely instead of changing - * it to "Transfer-Encoding: identity", which is invalid. - * Thanks Michael Shields . Fixes PR 1318658. - * - * Don't use localtime in parse_header_time. An empty time struct - * is good enough, it gets overwritten by strptime anyway. - * - * Revision 1.66 2006/09/03 19:38:28 fabiankeil - * Use gmtime_r if available, fallback to gmtime with mutex - * protection for MacOSX and use vanilla gmtime for the rest. - * - * Revision 1.65 2006/08/22 10:55:56 fabiankeil - * Changed client_referrer to use the right type (size_t) for - * hostlenght and to shorten the temporary referrer string with - * '\0' instead of adding a useless line break. - * - * Revision 1.64 2006/08/17 17:15:10 fabiankeil - * - Back to timegm() using GnuPG's replacement if necessary. - * Using mktime() and localtime() could add a on hour offset if - * the randomize factor was big enough to lead to a summer/wintertime - * switch. - * - * - Removed now-useless Privoxy 3.0.3 compatibility glue. - * - * - Moved randomization code into pick_from_range(). - * - * - Changed parse_header_time definition. - * time_t isn't guaranteed to be signed and - * if it isn't, -1 isn't available as error code. - * Changed some variable types in client_if_modified_since() - * because of the same reason. - * - * Revision 1.63 2006/08/14 13:18:08 david__schmidt - * OS/2 compilation compatibility fixups - * - * Revision 1.62 2006/08/14 08:58:42 fabiankeil - * Changed include from strptime.c to strptime.h - * - * Revision 1.61 2006/08/14 08:25:19 fabiankeil - * Split filter-headers{} into filter-client-headers{} - * and filter-server-headers{}. - * Added parse_header_time() to share some code. - * Replaced timegm() with mktime(). - * - * Revision 1.60 2006/08/12 03:54:37 david__schmidt - * Windows service integration - * - * Revision 1.59 2006/08/03 02:46:41 david__schmidt - * Incorporate Fabian Keil's patch work: http://www.fabiankeil.de/sourcecode/privoxy/ - * - * Revision 1.58 2006/07/18 14:48:47 david__schmidt - * Reorganizing the repository: swapping out what was HEAD (the old 3.1 branch) - * with what was really the latest development (the v_3_0_branch branch) - * - * Revision 1.56.2.10 2006/01/21 16:16:08 david__schmidt - * Thanks to Edward Carrel for his patch to modernize OSX's pthreads support. See bug #1409623. - * - * Revision 1.56.2.9 2004/10/03 12:53:45 david__schmidt - * Add the ability to check jpeg images for invalid - * lengths of comment blocks. Defensive strategy - * against the exploit: - * Microsoft Security Bulletin MS04-028 - * Buffer Overrun in JPEG Processing (GDI+) Could - * Allow Code Execution (833987) - * Enabled with +inspect-jpegs in actions files. - * - * Revision 1.56.2.8 2003/07/11 13:21:25 oes - * Excluded text/plain objects from filtering. This fixes a - * couple of client-crashing, download corruption and - * Privoxy performance issues, whose root cause lies in - * web servers labelling content of unknown type as text/plain. - * - * Revision 1.56.2.7 2003/05/06 12:07:26 oes - * Fixed bug #729900: Suspicious HOST: headers are now killed and regenerated if necessary - * - * Revision 1.56.2.6 2003/04/14 21:28:30 oes - * Completing the previous change - * - * Revision 1.56.2.5 2003/04/14 12:08:16 oes - * Added temporary workaround for bug in PHP < 4.2.3 - * - * Revision 1.56.2.4 2003/03/07 03:41:05 david__schmidt - * Wrapping all *_r functions (the non-_r versions of them) with mutex semaphores for OSX. Hopefully this will take care of all of those pesky crash reports. - * - * Revision 1.56.2.3 2002/11/10 04:20:02 hal9 - * Fix typo: supressed -> suppressed - * - * Revision 1.56.2.2 2002/09/25 14:59:53 oes - * Improved cookie logging - * - * Revision 1.56.2.1 2002/09/25 14:52:45 oes - * Added basic support for OPTIONS and TRACE HTTP methods: - * - New parser function client_max_forwards which decrements - * the Max-Forwards HTTP header field of OPTIONS and TRACE - * requests by one before forwarding - * - New parser function client_host which extracts the host - * and port information from the HTTP header field if the - * request URI was not absolute - * - Don't crumble and re-add the Host: header, but only generate - * and append if missing - * - * Revision 1.56 2002/05/12 15:34:22 jongfoster - * Fixing typo in a comment - * - * Revision 1.55 2002/05/08 16:01:07 oes - * Optimized add_to_iob: - * - Use realloc instead of malloc(), memcpy(), free() - * - Expand to powers of two if possible, to get - * O(log n) reallocs instead of O(n). - * - Moved check for buffer limit here from chat - * - Report failure via returncode - * - * Revision 1.54 2002/04/02 15:03:16 oes - * Tiny code cosmetics - * - * Revision 1.53 2002/03/26 22:29:55 swa - * we have a new homepage! - * - * Revision 1.52 2002/03/24 13:25:43 swa - * name change related issues - * - * Revision 1.51 2002/03/13 00:27:05 jongfoster - * Killing warnings - * - * Revision 1.50 2002/03/12 01:45:35 oes - * More verbose logging - * - * Revision 1.49 2002/03/09 20:03:52 jongfoster - * - Making various functions return int rather than size_t. - * (Undoing a recent change). Since size_t is unsigned on - * Windows, functions like read_socket that return -1 on - * error cannot return a size_t. - * - * THIS WAS A MAJOR BUG - it caused frequent, unpredictable - * crashes, and also frequently caused JB to jump to 100% - * CPU and stay there. (Because it thought it had just - * read ((unsigned)-1) == 4Gb of data...) - * - * - The signature of write_socket has changed, it now simply - * returns success=0/failure=nonzero. - * - * - Trying to get rid of a few warnings --with-debug on - * Windows, I've introduced a new type "jb_socket". This is - * used for the socket file descriptors. On Windows, this - * is SOCKET (a typedef for unsigned). Everywhere else, it's - * an int. The error value can't be -1 any more, so it's - * now JB_INVALID_SOCKET (which is -1 on UNIX, and in - * Windows it maps to the #define INVALID_SOCKET.) - * - * - The signature of bind_port has changed. - * - * Revision 1.48 2002/03/07 03:46:53 oes - * Fixed compiler warnings etc - * - * Revision 1.47 2002/02/20 23:15:13 jongfoster - * Parsing functions now handle out-of-memory gracefully by returning - * an error code. - * - * Revision 1.46 2002/01/17 21:03:47 jongfoster - * Moving all our URL and URL pattern parsing code to urlmatch.c. - * - * Revision 1.45 2002/01/09 14:33:03 oes - * Added support for localtime_r. - * - * Revision 1.44 2001/12/14 01:22:54 steudten - * Remove 'user:pass@' from 'proto://user:pass@host' for the - * new added header 'Host: ..'. (See Req ID 491818) - * - * Revision 1.43 2001/11/23 00:26:38 jongfoster - * Fixing two really stupid errors in my previous commit - * - * Revision 1.42 2001/11/22 21:59:30 jongfoster - * Adding code to handle +no-cookies-keep - * - * Revision 1.41 2001/11/05 23:43:05 steudten - * Add time+date to log files. - * - * Revision 1.40 2001/10/26 20:13:09 jongfoster - * ctype.h is needed in Windows, too. - * - * Revision 1.39 2001/10/26 17:40:04 oes - * Introduced get_header_value() - * Removed http->user_agent, csp->referrer and csp->accept_types - * Removed client_accept() - * - * Revision 1.38 2001/10/25 03:40:48 david__schmidt - * Change in porting tactics: OS/2's EMX porting layer doesn't allow multiple - * threads to call select() simultaneously. So, it's time to do a real, live, - * native OS/2 port. See defines for __EMX__ (the porting layer) vs. __OS2__ - * (native). Both versions will work, but using __OS2__ offers multi-threading. - * - * Revision 1.37 2001/10/23 21:36:02 jongfoster - * Documenting sed()'s error behaviou (doc change only) - * - * Revision 1.36 2001/10/13 12:51:51 joergs - * Removed client_host, (was only required for the old 2.0.2-11 http://noijb. - * force-load), instead crumble Host: and add it (again) in client_host_adder - * (in case we get a HTTP/1.0 request without Host: header and forward it to - * a HTTP/1.1 server/proxy). - * - * Revision 1.35 2001/10/09 22:39:21 jongfoster - * assert.h is also required under Win32, so moving out of #ifndef _WIN32 - * block. - * - * Revision 1.34 2001/10/07 18:50:55 oes - * Added server_content_encoding, renamed server_transfer_encoding - * - * Revision 1.33 2001/10/07 18:04:49 oes - * Changed server_http11 to server_http and its pattern to "HTTP". - * Additional functionality: it now saves the HTTP status into - * csp->http->status and sets CT_TABOO for Status 206 (partial range) - * - * Revision 1.32 2001/10/07 15:43:28 oes - * Removed FEATURE_DENY_GZIP and replaced it with client_accept_encoding, - * client_te and client_accept_encoding_adder, triggered by the new - * +no-compression action. For HTTP/1.1 the Accept-Encoding header is - * changed to allow only identity and chunked, and the TE header is - * crunched. For HTTP/1.0, Accept-Encoding is crunched. - * - * parse_http_request no longer does anything than parsing. The rewriting - * of http->cmd and version mangling are gone. It now also recognizes - * the put and delete methods and saves the url in http->url. Removed - * unused variable. - * - * renamed content_type and content_length to have the server_ prefix - * - * server_content_type now only works if csp->content_type != CT_TABOO - * - * added server_transfer_encoding, which - * - Sets CT_TABOO to prohibit filtering if encoding compresses - * - Raises the CSP_FLAG_CHUNKED flag if Encoding is "chunked" - * - Change from "chunked" to "identity" if body was chunked - * but has been de-chunked for filtering. - * - * added server_content_md5 which crunches any Content-MD5 headers - * if the body was modified. - * - * made server_http11 conditional on +downgrade action - * - * Replaced 6 boolean members of csp with one bitmap (csp->flags) - * - * Revision 1.31 2001/10/05 14:25:02 oes - * Crumble Keep-Alive from Server - * - * Revision 1.30 2001/09/29 12:56:03 joergs - * IJB now changes HTTP/1.1 to HTTP/1.0 in requests and answers. - * - * Revision 1.29 2001/09/24 21:09:24 jongfoster - * Fixing 2 memory leaks that Guy spotted, where the paramater to - * enlist() was not being free()d. - * - * Revision 1.28 2001/09/22 16:32:28 jongfoster - * Removing unused #includes. - * - * Revision 1.27 2001/09/20 15:45:25 steudten - * - * add casting from size_t to int for printf() - * remove local variable shadow s2 - * - * Revision 1.26 2001/09/16 17:05:14 jongfoster - * Removing unused #include showarg.h - * - * Revision 1.25 2001/09/16 13:21:27 jongfoster - * Changes to use new list functions. - * - * Revision 1.24 2001/09/13 23:05:50 jongfoster - * Changing the string paramater to the header parsers a "const". - * - * Revision 1.23 2001/09/12 18:08:19 steudten - * - * In parse_http_request() header rewriting miss the host value, so - * from http://www.mydomain.com the result was just " / " not - * http://www.mydomain.com/ in case we forward. - * - * Revision 1.22 2001/09/10 10:58:53 oes - * Silenced compiler warnings - * - * Revision 1.21 2001/07/31 14:46:00 oes - * - Persistant connections now suppressed - * - sed() no longer appends empty header to csp->headers - * - * Revision 1.20 2001/07/30 22:08:36 jongfoster - * Tidying up #defines: - * - All feature #defines are now of the form FEATURE_xxx - * - Permanently turned off WIN_GUI_EDIT - * - Permanently turned on WEBDAV and SPLIT_PROXY_ARGS - * - * Revision 1.19 2001/07/25 17:21:54 oes - * client_uagent now saves copy of User-Agent: header value - * - * Revision 1.18 2001/07/13 14:02:46 oes - * - Included fix to repair broken HTTP requests that - * don't contain a path, not even '/'. - * - Removed all #ifdef PCRS - * - content_type now always inspected and classified as - * text, gif or other. - * - formatting / comments - * - * Revision 1.17 2001/06/29 21:45:41 oes - * Indentation, CRLF->LF, Tab-> Space - * - * Revision 1.16 2001/06/29 13:32:42 oes - * - Fixed a comment - * - Adapted free_http_request - * - Removed logentry from cancelled commit - * - * Revision 1.15 2001/06/03 19:12:38 oes - * deleted const struct interceptors - * - * Revision 1.14 2001/06/01 18:49:17 jongfoster - * Replaced "list_share" with "list" - the tiny memory gain was not - * worth the extra complexity. - * - * Revision 1.13 2001/05/31 21:30:33 jongfoster - * Removed list code - it's now in list.[ch] - * Renamed "permission" to "action", and changed many features - * to use the actions file rather than the global config. - * - * Revision 1.12 2001/05/31 17:33:13 oes - * - * CRLF -> LF - * - * Revision 1.11 2001/05/29 20:11:19 joergs - * '/ * inside comment' warning removed. - * - * Revision 1.10 2001/05/29 09:50:24 jongfoster - * Unified blocklist/imagelist/permissionslist. - * File format is still under discussion, but the internal changes - * are (mostly) done. - * - * Also modified interceptor behaviour: - * - We now intercept all URLs beginning with one of the following - * prefixes (and *only* these prefixes): - * * http://i.j.b/ - * * http://ijbswa.sf.net/config/ - * * http://ijbswa.sourceforge.net/config/ - * - New interceptors "home page" - go to http://i.j.b/ to see it. - * - Internal changes so that intercepted and fast redirect pages - * are not replaced with an image. - * - Interceptors now have the option to send a binary page direct - * to the client. (i.e. ijb-send-banner uses this) - * - Implemented show-url-info interceptor. (Which is why I needed - * the above interceptors changes - a typical URL is - * "http://i.j.b/show-url-info?url=www.somesite.com/banner.gif". - * The previous mechanism would not have intercepted that, and - * if it had been intercepted then it then it would have replaced - * it with an image.) - * - * Revision 1.9 2001/05/28 17:26:33 jongfoster - * Fixing segfault if last header was crunched. - * Fixing Windows build (snprintf() is _snprintf() under Win32, but we - * can use the cross-platform sprintf() instead.) - * - * Revision 1.8 2001/05/27 22:17:04 oes - * - * - re_process_buffer no longer writes the modified buffer - * to the client, which was very ugly. It now returns the - * buffer, which it is then written by chat. - * - * - content_length now adjusts the Content-Length: header - * for modified documents rather than crunch()ing it. - * (Length info in csp->content_length, which is 0 for - * unmodified documents) - * - * - For this to work, sed() is called twice when filtering. - * - * Revision 1.7 2001/05/27 13:19:06 oes - * Patched Joergs solution for the content-length in. - * - * Revision 1.6 2001/05/26 13:39:32 jongfoster - * Only crunches Content-Length header if applying RE filtering. - * Without this fix, Microsoft Windows Update wouldn't work. - * - * Revision 1.5 2001/05/26 00:28:36 jongfoster - * Automatic reloading of config file. - * Removed obsolete SIGHUP support (Unix) and Reload menu option (Win32). - * Most of the global variables have been moved to a new - * struct configuration_spec, accessed through csp->config->globalname - * Most of the globals remaining are used by the Win32 GUI. - * - * Revision 1.4 2001/05/22 18:46:04 oes - * - * - Enabled filtering banners by size rather than URL - * by adding patterns that replace all standard banner - * sizes with the "Junkbuster" gif to the re_filterfile - * - * - Enabled filtering WebBugs by providing a pattern - * which kills all 1x1 images - * - * - Added support for PCRE_UNGREEDY behaviour to pcrs, - * which is selected by the (nonstandard and therefore - * capital) letter 'U' in the option string. - * It causes the quantifiers to be ungreedy by default. - * Appending a ? turns back to greedy (!). - * - * - Added a new interceptor ijb-send-banner, which - * sends back the "Junkbuster" gif. Without imagelist or - * MSIE detection support, or if tinygif = 1, or the - * URL isn't recognized as an imageurl, a lame HTML - * explanation is sent instead. - * - * - Added new feature, which permits blocking remote - * script redirects and firing back a local redirect - * to the browser. - * The feature is conditionally compiled, i.e. it - * can be disabled with --disable-fast-redirects, - * plus it must be activated by a "fast-redirects" - * line in the config file, has its own log level - * and of course wants to be displayed by show-proxy-args - * Note: Boy, all the #ifdefs in 1001 locations and - * all the fumbling with configure.in and acconfig.h - * were *way* more work than the feature itself :-( - * - * - Because a generic redirect template was needed for - * this, tinygif = 3 now uses the same. - * - * - Moved GIFs, and other static HTTP response templates - * to project.h - * - * - Some minor fixes - * - * - Removed some >400 CRs again (Jon, you really worked - * a lot! ;-) - * - * Revision 1.3 2001/05/20 01:21:20 jongfoster - * Version 2.9.4 checkin. - * - Merged popupfile and cookiefile, and added control over PCRS - * filtering, in new "permissionsfile". - * - Implemented LOG_LEVEL_FATAL, so that if there is a configuration - * file error you now get a message box (in the Win32 GUI) rather - * than the program exiting with no explanation. - * - Made killpopup use the PCRS MIME-type checking and HTTP-header - * skipping. - * - Removed tabs from "config" - * - Moved duplicated url parsing code in "loaders.c" to a new funcition. - * - Bumped up version number. - * - * Revision 1.2 2001/05/17 23:02:36 oes - * - Made referrer option accept 'L' as a substitute for 'ยง' - * - * Revision 1.1.1.1 2001/05/15 13:59:01 oes - * Initial import of version 2.9.3 source tree - * - * *********************************************************************/ - + #include "config.h" @@ -728,6 +55,15 @@ const char parsers_rcs[] = "$Id: parsers.c,v 1.110 2007/09/29 10:42:37 fabiankei #ifdef FEATURE_ZLIB #include + +#define GZIP_IDENTIFIER_1 0x1f +#define GZIP_IDENTIFIER_2 0x8b + +#define GZIP_FLAG_CHECKSUM 0x02 +#define GZIP_FLAG_EXTRA_FIELDS 0x04 +#define GZIP_FLAG_FILE_NAME 0x08 +#define GZIP_FLAG_COMMENT 0x10 +#define GZIP_FLAG_RESERVED_BITS 0xe0 #endif #if !defined(_WIN32) && !defined(__OS2__) @@ -742,7 +78,6 @@ const char parsers_rcs[] = "$Id: parsers.c,v 1.110 2007/09/29 10:42:37 fabiankei #endif /* def FEATURE_PTHREAD */ #include "list.h" #include "parsers.h" -#include "encode.h" #include "ssplit.h" #include "errlog.h" #include "jbsockets.h" @@ -757,26 +92,15 @@ const char parsers_rcs[] = "$Id: parsers.c,v 1.110 2007/09/29 10:42:37 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); 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 connection (struct client_state *csp, char **header); static jb_err filter_header (struct client_state *csp, char **header); +static jb_err client_connection (struct client_state *csp, char **header); static jb_err client_referrer (struct client_state *csp, char **header); static jb_err client_uagent (struct client_state *csp, char **header); static jb_err client_ua (struct client_state *csp, char **header); @@ -792,9 +116,12 @@ static jb_err client_accept_language (struct client_state *csp, char **header 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); -static jb_err server_content_length (struct client_state *csp, char **header); +static jb_err server_adjust_content_length(struct client_state *csp, char **header); static jb_err server_content_md5 (struct client_state *csp, char **header); static jb_err server_content_encoding (struct client_state *csp, char **header); static jb_err server_transfer_coding (struct client_state *csp, char **header); @@ -802,19 +129,52 @@ static jb_err server_http (struct client_state *csp, char **header static jb_err crunch_server_header (struct client_state *csp, char **header); static jb_err server_last_modified (struct client_state *csp, char **header); static jb_err server_content_disposition(struct client_state *csp, char **header); +#ifdef FEATURE_ZLIB +static jb_err server_adjust_content_encoding(struct client_state *csp, char **header); +#endif + +#ifdef FEATURE_CONNECTION_KEEP_ALIVE +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_host_adder (struct client_state *csp); -static jb_err client_cookie_adder (struct client_state *csp); static jb_err client_xtra_adder (struct client_state *csp); -static jb_err client_x_forwarded_adder(struct client_state *csp); -static jb_err connection_close_adder (struct client_state *csp); +static jb_err client_x_forwarded_for_adder(struct client_state *csp); +static jb_err client_connection_header_adder(struct client_state *csp); +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); static jb_err handle_conditional_hide_referrer_parameter(char **header, const char *host, const int parameter_conditional_block); +static void create_content_length_header(unsigned long long content_length, + char *header, size_t buffer_length); + +/* + * List of functions to run on a list of headers. + */ +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; +}; -const struct parsers client_patterns[] = { +static const struct parsers client_patterns[] = { { "referer:", 8, client_referrer }, { "user-agent:", 11, client_uagent }, { "ua-", 3, client_ua }, @@ -825,68 +185,71 @@ const struct parsers client_patterns[] = { { "TE:", 3, client_te }, { "Host:", 5, client_host }, { "if-modified-since:", 18, client_if_modified_since }, +#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 }, - { "connection:", 11, connection }, - { "proxy-connection:", 17, crumble }, + { "Proxy-Connection:", 17, crumble }, +#endif + { "connection:", 11, client_connection }, { "max-forwards:", 13, client_max_forwards }, { "Accept-Language:", 16, client_accept_language }, { "if-none-match:", 14, client_if_none_match }, + { "Range:", 6, client_range }, + { "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 } }; -const struct parsers server_patterns[] = { +static const struct parsers server_patterns[] = { { "HTTP/", 5, server_http }, { "set-cookie:", 11, server_set_cookie }, - { "connection:", 11, connection }, + { "connection:", 11, server_connection }, { "Content-Type:", 13, server_content_type }, { "Content-MD5:", 12, server_content_md5 }, { "Content-Encoding:", 17, server_content_encoding }, - { "Transfer-Encoding:", 18, server_transfer_coding }, +#ifdef FEATURE_CONNECTION_KEEP_ALIVE + { "Content-Length:", 15, server_save_content_length }, + { "Keep-Alive:", 11, server_keep_alive }, + { "Proxy-Connection:", 17, server_proxy_connection }, +#else { "Keep-Alive:", 11, crumble }, +#endif /* def FEATURE_CONNECTION_KEEP_ALIVE */ + { "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 } + { NULL, 0, NULL } }; -const struct parsers server_patterns_light[] = { - { "Content-Length:", 15, server_content_length }, - { "Transfer-Encoding:", 18, server_transfer_coding }, -#ifdef FEATURE_ZLIB - { "Content-Encoding:", 17, server_content_encoding }, -#endif /* def FEATURE_ZLIB */ - { NULL, 0, NULL } -}; - -const add_header_func_ptr add_client_headers[] = { +static const add_header_func_ptr add_client_headers[] = { client_host_adder, - client_cookie_adder, - client_x_forwarded_adder, + client_x_forwarded_for_adder, client_xtra_adder, - /* Temporarily disabled: client_accept_encoding_adder, */ - connection_close_adder, + client_connection_header_adder, NULL }; -const add_header_func_ptr add_server_headers[] = { - connection_close_adder, +static const add_header_func_ptr add_server_headers[] = { + server_connection_adder, +#ifdef FEATURE_CONNECTION_KEEP_ALIVE + server_proxy_connection_adder, +#endif /* def FEATURE_CONNECTION_KEEP_ALIVE */ NULL }; -/* The vanilla wafer. */ -static const char VANILLA_WAFER[] = - "NOTICE=TO_WHOM_IT_MAY_CONCERN_" - "Do_not_send_me_any_copyrighted_information_other_than_the_" - "document_that_I_am_requesting_or_any_of_its_necessary_components._" - "In_particular_do_not_send_me_any_cookies_that_" - "are_subject_to_a_claim_of_copyright_by_anybody._" - "Take_notice_that_I_refuse_to_be_bound_by_any_license_condition_" - "(copyright_or_otherwise)_applying_to_any_cookie._"; - /********************************************************************* * * Function : flush_socket @@ -895,7 +258,7 @@ static const char VANILLA_WAFER[] = * * Parameters : * 1 : fd = file descriptor of the socket to read - * 2 : csp = Current client state (buffers, headers, etc...) + * 2 : iob = The I/O buffer to flush, usually csp->iob. * * Returns : On success, the number of bytes written are returned (zero * indicates nothing was written). On error, -1 is returned, @@ -905,10 +268,9 @@ static const char VANILLA_WAFER[] = * file, the results are not portable. * *********************************************************************/ -int flush_socket(jb_socket fd, struct client_state *csp) +long flush_socket(jb_socket fd, struct iob *iob) { - struct iob *iob = csp->iob; - int len = iob->eod - iob->cur; + long len = iob->eod - iob->cur; if (len <= 0) { @@ -929,22 +291,22 @@ int flush_socket(jb_socket fd, struct client_state *csp) * * 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, int 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, want; + size_t used, offset, need; char *p; if (n <= 0) return JB_ERR_OK; @@ -957,17 +319,24 @@ jb_err add_to_iob(struct client_state *csp, char *buf, int 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_ERROR, "Buffer limit reached while extending the buffer (iob)"); + log_error(LOG_LEVEL_INFO, + "Buffer limit reached while extending the buffer (iob). Needed: %d. Limit: %d", + need, buffer_limit); return JB_ERR_MEMORY; } if (need > iob->size) { - for (want = csp->iob->size ? csp->iob->size : 512; want <= need;) want *= 2; - - if (want <= csp->config->buffer_limit && NULL != (p = (char *)realloc(iob->buf, want))) + size_t want = iob->size ? iob->size : 512; + + while (want <= need) + { + want *= 2; + } + + if (want <= buffer_limit && NULL != (p = (char *)realloc(iob->buf, want))) { iob->size = want; } @@ -988,7 +357,7 @@ jb_err add_to_iob(struct client_state *csp, char *buf, int 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; @@ -1001,6 +370,27 @@ jb_err add_to_iob(struct client_state *csp, char *buf, int 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 /********************************************************************* * @@ -1022,7 +412,7 @@ jb_err add_to_iob(struct client_state *csp, char *buf, int 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 */ @@ -1040,13 +430,15 @@ jb_err decompress_iob(struct client_state *csp) cur = csp->iob->cur; - if (bufsize < 10) + if (bufsize < (size_t)10) { /* * 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; } @@ -1064,8 +456,8 @@ jb_err decompress_iob(struct client_state *csp) * Strip off the gzip header. Please see RFC 1952 for more * explanation of the appropriate fields. */ - if ((*cur++ != (char)0x1f) - || (*cur++ != (char)0x8b) + if (((*cur++ & 0xff) != GZIP_IDENTIFIER_1) + || ((*cur++ & 0xff) != GZIP_IDENTIFIER_2) || (*cur++ != Z_DEFLATED)) { log_error(LOG_LEVEL_ERROR, "Invalid gzip header when decompressing"); @@ -1074,47 +466,31 @@ jb_err decompress_iob(struct client_state *csp) else { int flags = *cur++; - /* - * XXX: These magic numbers should be replaced - * with macros to give a better idea what they do. - */ - if (flags & 0xe0) + if (flags & GZIP_FLAG_RESERVED_BITS) { /* The gzip header has reserved bits set; bail out. */ log_error(LOG_LEVEL_ERROR, "Invalid gzip header flags when decompressing"); return JB_ERR_COMPRESS; } + + /* + * Skip mtime (4 bytes), extra flags (1 byte) + * and OS type (1 byte). + */ cur += 6; /* Skip extra fields if necessary. */ - if (flags & 0x04) + if (flags & GZIP_FLAG_EXTRA_FIELDS) { /* * Skip a given number of bytes, specified * as a 16-bit little-endian value. - */ - /* - * XXX: This code used to be: - * - * csp->iob->cur += *csp->iob->cur++ + (*csp->iob->cur++ << 8); - * - * which I had to change into: - * - * cur += *cur++ + (*cur++ << 8); * - * at which point gcc43 finally noticed that the value - * of cur is undefined (it depends on which of the - * summands is evaluated first). - * - * I haven't come across a site where this - * code is actually executed yet, but I hope - * it works anyway. + * XXX: this code is untested and should probably be removed. */ int skip_bytes; skip_bytes = *cur++; - skip_bytes = *cur++ << 8; - - assert(skip_bytes == *csp->iob->cur - 2 + ((*csp->iob->cur - 1) << 8)); + skip_bytes += *cur++ << 8; /* * The number of bytes to skip should be positive @@ -1134,22 +510,21 @@ jb_err decompress_iob(struct client_state *csp) } /* Skip the filename if necessary. */ - if (flags & 0x08) + if (flags & GZIP_FLAG_FILE_NAME) { /* A null-terminated string is supposed to follow. */ while (*cur++ && (cur < csp->iob->eod)); - } /* Skip the comment if necessary. */ - if (flags & 0x10) + if (flags & GZIP_FLAG_COMMENT) { /* A null-terminated string is supposed to follow. */ while (*cur++ && (cur < csp->iob->eod)); } /* Skip the CRC if necessary. */ - if (flags & 0x02) + if (flags & GZIP_FLAG_CHECKSUM) { cur += 2; } @@ -1169,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 @@ -1187,7 +557,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++; } @@ -1209,7 +579,7 @@ jb_err decompress_iob(struct client_state *csp) * Passing -MAX_WBITS to inflateInit2 tells the library * that there is no zlib header. */ - if (inflateInit2 (&zstr, -MAX_WBITS) != Z_OK) + if (inflateInit2(&zstr, -MAX_WBITS) != Z_OK) { log_error(LOG_LEVEL_ERROR, "Error initializing decompression"); return JB_ERR_COMPRESS; @@ -1229,7 +599,7 @@ jb_err decompress_iob(struct client_state *csp) assert(bufsize >= skip_size); memcpy(buf, csp->iob->buf, skip_size); - zstr.avail_out = bufsize - skip_size; + zstr.avail_out = (uInt)(bufsize - skip_size); zstr.next_out = (Bytef *)buf + skip_size; /* Try to decompress the whole stream in one shot. */ @@ -1240,21 +610,24 @@ jb_err decompress_iob(struct client_state *csp) char *tmpbuf; /* used for realloc'ing the buffer */ size_t oldbufsize = bufsize; /* keep track of the old bufsize */ - /* - * If zlib wants more data then there's a problem, because - * the complete compressed file should have been buffered. - */ if (0 == zstr.avail_in) { - log_error(LOG_LEVEL_ERROR, "Unexpected end of compressed iob"); - return JB_ERR_COMPRESS; + /* + * If zlib wants more data then there's a problem, because + * the complete compressed file should have been buffered. + */ + log_error(LOG_LEVEL_ERROR, + "Unexpected end of compressed iob. Using what we got so far."); + break; } /* - * 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; @@ -1268,7 +641,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) @@ -1286,7 +659,7 @@ jb_err decompress_iob(struct client_state *csp) * buffer, which may be in a location different from * the old one. */ - zstr.avail_out += bufsize - oldbufsize; + zstr.avail_out += (uInt)(bufsize - oldbufsize); zstr.next_out = (Bytef *)tmpbuf + bufsize - zstr.avail_out; /* @@ -1295,7 +668,6 @@ jb_err decompress_iob(struct client_state *csp) */ assert(zstr.avail_out == tmpbuf + bufsize - (char *)zstr.next_out); assert((char *)zstr.next_out == tmpbuf + ((char *)oldnext_out - buf)); - assert(zstr.avail_out > 0); buf = tmpbuf; } @@ -1317,11 +689,15 @@ jb_err decompress_iob(struct client_state *csp) */ } - if (status != Z_STREAM_END) + if ((status != Z_STREAM_END) && (0 != zstr.avail_in)) { - /* We failed to decompress the stream. */ + /* + * We failed to decompress the stream and it's + * not simply because of missing data. + */ log_error(LOG_LEVEL_ERROR, - "Error in decompressing to the buffer (iob): %s", zstr.msg); + "Unexpected error while decompressing to the buffer (iob): %s", + zstr.msg); return JB_ERR_COMPRESS; } @@ -1337,7 +713,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. @@ -1347,7 +723,7 @@ jb_err decompress_iob(struct client_state *csp) && (csp->iob->eod <= csp->iob->buf + csp->iob->size)) { const size_t new_size = (size_t)(csp->iob->eod - csp->iob->cur); - if (new_size > 0) + if (new_size > (size_t)0) { log_error(LOG_LEVEL_RE_FILTER, "Decompression successful. Old size: %d, new size: %d.", @@ -1375,14 +751,156 @@ jb_err decompress_iob(struct client_state *csp) #endif /* defined(FEATURE_ZLIB) */ +/********************************************************************* + * + * Function : normalize_lws + * + * 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. + * + * Parameters : + * 1 : header = A header with linear whitespace to reduce. + * + * Returns : N/A + * + *********************************************************************/ +static void normalize_lws(char *header) +{ + char *p = header; + + while (*p != '\0') + { + if (privoxy_isspace(*p) && privoxy_isspace(*(p+1))) + { + char *q = p+1; + + while (privoxy_isspace(*q)) + { + q++; + } + log_error(LOG_LEVEL_HEADER, "Reducing whitespace in '%s'", header); + string_move(p+1, q); + } + + if (*p == '\t') + { + log_error(LOG_LEVEL_HEADER, + "Converting tab to space in '%s'", header); + *p = ' '; + } + else if (*p == '"') + { + char *end_of_token = strstr(p+1, "\""); + + if (NULL != end_of_token) + { + /* Don't mess with quoted text. */ + p = end_of_token; + } + else + { + log_error(LOG_LEVEL_HEADER, + "Ignoring single quote in '%s'", header); + } + } + p++; + } + + p = strchr(header, ':'); + if ((p != NULL) && (p != header) && privoxy_isspace(*(p-1))) + { + /* + * There's still space before the colon. + * We don't want it. + */ + string_move(p-1, p); + } +} + + /********************************************************************* * * Function : get_header * * Description : This (odd) routine will parse the csp->iob + * to get the next complete header. * * Parameters : - * 1 : csp = Current client state (buffers, headers, etc...) + * 1 : iob = The I/O buffer to parse, usually csp->iob. + * + * Returns : Any one of the following: + * + * 1) a pointer to a dynamically allocated string that contains a header line + * 2) NULL indicating that the end of the header was reached + * 3) "" indicating that the end of the iob was reached before finding + * a complete header line. + * + *********************************************************************/ +char *get_header(struct iob *iob) +{ + char *header; + + header = get_header_line(iob); + + if ((header == NULL) || (*header == '\0')) + { + /* + * No complete header read yet, tell the client. + */ + return header; + } + + while ((iob->cur[0] == ' ') || (iob->cur[0] == '\t')) + { + /* + * Header spans multiple lines, append the next one. + */ + char *continued_header; + + continued_header = get_header_line(iob); + if ((continued_header == NULL) || (*continued_header == '\0')) + { + /* + * No complete header read yet, return what we got. + * XXX: Should "unread" header instead. + */ + log_error(LOG_LEVEL_INFO, + "Failed to read a multi-line header properly: '%s'", + header); + break; + } + + if (JB_ERR_OK != string_join(&header, continued_header)) + { + log_error(LOG_LEVEL_FATAL, + "Out of memory while appending multiple headers."); + } + else + { + /* XXX: remove before next stable release. */ + log_error(LOG_LEVEL_HEADER, + "Merged multiple header lines to: '%s'", + header); + } + } + + normalize_lws(header); + + return header; + +} + + +/********************************************************************* + * + * Function : get_header_line + * + * Description : This (odd) routine will parse the csp->iob + * to get the next header line. + * + * Parameters : + * 1 : iob = The I/O buffer to parse, usually csp->iob. * * Returns : Any one of the following: * @@ -1392,11 +910,9 @@ jb_err decompress_iob(struct client_state *csp) * a complete header line. * *********************************************************************/ -char *get_header(struct client_state *csp) +static char *get_header_line(struct iob *iob) { - struct iob *iob; char *p, *q, *ret; - iob = csp->iob; if ((iob->cur == NULL) || ((p = strchr(iob->cur, '\n')) == NULL)) @@ -1410,8 +926,9 @@ char *get_header(struct client_state *csp) if (ret == NULL) { /* FIXME No way to handle error properly */ - log_error(LOG_LEVEL_FATAL, "Out of memory in get_header()"); + log_error(LOG_LEVEL_FATAL, "Out of memory in get_header_line()"); } + assert(ret != NULL); iob->cur = p+1; @@ -1421,10 +938,10 @@ char *get_header(struct client_state *csp) if (*ret == '\0') { freez(ret); - return(NULL); + return NULL; } - return(ret); + return ret; } @@ -1465,14 +982,14 @@ char *get_header_value(const struct list *header_list, const char *header_name) /* * Found: return pointer to start of value */ - ret = (char *) (cur_entry->str + length); - while (*ret && ijb_isspace(*ret)) ret++; - return(ret); + ret = cur_entry->str + length; + while (*ret && privoxy_isspace(*ret)) ret++; + return ret; } } } - /* + /* * Not found */ return NULL; @@ -1484,7 +1001,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...) @@ -1511,102 +1028,246 @@ static jb_err scan_headers(struct client_state *csp) /********************************************************************* * - * Function : sed - * - * Description : add, delete or modify lines in the HTTP header streams. - * On entry, it receives a linked list of headers space - * that was allocated dynamically (both the list nodes - * and the header contents). - * - * As a side effect it frees the space used by the original - * header lines. + * Function : enforce_header_order * - * XXX: should be split to remove the first_run hack. + * Description : Enforces a given header order. * * Parameters : - * 1 : pats = list of patterns to match against headers - * 2 : more_headers = list of functions to add more - * headers (client or server) - * 3 : csp = Current client state (buffers, headers, etc...) + * 1 : headers = List of headers to order. + * 2 : ordered_headers = List of ordered header names. * - * Returns : JB_ERR_OK in case off success, or - * JB_ERR_MEMORY on out-of-memory error. + * Returns : N/A * *********************************************************************/ -jb_err sed(const struct parsers pats[], - const add_header_func_ptr more_headers[], - struct client_state *csp) +static void enforce_header_order(struct list *headers, const struct list *ordered_headers) { - struct list_entry *p; - const struct parsers *v; - const add_header_func_ptr *f; - jb_err err = JB_ERR_OK; - int first_run; + struct list_entry *sorted_header; + struct list new_headers[1]; + struct list_entry *header; - /* - * If filtering is enabled, sed is run twice, - * but most of the work needs to be done only once. - */ - first_run = (more_headers != NULL ) ? 1 : 0; + init_list(new_headers); - if (first_run) /* Parse and print */ - { - scan_headers(csp); + /* The request line is always the first "header" */ - for (v = pats; (err == JB_ERR_OK) && (v->str != NULL) ; v++) + 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) { - for (p = csp->headers->first; (err == JB_ERR_OK) && (p != NULL) ; p = p->next) - { - /* Header crunch()ed in previous run? -> ignore */ - if (p->str == NULL) continue; + /* Header enlisted in previous run? -> ignore */ + if (header->str == NULL) continue; - /* Does the current parser handle this header? */ - if ((strncmpic(p->str, v->str, v->len) == 0) || (v->len == CHECK_EVERY_HEADER_REMAINING)) + 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)) { - err = v->parser(csp, (char **)&(p->str)); + log_error(LOG_LEVEL_HEADER, "Failed to enlist %s", header->str); } + freez(header->str); } } - /* place any additional headers on the csp->headers list */ - for (f = more_headers; (err == JB_ERR_OK) && (*f) ; f++) - { - err = (*f)(csp); - } } - else /* Parse only */ + + /* Enlist the rest of the headers behind the ordered ones */ + for (header = headers->first; header != NULL; header = header->next) { - /* - * The second run is only needed if the body was modified - * and the content-lenght has changed. - */ - if (strncmpic(csp->http->cmd, "HEAD", 4)) - { - /*XXX: Code duplication */ - for (v = pats; (err == JB_ERR_OK) && (v->str != NULL) ; v++) - { - for (p = csp->headers->first; (err == JB_ERR_OK) && (p != NULL) ; p = p->next) - { - /* Header crunch()ed in previous run? -> ignore */ - if (p->str == NULL) continue; + /* Header enlisted in previous run? -> ignore */ + if (header->str == NULL) continue; - /* Does the current parser handle this header? */ - if (strncmpic(p->str, v->str, v->len) == 0) - { - err = v->parser(csp, (char **)&(p->str)); - } - } - } + 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); } - return err; -} + list_remove_all(headers); + list_duplicate(headers, new_headers); + list_remove_all(new_headers); + return; +} /********************************************************************* * - * Function : header_tagger + * Function : sed + * + * Description : add, delete or modify lines in the HTTP header streams. + * On entry, it receives a linked list of headers space + * that was allocated dynamically (both the list nodes + * and the header contents). + * + * As a side effect it frees the space used by the original + * header lines. + * + * Parameters : + * 1 : csp = Current client state (buffers, headers, etc...) + * 2 : filter_server_headers = Boolean to switch between + * server and header filtering. + * + * 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(struct client_state *csp, int filter_server_headers) +{ + /* XXX: use more descriptive names. */ + struct list_entry *p; + const struct parsers *v; + 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); + } + + while (v->str != NULL) + { + for (p = csp->headers->first; p != NULL; p = p->next) + { + /* Header crunch()ed in previous run? -> ignore */ + if (p->str == NULL) continue; + + /* Does the current parser handle this header? */ + if ((strncmpic(p->str, v->str, v->len) == 0) || + (v->len == CHECK_EVERY_HEADER_REMAINING)) + { + err = v->parser(csp, &(p->str)); + if (err != JB_ERR_OK) + { + return err; + } + } + } + v++; + } + + /* place additional headers on the csp->headers list */ + while ((err == JB_ERR_OK) && (*f)) + { + err = (*f)(csp); + 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; +} + + +/********************************************************************* + * + * Function : update_server_headers + * + * Description : Updates server headers after the body has been modified. + * + * Parameters : + * 1 : csp = Current client state (buffers, headers, etc...) + * + * Returns : JB_ERR_OK in case off success, or + * JB_ERR_MEMORY on out-of-memory error. + * + *********************************************************************/ +jb_err update_server_headers(struct client_state *csp) +{ + jb_err err = JB_ERR_OK; + + static const struct parsers server_patterns_light[] = { + { "Content-Length:", 15, server_adjust_content_length }, + { "Transfer-Encoding:", 18, server_transfer_coding }, +#ifdef FEATURE_ZLIB + { "Content-Encoding:", 17, server_adjust_content_encoding }, +#endif /* def FEATURE_ZLIB */ + { NULL, 0, NULL } + }; + + if (strncmpic(csp->http->cmd, "HEAD", 4)) + { + const struct parsers *v; + struct list_entry *p; + + for (v = server_patterns_light; (err == JB_ERR_OK) && (v->str != NULL); v++) + { + for (p = csp->headers->first; (err == JB_ERR_OK) && (p != NULL); p = p->next) + { + /* Header crunch()ed in previous run? -> ignore */ + if (p->str == NULL) continue; + + /* Does the current parser handle this header? */ + if (strncmpic(p->str, v->str, v->len) == 0) + { + err = v->parser(csp, (char **)&(p->str)); + } + } + } + } + +#ifdef FEATURE_CONNECTION_KEEP_ALIVE + if ((JB_ERR_OK == err) + && (csp->flags & CSP_FLAG_MODIFIED) + && (csp->flags & CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE) + && !(csp->flags & CSP_FLAG_SERVER_CONTENT_LENGTH_SET)) + { + char header[50]; + + create_content_length_header(csp->content_length, header, sizeof(header)); + err = enlist(csp->headers, header); + if (JB_ERR_OK == err) + { + 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 */ + +#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; +} + + +/********************************************************************* + * + * Function : header_tagger * * Description : Executes all text substitutions from applying * tag actions and saves the result as tag. @@ -1623,200 +1284,551 @@ jb_err sed(const struct parsers pats[], *********************************************************************/ 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; - int found_filters = 0; const size_t header_length = strlen(header); - if (csp->flags & CSP_FLAG_CLIENT_HEADER_PARSING_DONE) - { - wanted_filter_type = FT_SERVER_HEADER_TAGGER; - multi_action_index = ACTION_MULTI_SERVER_HEADER_TAGGER; - } - else + if (csp->flags & CSP_FLAG_CLIENT_HEADER_PARSING_DONE) + { + wanted_filter_type = FT_SERVER_HEADER_TAGGER; + multi_action_index = ACTION_MULTI_SERVER_HEADER_TAGGER; + } + else + { + wanted_filter_type = FT_CLIENT_HEADER_TAGGER; + multi_action_index = ACTION_MULTI_CLIENT_HEADER_TAGGER; + } + + if (list_is_empty(csp->action->multi[multi_action_index]) + || filters_available(csp) == FALSE) + { + /* Return early if no taggers apply or if none are available. */ + return JB_ERR_OK; + } + + /* Execute all applying taggers */ + for (tag_name = csp->action->multi[multi_action_index]->first; + NULL != tag_name; tag_name = tag_name->next) + { + 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) + { + continue; + } + + joblist = b->joblist; + + if (b->dynamic) joblist = compile_dynamic_pcrs_job_list(csp, b); + + if (NULL == joblist) + { + log_error(LOG_LEVEL_RE_FILTER, + "Tagger %s has empty joblist. Nothing to do.", b->name); + continue; + } + + /* execute their pcrs_joblist on the header. */ + for (job = joblist; NULL != job; job = job->next) + { + const int hits = pcrs_execute(job, tag, size, &modified_tag, &size); + + if (0 < hits) + { + /* Success, continue with the modified version. */ + if (tag != header) + { + freez(tag); + } + tag = modified_tag; + } + else + { + /* Tagger doesn't match */ + if (0 > hits) + { + /* Regex failure, log it but continue anyway. */ + 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 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 (!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); + } + } + + return JB_ERR_OK; +} + +/* here begins the family of parser functions that reformat header lines */ + +/********************************************************************* + * + * Function : filter_header + * + * Description : Executes all text substitutions from all applying + * +(server|client)-header-filter actions on the header. + * Most of the code was copied from pcrs_filter_response, + * including the rather short variable names + * + * Parameters : + * 1 : csp = Current client state (buffers, headers, etc...) + * 2 : header = On input, pointer to header to modify. + * On output, pointer to the modified header, or NULL + * to remove the header. This function frees the + * original string if necessary. + * + * Returns : JB_ERR_OK on success and always succeeds + * + *********************************************************************/ +static jb_err filter_header(struct client_state *csp, char **header) +{ + int hits=0; + int matches; + size_t size = strlen(*header); + + char *newheader = NULL; + pcrs_job *job; + + struct re_filterfile_spec *b; + struct list_entry *filtername; + + enum filter_type wanted_filter_type; + int multi_action_index; + + if (csp->flags & CSP_FLAG_NO_FILTERING) + { + return JB_ERR_OK; + } + + if (csp->flags & CSP_FLAG_CLIENT_HEADER_PARSING_DONE) + { + wanted_filter_type = FT_SERVER_HEADER_FILTER; + multi_action_index = ACTION_MULTI_SERVER_HEADER_FILTER; + } + else + { + wanted_filter_type = FT_CLIENT_HEADER_FILTER; + multi_action_index = ACTION_MULTI_CLIENT_HEADER_FILTER; + } + + if (list_is_empty(csp->action->multi[multi_action_index]) + || filters_available(csp) == FALSE) + { + /* Return early if no filters apply or if none are available. */ + return JB_ERR_OK; + } + + /* Execute all applying header filters */ + for (filtername = csp->action->multi[multi_action_index]->first; + filtername != NULL; filtername = filtername->next) + { + int current_hits = 0; + pcrs_job *joblist; + + b = get_filter(csp, filtername->str, wanted_filter_type); + if (b == NULL) + { + continue; + } + + 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; + } + + log_error(LOG_LEVEL_RE_FILTER, "filtering \'%s\' (size %d) with \'%s\' ...", + *header, size, b->name); + + /* 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; + } + + /* + * Additionally checking for hits is important because if + * the continue hack is triggered, server headers can + * arrive empty to separate multiple heads from each other. + */ + if ((0 == size) && hits) + { + log_error(LOG_LEVEL_HEADER, "Removing empty header %s", *header); + freez(*header); + } + + return JB_ERR_OK; +} + + +/********************************************************************* + * + * Function : server_connection + * + * Description : Makes sure a proper "Connection:" header is + * set and signals connection_header_adder to + * do nothing. + * + * Parameters : + * 1 : csp = Current client state (buffers, headers, etc...) + * 2 : header = On input, pointer to header to modify. + * On output, pointer to the modified header, or NULL + * to remove the header. This function frees the + * original string if necessary. + * + * Returns : JB_ERR_OK on success. + * + *********************************************************************/ +static jb_err server_connection(struct client_state *csp, char **header) +{ + if (!strcmpic(*header, "Connection: keep-alive") +#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)) + { + csp->flags |= CSP_FLAG_SERVER_CONNECTION_KEEP_ALIVE; + } + + if ((csp->flags & CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE)) + { + log_error(LOG_LEVEL_HEADER, + "Keeping the server header '%s' around.", *header); + } + else +#endif /* FEATURE_CONNECTION_KEEP_ALIVE */ + { + char *old_header = *header; + + *header = strdup_or_die("Connection: close"); + log_error(LOG_LEVEL_HEADER, "Replaced: \'%s\' with \'%s\'", old_header, *header); + freez(old_header); + } + } + + /* Signal server_connection_adder() to return early. */ + csp->flags |= CSP_FLAG_SERVER_CONNECTION_HEADER_SET; + + return JB_ERR_OK; +} + + +#ifdef FEATURE_CONNECTION_KEEP_ALIVE +/********************************************************************* + * + * Function : server_keep_alive + * + * Description : Stores the server's keep alive timeout. + * + * 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 server_keep_alive(struct client_state *csp, char **header) +{ + unsigned int keep_alive_timeout; + const char *timeout_position = strstr(*header, "timeout="); + + if ((NULL == timeout_position) + || (1 != sscanf(timeout_position, "timeout=%u", &keep_alive_timeout))) + { + log_error(LOG_LEVEL_ERROR, "Couldn't parse: %s", *header); + } + else + { + if (keep_alive_timeout < csp->server_connection.keep_alive_timeout) + { + log_error(LOG_LEVEL_HEADER, + "Reducing keep-alive timeout from %u to %u.", + csp->server_connection.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, + "Server keep-alive timeout is %u. Sticking with %u.", + keep_alive_timeout, csp->server_connection.keep_alive_timeout); + } + csp->flags |= CSP_FLAG_SERVER_KEEP_ALIVE_TIMEOUT_SET; + } + + freez(*header); + + return JB_ERR_OK; +} + + +/********************************************************************* + * + * Function : server_proxy_connection + * + * Description : Figures out whether or not we should add a + * 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 server_proxy_connection(struct client_state *csp, char **header) +{ + csp->flags |= CSP_FLAG_SERVER_PROXY_CONNECTION_HEADER_SET; + return JB_ERR_OK; +} + + +/********************************************************************* + * + * 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 + * + * Description : Stores the client's keep alive timeout. + * + * 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_keep_alive(struct client_state *csp, char **header) +{ + unsigned int keep_alive_timeout; + char *timeout_position; + + if (!(csp->config->feature_flags & RUNTIME_FEATURE_CONNECTION_KEEP_ALIVE)) { - wanted_filter_type = FT_CLIENT_HEADER_TAGGER; - multi_action_index = ACTION_MULTI_CLIENT_HEADER_TAGGER; + log_error(LOG_LEVEL_HEADER, + "keep-alive support is disabled. Crunching: %s.", *header); + freez(*header); + return JB_ERR_OK; } - /* Check if there are any filters */ - for (i = 0; i < MAX_AF_FILES; i++) + /* Check for parameter-less format "Keep-Alive: 100" */ + timeout_position = strstr(*header, ": "); + if ((NULL == timeout_position) + || (1 != sscanf(timeout_position, ": %u", &keep_alive_timeout))) { - fl = csp->rlist[i]; - if (NULL != fl) + /* 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))) { - if (NULL != fl->f) - { - found_filters = 1; - break; - } + log_error(LOG_LEVEL_HEADER, + "Couldn't parse: '%s'. Using default timeout %u", + *header, csp->config->keep_alive_timeout); + freez(*header); + + return JB_ERR_OK; } } - if (0 == found_filters) + if (keep_alive_timeout < csp->config->keep_alive_timeout) { - log_error(LOG_LEVEL_ERROR, "Unable to get current state of regex tagging."); - return(JB_ERR_OK); + 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; } - - for (i = 0; i < MAX_AF_FILES; i++) + else { - fl = csp->rlist[i]; - if ((NULL == fl) || (NULL == fl->f)) - { - /* - * Either there are no filter files - * left, or this filter file just - * contains no valid filters. - * - * Continue to be sure we don't miss - * valid filter files that are chained - * after empty or invalid ones. - */ - continue; - } - - /* For all filters, */ - for (b = fl->f; b; b = b->next) - { - if (b->type != wanted_filter_type) - { - /* skip the ones we don't care about, */ - continue; - } - /* leaving only taggers that could apply, of which we use the ones, */ - for (tag_name = csp->action->multi[multi_action_index]->first; - NULL != tag_name; tag_name = tag_name->next) - { - /* that do apply, and */ - if (strcmp(b->name, tag_name->str) == 0) - { - char *modified_tag = NULL; - char *tag = header; - size_t size = header_length; - pcrs_job *joblist = b->joblist; - - if (b->dynamic) joblist = compile_dynamic_pcrs_job_list(csp, b); - - if (NULL == joblist) - { - log_error(LOG_LEVEL_RE_FILTER, - "Tagger %s has empty joblist. Nothing to do.", b->name); - continue; - } + /* 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); + } - /* execute their pcrs_joblist on the header. */ - for (job = joblist; NULL != job; job = job->next) - { - const int hits = pcrs_execute(job, tag, size, &modified_tag, &size); - - if (0 < hits) - { - /* Success, continue with the modified version. */ - if (tag != header) - { - freez(tag); - } - tag = modified_tag; - } - else - { - /* Tagger doesn't match */ - if (0 > hits) - { - /* Regex failure, log it but continue anyway. */ - log_error(LOG_LEVEL_ERROR, - "Problems with tagger \'%s\' and header \'%s\': %s", - b->name, *header, pcrs_strerror(hits)); - } - freez(modified_tag); - } - } + return JB_ERR_OK; +} - 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 */ +/********************************************************************* + * + * Function : get_content_length + * + * Description : Gets the content length specified in a + * Content-Length header. + * + * Parameters : + * 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_value, unsigned long long *length) +{ +#ifdef _WIN32 + assert(sizeof(unsigned long long) > 4); + if (1 != sscanf(header_value, "%I64u", length)) +#else + if (1 != sscanf(header_value, "%llu", length)) +#endif + { + return JB_ERR_PARSE; + } return JB_ERR_OK; } -/* here begins the family of parser functions that reformat header lines */ /********************************************************************* * - * Function : filter_header + * Function : client_save_content_length * - * Description : Executes all text substitutions from all applying - * +(server|client)-header-filter actions on the header. - * Most of the code was copied from pcrs_filter_response, - * including the rather short variable names + * Description : Save the Content-Length sent by the client. * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) @@ -1825,172 +1837,164 @@ static jb_err header_tagger(struct client_state *csp, char *header) * to remove the header. This function frees the * original string if necessary. * - * Returns : JB_ERR_OK on success and always succeeds + * Returns : JB_ERR_OK on success, or + * JB_ERR_MEMORY on out-of-memory error. * *********************************************************************/ -static jb_err filter_header(struct client_state *csp, char **header) +static jb_err client_save_content_length(struct client_state *csp, char **header) { - int hits=0; - int matches; - size_t size = strlen(*header); - - char *newheader = NULL; - pcrs_job *job; - - struct file_list *fl; - struct re_filterfile_spec *b; - struct list_entry *filtername; + unsigned long long content_length = 0; + const char *header_value; - int i, found_filters = 0; - int wanted_filter_type; - int multi_action_index; - - if (csp->flags & CSP_FLAG_NO_FILTERING) - { - return JB_ERR_OK; - } + assert(*(*header+14) == ':'); - if (csp->flags & CSP_FLAG_CLIENT_HEADER_PARSING_DONE) + header_value = *header + 15; + if (JB_ERR_OK != get_content_length(header_value, &content_length)) { - wanted_filter_type = FT_SERVER_HEADER_FILTER; - multi_action_index = ACTION_MULTI_SERVER_HEADER_FILTER; + log_error(LOG_LEVEL_ERROR, "Crunching invalid header: %s", *header); + freez(*header); } else { - wanted_filter_type = FT_CLIENT_HEADER_FILTER; - multi_action_index = ACTION_MULTI_CLIENT_HEADER_FILTER; + csp->expected_client_content_length = content_length; } - /* - * Need to check the set of re_filterfiles... - */ - for (i = 0; i < MAX_AF_FILES; i++) + return JB_ERR_OK; +} +#endif /* def FEATURE_CONNECTION_KEEP_ALIVE */ + + + +/********************************************************************* + * + * Function : client_connection + * + * Description : Makes sure a proper "Connection:" header is + * set and signals connection_header_adder + * to do nothing. + * + * Parameters : + * 1 : csp = Current client state (buffers, headers, etc...) + * 2 : header = On input, pointer to header to modify. + * On output, pointer to the modified header, or NULL + * to remove the header. This function frees the + * original string if necessary. + * + * Returns : JB_ERR_OK on success. + * + *********************************************************************/ +static jb_err client_connection(struct client_state *csp, char **header) +{ + static const char connection_close[] = "Connection: close"; + + if (!strcmpic(*header, connection_close)) { - fl = csp->rlist[i]; - if (NULL != fl) +#ifdef FEATURE_CONNECTION_KEEP_ALIVE + if ((csp->config->feature_flags & RUNTIME_FEATURE_CONNECTION_SHARING) + && !(csp->flags & CSP_FLAG_SERVER_SOCKET_TAINTED)) { - if (NULL != fl->f) - { - found_filters = 1; - break; - } + 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_or_die("Connection: keep-alive"); + log_error(LOG_LEVEL_HEADER, + "Replaced: \'%s\' with \'%s\'", old_header, *header); + freez(old_header); + } + } + else + { + log_error(LOG_LEVEL_HEADER, + "Keeping the client header '%s' around. " + "The connection will not be kept alive.", + *header); + csp->flags &= ~CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE; } } - - if (0 == found_filters) + else if ((csp->config->feature_flags & RUNTIME_FEATURE_CONNECTION_KEEP_ALIVE) + && !(csp->flags & CSP_FLAG_SERVER_SOCKET_TAINTED)) { - log_error(LOG_LEVEL_ERROR, "Unable to get current state of regexp filtering."); - return(JB_ERR_OK); + log_error(LOG_LEVEL_HEADER, + "Keeping the client header '%s' around. " + "The server connection will be kept alive if possible.", + *header); + csp->flags |= CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE; +#endif /* def FEATURE_CONNECTION_KEEP_ALIVE */ } - - for (i = 0; i < MAX_AF_FILES; i++) + else { - fl = csp->rlist[i]; - if ((NULL == fl) || (NULL == fl->f)) - { - /* - * Either there are no filter files - * left, or this filter file just - * contains no valid filters. - * - * Continue to be sure we don't miss - * valid filter files that are chained - * after empty or invalid ones. - */ - continue; - } - /* - * For all 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; - } + char *old_header = *header; - log_error(LOG_LEVEL_RE_FILTER, "filtering \'%s\' (size %d) with \'%s\' ...", - *header, size, b->name); + *header = strdup_or_die(connection_close); + log_error(LOG_LEVEL_HEADER, + "Replaced: \'%s\' with \'%s\'", old_header, *header); + freez(old_header); + } - /* 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); - } - } - } + /* Signal client_connection_header_adder() to return early. */ + csp->flags |= CSP_FLAG_CLIENT_CONNECTION_HEADER_SET; - if (b->dynamic) pcrs_free_joblist(joblist); + return JB_ERR_OK; +} - log_error(LOG_LEVEL_RE_FILTER, "... produced %d hits (new size %d).", current_hits, size); - hits += current_hits; - } - } - } - } - /* - * Additionally checking for hits is important because if - * the continue hack is triggered, server headers can - * arrive empty to separate multiple heads from each other. - */ - if ((0 == size) && hits) +#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, "Removing empty header %s", *header); - freez(*header); + 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); + return JB_ERR_OK; } +#endif /* def FEATURE_CONNECTION_KEEP_ALIVE */ /********************************************************************* * - * Function : connection + * Function : client_transfer_encoding * - * Description : Makes sure that the value of the Connection: header - * is "close" and signals connection_close_adder - * to do nothing. + * 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...) @@ -2000,37 +2004,51 @@ static jb_err filter_header(struct client_state *csp, char **header) * original string if necessary. * * Returns : JB_ERR_OK on success, or - * JB_ERR_MEMORY on out-of-memory error. * *********************************************************************/ -static jb_err connection(struct client_state *csp, char **header) +jb_err client_transfer_encoding(struct client_state *csp, char **header) { - char *old_header = *header; - - /* Do we have a 'Connection: close' header? */ - if (strcmpic(*header, "Connection: close")) + if (strstr(*header, "chunked")) { - /* No, create one */ - *header = strdup("Connection: close"); - if (header == NULL) - { - return JB_ERR_MEMORY; - } - log_error(LOG_LEVEL_HEADER, "Replaced: \'%s\' with \'%s\'", old_header, *header); - freez(old_header); + csp->flags |= CSP_FLAG_CHUNKED_CLIENT_BODY; + log_error(LOG_LEVEL_HEADER, "Expecting chunked client body"); } - /* Signal connection_close_adder() to return early. */ - if (csp->flags & CSP_FLAG_CLIENT_HEADER_PARSING_DONE) - { - csp->flags |= CSP_FLAG_SERVER_CONNECTION_CLOSE_SET; - } - else + 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_CLIENT_CONNECTION_CLOSE_SET; + csp->flags |= CSP_FLAG_UNSUPPORTED_CLIENT_EXPECTATION; + log_error(LOG_LEVEL_HEADER, + "Unsupported client expectaction: %s", *header); } return JB_ERR_OK; + } @@ -2053,6 +2071,7 @@ static jb_err connection(struct client_state *csp, char **header) *********************************************************************/ static jb_err crumble(struct client_state *csp, char **header) { + (void)csp; log_error(LOG_LEVEL_HEADER, "crumble crunched: %s!", *header); freez(*header); return JB_ERR_OK; @@ -2088,7 +2107,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); } } @@ -2102,7 +2121,7 @@ static jb_err crunch_server_header(struct client_state *csp, char **header) * Function : server_content_type * * Description : Set the content-type for filterable types (text/.*, - * .*xml.*, javascript and image/gif) unless filtering has been + * .*xml.*, .*script.* and image/gif) unless filtering has been * forbidden (CT_TABOO) while parsing earlier headers. * NOTE: Since text/plain is commonly used by web servers * for files whose correct type is unknown, we don't @@ -2124,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; } @@ -2145,39 +2172,41 @@ static jb_err server_content_type(struct client_state *csp, char **header) if (!(csp->content_type & CT_TABOO)) { - if ((strstr(*header, " text/") && !strstr(*header, "plain")) + /* + * XXX: The assumption that text/plain is a sign of + * binary data seems to be somewhat unreasonable nowadays + * and should be dropped after 3.0.8 is out. + */ + if ((strstr(*header, "text/") && !strstr(*header, "plain")) || strstr(*header, "xml") - || strstr(*header, "application/x-javascript")) + || strstr(*header, "script")) { csp->content_type |= CT_TEXT; } - else if (strstr(*header, " image/gif")) + else if (strstr(*header, "image/gif")) { csp->content_type |= CT_GIF; } - else if (strstr(*header, " image/jpeg")) - { - csp->content_type |= CT_JPEG; - } } /* * Are we messing with the content type? - */ + */ if (csp->action->flags & ACTION_CONTENT_TYPE_OVERWRITE) - { + { /* - * Make sure the user doesn't accidently - * change the content type of binary documents. - */ + * Make sure the user doesn't accidentally + * change the content type of binary documents. + */ 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; } @@ -2185,10 +2214,11 @@ static jb_err server_content_type(struct client_state *csp, char **header) } else { - log_error(LOG_LEVEL_HEADER, "%s not replaced. It doesn't look like text. " - "Enable force-text-mode if you know what you're doing.", *header); + log_error(LOG_LEVEL_HEADER, "%s not replaced. " + "It doesn't look like a content type that should be filtered. " + "Enable force-text-mode if you know what you're doing.", *header); } - } + } return JB_ERR_OK; } @@ -2260,16 +2290,16 @@ static jb_err server_transfer_coding(struct client_state *csp, char **header) * * Function : server_content_encoding * - * Description : This function is run twice for each request, - * unless FEATURE_ZLIB and filtering are disabled. + * Description : Used to check if the content is compressed, and if + * FEATURE_ZLIB is disabled, filtering is disabled as + * well. * - * The first run is used to check if the content - * is compressed, if FEATURE_ZLIB is disabled - * filtering is then disabled as well, if FEATURE_ZLIB - * is enabled the content is marked for decompression. - * - * The second run is used to remove the Content-Encoding - * header if the decompression was successful. + * 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. * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) @@ -2285,19 +2315,24 @@ static jb_err server_transfer_coding(struct client_state *csp, char **header) static jb_err server_content_encoding(struct client_state *csp, char **header) { #ifdef FEATURE_ZLIB - if ((csp->flags & CSP_FLAG_MODIFIED) - && (csp->content_type & (CT_GZIP | CT_DEFLATE))) + if (strstr(*header, "sdch")) { /* - * We successfully decompressed the content, - * and have to clean the header now, so the - * client no longer expects compressed data.. - * - * XXX: There is a difference between cleaning - * and removing it completely. + * Shared Dictionary Compression over HTTP isn't supported, + * filtering it anyway is pretty much guaranteed to mess up + * the encoding. */ - log_error(LOG_LEVEL_HEADER, "Crunching: %s", *header); - freez(*header); + csp->content_type |= CT_TABOO; + + /* + * Log a warning if the user expects the content to be filtered. + */ + if (content_filters_enabled(csp->action)) + { + log_error(LOG_LEVEL_INFO, + "SDCH-compressed content detected, content filtering disabled. " + "Consider suppressing SDCH offers made by the client."); + } } else if (strstr(*header, "gzip")) { @@ -2318,7 +2353,16 @@ static jb_err server_content_encoding(struct client_state *csp, char **header) csp->content_type |= CT_TABOO; } #else /* !defined(FEATURE_ZLIB) */ - if (strstr(*header, "gzip") || strstr(*header, "compress") || strstr(*header, "deflate")) + /* + * XXX: Using a black list here isn't the right approach. + * + * In case of SDCH, building with zlib support isn't + * going to help. + */ + if (strstr(*header, "gzip") || + strstr(*header, "compress") || + strstr(*header, "deflate") || + strstr(*header, "sdch")) { /* * Body is compressed, turn off pcrs and gif filtering. @@ -2344,9 +2388,52 @@ static jb_err server_content_encoding(struct client_state *csp, char **header) } +#ifdef FEATURE_ZLIB +/********************************************************************* + * + * Function : server_adjust_content_encoding + * + * Description : Remove the Content-Encoding header if the + * decompression was successful and the content + * has been modifed. + * + * Parameters : + * 1 : csp = Current client state (buffers, headers, etc...) + * 2 : header = On input, pointer to header to modify. + * On output, pointer to the modified header, or NULL + * to remove the header. This function frees the + * original string if necessary. + * + * Returns : JB_ERR_OK on success, or + * JB_ERR_MEMORY on out-of-memory error. + * + *********************************************************************/ +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))) + { + /* + * We successfully decompressed the content, + * and have to clean the header now, so the + * client no longer expects compressed data. + * + * XXX: There is a difference between cleaning + * and removing it completely. + */ + log_error(LOG_LEVEL_HEADER, "Crunching: %s", *header); + freez(*header); + } + + return JB_ERR_OK; + +} +#endif /* defined(FEATURE_ZLIB) */ + + /********************************************************************* * - * Function : server_content_length + * Function : server_adjust_content_length * * Description : Adjust Content-Length header if we modified * the body. @@ -2362,28 +2449,68 @@ static jb_err server_content_encoding(struct client_state *csp, char **header) * JB_ERR_MEMORY on out-of-memory error. * *********************************************************************/ -static jb_err server_content_length(struct client_state *csp, char **header) +static jb_err server_adjust_content_length(struct client_state *csp, char **header) { - const size_t max_header_length = 80; - /* Regenerate header if the content was modified. */ if (csp->flags & CSP_FLAG_MODIFIED) { + const size_t header_length = 50; freez(*header); - *header = (char *) zalloc(max_header_length); + *header = malloc(header_length); if (*header == NULL) { 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); + } + + return JB_ERR_OK; +} + + +#ifdef FEATURE_CONNECTION_KEEP_ALIVE +/********************************************************************* + * + * Function : server_save_content_length + * + * Description : Save the Content-Length sent by the server. + * + * Parameters : + * 1 : csp = Current client state (buffers, headers, etc...) + * 2 : header = On input, pointer to header to modify. + * On output, pointer to the modified header, or NULL + * to remove the header. This function frees the + * original string if necessary. + * + * Returns : JB_ERR_OK on success, or + * JB_ERR_MEMORY on out-of-memory error. + * + *********************************************************************/ +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) == ':'); - snprintf(*header, max_header_length, "Content-Length: %d", - (int)csp->content_length); - log_error(LOG_LEVEL_HEADER, "Adjusted Content-Length to %d", - (int)csp->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); + } + else + { + csp->expected_content_length = content_length; + csp->flags |= CSP_FLAG_SERVER_CONTENT_LENGTH_SET; + csp->flags |= CSP_FLAG_CONTENT_LENGTH_SET; } return JB_ERR_OK; } +#endif /* def FEATURE_CONNECTION_KEEP_ALIVE */ /********************************************************************* @@ -2420,7 +2547,7 @@ static jb_err server_content_md5(struct client_state *csp, char **header) * * Function : server_content_disposition * - * Description : If enabled, blocks or modifies the "content-disposition" header. + * Description : If enabled, blocks or modifies the "Content-Disposition" header. * Called from `sed'. * * Parameters : @@ -2439,17 +2566,17 @@ static jb_err server_content_disposition(struct client_state *csp, char **header const char *newval; /* - * Are we messing with the content-disposition header? + * Are we messing with the Content-Disposition header? */ if ((csp->action->flags & ACTION_HIDE_CONTENT_DISPOSITION) == 0) { - /*Me tinks not*/ + /* Me tinks not */ return JB_ERR_OK; } newval = csp->action->string[ACTION_STRING_CONTENT_DISPOSITION]; - if ((newval == NULL) || (0 == strcmpic(newval, "block")) ) + if ((newval == NULL) || (0 == strcmpic(newval, "block"))) { /* * Blocking content-disposition header @@ -2459,21 +2586,18 @@ static jb_err server_content_disposition(struct client_state *csp, char **header return JB_ERR_OK; } else - { + { /* - * Replacing content-disposition header + * Replacing Content-Disposition header */ freez(*header); - *header = strdup("content-disposition: "); - string_append(header, newval); + *header = strdup("Content-Disposition: "); + string_append(header, newval); - if (*header == NULL) - { - log_error(LOG_LEVEL_HEADER, "Insufficent memory. content-disposition header not fully replaced."); - } - else + if (*header != NULL) { - log_error(LOG_LEVEL_HEADER, "content-disposition header crunched and replaced with: %s", *header); + log_error(LOG_LEVEL_HEADER, + "Content-Disposition header crunched and replaced with: %s", *header); } } return (*header == NULL) ? JB_ERR_MEMORY : JB_ERR_OK; @@ -2502,17 +2626,9 @@ 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]; -#ifdef HAVE_GMTIME_R - struct tm gmt; -#endif - struct tm *timeptr = NULL; - time_t now, last_modified; - long int rtime; - long int days, hours, minutes, seconds; - + /* * Are we messing with the Last-Modified header? */ @@ -2524,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. @@ -2534,18 +2650,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. */ - get_http_time(0, buf); + 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, "Insufficent memory. Last-Modified header got lost, boohoo."); + log_error(LOG_LEVEL_HEADER, "Insufficient memory. Last-Modified header got lost, boohoo."); } else { @@ -2554,62 +2671,78 @@ 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); - now = time(NULL); -#ifdef HAVE_GMTIME_R - timeptr = gmtime_r(&now, &gmt); -#elif FEATURE_PTHREAD - pthread_mutex_lock(&gmtime_mutex); - timeptr = gmtime(&now); - pthread_mutex_unlock(&gmtime_mutex); -#else - timeptr = gmtime(&now); -#endif - 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 { + 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) { + long int days, hours, minutes, seconds; + const int negative_delta = (rtime < 0); + + if (negative_delta) + { + rtime *= -1; + log_error(LOG_LEVEL_HEADER, "Server time in the future."); + } rtime = pick_from_range(rtime); + if (negative_delta) + { + rtime *= -1; + } last_modified += rtime; #ifdef HAVE_GMTIME_R timeptr = gmtime_r(&last_modified, &gmt); -#elif FEATURE_PTHREAD - pthread_mutex_lock(&gmtime_mutex); +#elif defined(MUTEX_LOCKS_AVAILABLE) + privoxy_mutex_lock(&gmtime_mutex); timeptr = gmtime(&last_modified); - pthread_mutex_unlock(&gmtime_mutex); + privoxy_mutex_unlock(&gmtime_mutex); #else timeptr = gmtime(&last_modified); #endif - strftime(newheader, sizeof(newheader), "%a, %d %b %Y %H:%M:%S GMT", timeptr); + if ((NULL == timeptr) || !strftime(newheader, + sizeof(newheader), "%a, %d %b %Y %H:%M:%S GMT", timeptr)) + { + log_error(LOG_LEVEL_ERROR, + "Randomizing '%s' failed. Crunching the header without replacement.", + *header); + freez(*header); + return JB_ERR_OK; + } + freez(*header); *header = strdup("Last-Modified: "); string_append(header, newheader); if (*header == NULL) { - log_error(LOG_LEVEL_ERROR, "Insufficent memory, header crunched without replacement."); - return JB_ERR_MEMORY; + log_error(LOG_LEVEL_ERROR, "Insufficient memory, header crunched without replacement."); + return JB_ERR_MEMORY; } - if (LOG_LEVEL_HEADER & debug) /* Save cycles if the user isn't interested. */ - { - days = rtime / (3600 * 24); - hours = rtime / 3600 % 24; - minutes = rtime / 60 % 60; - seconds = rtime % 60; - - log_error(LOG_LEVEL_HEADER, "Randomized: %s (added %d da%s %d hou%s %d minut%s %d second%s", - *header, days, (days == 1) ? "y" : "ys", hours, (hours == 1) ? "r" : "rs", - minutes, (minutes == 1) ? "e" : "es", seconds, (seconds == 1) ? ")" : "s)"); - } + days = rtime / (3600 * 24); + hours = rtime / 3600 % 24; + minutes = rtime / 60 % 60; + seconds = rtime % 60; + + log_error(LOG_LEVEL_HEADER, + "Randomized: %s (added %d da%s %d hou%s %d minut%s %d second%s", + *header, days, (days == 1) ? "y" : "ys", hours, (hours == 1) ? "r" : "rs", + minutes, (minutes == 1) ? "e" : "es", seconds, (seconds == 1) ? ")" : "s)"); } else { @@ -2643,25 +2776,17 @@ 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"); - freez(*header); - - /* Temporarily disable the correct behaviour to - * work around a PHP bug. - * - * if (!strcmpic(csp->http->ver, "HTTP/1.1")) - * { - * *header = strdup("Accept-Encoding: identity;q=1.0, *;q=0"); - * if (*header == NULL) - * { - * return JB_ERR_MEMORY; - * } - * } - * - */ } return JB_ERR_OK; @@ -2722,7 +2847,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 @@ -2810,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 @@ -2820,18 +2945,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, - "Insufficent memory. Accept-Language header crunched without replacement."); + "Insufficient memory. Accept-Language header crunched without replacement."); } else { @@ -2872,7 +2997,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); } } @@ -2987,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; @@ -3024,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); @@ -3054,22 +3179,33 @@ static jb_err client_send_cookie(struct client_state *csp, char **header) *********************************************************************/ jb_err client_x_forwarded(struct client_state *csp, char **header) { - if ((csp->action->flags & ACTION_HIDE_FORWARDED) == 0) + if (0 != (csp->action->flags & ACTION_CHANGE_X_FORWARDED_FOR)) { - /* Save it so we can re-add it later */ - freez(csp->x_forwarded); - csp->x_forwarded = *header; + const char *parameter = csp->action->string[ACTION_STRING_CHANGE_X_FORWARDED_FOR]; - /* - * Always set *header = NULL, since this information - * will be sent at the end of the header. - */ - *header = NULL; - } - else - { - freez(*header); - log_error(LOG_LEVEL_HEADER, "crunched x-forwarded-for!"); + if (0 == strcmpic(parameter, "block")) + { + freez(*header); + log_error(LOG_LEVEL_HEADER, "crunched x-forwarded-for!"); + } + else if (0 == strcmpic(parameter, "add")) + { + string_append(header, ", "); + string_append(header, csp->ip_addr_str); + + if (*header == NULL) + { + return JB_ERR_MEMORY; + } + log_error(LOG_LEVEL_HEADER, + "Appended client IP address to %s", *header); + csp->flags |= CSP_FLAG_X_FORWARDED_FOR_APPENDED; + } + else + { + log_error(LOG_LEVEL_FATAL, + "Invalid change-x-forwarded-for parameter: '%s'", parameter); + } } return JB_ERR_OK; @@ -3102,12 +3238,13 @@ static jb_err client_max_forwards(struct client_state *csp, char **header) (0 == strcmpic(csp->http->gpc, "options"))) { assert(*(*header+12) == ':'); - if (1 == sscanf(*header+12, ": %u", &max_forwards)) + if (1 == sscanf(*header+12, ": %d", &max_forwards)) { if (max_forwards > 0) { - snprintf(*header, strlen(*header)+1, "Max-Forwards: %u", --max_forwards); - log_error(LOG_LEVEL_HEADER, "Max-Forwards value for %s request reduced to %u.", + snprintf(*header, strlen(*header)+1, "Max-Forwards: %d", --max_forwards); + log_error(LOG_LEVEL_HEADER, + "Max-Forwards value for %s request reduced to %d.", csp->http->gpc, max_forwards); } else if (max_forwards < 0) @@ -3115,17 +3252,6 @@ static jb_err client_max_forwards(struct client_state *csp, char **header) log_error(LOG_LEVEL_ERROR, "Crunching invalid header: %s", *header); freez(*header); } - else - { - /* - * Not supposed to be reached. direct_response() which - * was already called earlier in chat() should have - * intercepted the request. - */ - log_error(LOG_LEVEL_ERROR, - "Non-intercepted %s request with Max-Forwards zero!", csp->http->gpc); - assert(max_forwards != 0); - } } else { @@ -3146,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. @@ -3164,32 +3287,20 @@ 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; } - 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; @@ -3242,16 +3353,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; - long int rtime; - long int hours, minutes, seconds; - int negative = 0; 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 @@ -3272,24 +3380,25 @@ 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 { - rtime = strtol(newval, &endptr, 0); + long int hours, minutes, seconds; + long int rtime = strtol(newval, &endptr, 0); + const int negative_range = (rtime < 0); + if (rtime) { log_error(LOG_LEVEL_HEADER, "Randomizing: %s (random range: %d minut%s)", *header, rtime, (rtime == 1 || rtime == -1) ? "e": "es"); - if (rtime < 0) + if (negative_range) { - rtime *= -1; - negative = 1; + rtime *= -1; } rtime *= 60; rtime = pick_from_range(rtime); @@ -3299,17 +3408,25 @@ static jb_err client_if_modified_since(struct client_state *csp, char **header) log_error(LOG_LEVEL_ERROR, "Random range is 0. Assuming time transformation test.", *header); } - tm += rtime * (negative ? -1 : 1); + tm += rtime * (negative_range ? -1 : 1); #ifdef HAVE_GMTIME_R timeptr = gmtime_r(&tm, &gmt); -#elif FEATURE_PTHREAD - pthread_mutex_lock(&gmtime_mutex); +#elif defined(MUTEX_LOCKS_AVAILABLE) + privoxy_mutex_lock(&gmtime_mutex); timeptr = gmtime(&tm); - pthread_mutex_unlock(&gmtime_mutex); + privoxy_mutex_unlock(&gmtime_mutex); #else timeptr = gmtime(&tm); #endif - strftime(newheader, sizeof(newheader), "%a, %d %b %Y %H:%M:%S GMT", timeptr); + if ((NULL == timeptr) || !strftime(newheader, + sizeof(newheader), "%a, %d %b %Y %H:%M:%S GMT", timeptr)) + { + log_error(LOG_LEVEL_ERROR, + "Randomizing '%s' failed. Crunching the header without replacement.", + *header); + freez(*header); + return JB_ERR_OK; + } freez(*header); *header = strdup("If-Modified-Since: "); @@ -3317,20 +3434,19 @@ static jb_err client_if_modified_since(struct client_state *csp, char **header) if (*header == NULL) { - log_error(LOG_LEVEL_HEADER, "Insufficent memory, header crunched without replacement."); - return JB_ERR_MEMORY; + log_error(LOG_LEVEL_HEADER, "Insufficient memory, header crunched without replacement."); + return JB_ERR_MEMORY; } - if (LOG_LEVEL_HEADER & debug) /* Save cycles if the user isn't interested. */ - { - hours = rtime / 3600; - minutes = rtime / 60 % 60; - seconds = rtime % 60; + hours = rtime / 3600; + minutes = rtime / 60 % 60; + seconds = rtime % 60; - log_error(LOG_LEVEL_HEADER, "Randomized: %s (%s %d hou%s %d minut%s %d second%s", - *header, (negative) ? "subtracted" : "added", hours, (hours == 1) ? "r" : "rs", - minutes, (minutes == 1) ? "e" : "es", seconds, (seconds == 1) ? ")" : "s)"); - } + log_error(LOG_LEVEL_HEADER, + "Randomized: %s (%s %d hou%s %d minut%s %d second%s", + *header, (negative_range) ? "subtracted" : "added", hours, + (hours == 1) ? "r" : "rs", minutes, (minutes == 1) ? "e" : "es", + seconds, (seconds == 1) ? ")" : "s)"); } } } @@ -3359,7 +3475,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); } @@ -3387,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)) { @@ -3401,7 +3517,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."); @@ -3410,7 +3526,56 @@ jb_err client_x_filter(struct client_state *csp, char **header) freez(*header); } } - return JB_ERR_OK; + return JB_ERR_OK; +} + + +/********************************************************************* + * + * Function : client_range + * + * Description : Removes Range, Request-Range and If-Range headers if + * 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...) + * 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_range(struct client_state *csp, char **header) +{ + 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; } /* the following functions add headers directly to the header list */ @@ -3440,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++; } @@ -3467,99 +3631,6 @@ static jb_err client_host_adder(struct client_state *csp) } -/********************************************************************* - * - * Function : client_cookie_adder - * - * Description : Used in the add_client_headers list to add "wafers". - * Called from `sed'. - * - * Parameters : - * 1 : csp = Current client state (buffers, headers, etc...) - * - * Returns : JB_ERR_OK on success, or - * JB_ERR_MEMORY on out-of-memory error. - * - *********************************************************************/ -jb_err client_cookie_adder(struct client_state *csp) -{ - char *tmp; - struct list_entry *wafer; - struct list_entry *wafer_list; - jb_err err; - - /* - * If the user has not supplied any wafers, and the user has not - * told us to suppress the vanilla wafer, then send the vanilla wafer. - */ - if ((0 != (csp->action->flags & ACTION_VANILLA_WAFER)) - && list_is_empty(csp->action->multi[ACTION_MULTI_WAFER])) - { - enlist(csp->action->multi[ACTION_MULTI_WAFER], VANILLA_WAFER); - } - - wafer_list = csp->action->multi[ACTION_MULTI_WAFER]->first; - - if (NULL == wafer_list) - { - /* Nothing to do */ - return JB_ERR_OK; - } - - tmp = strdup("Cookie: "); - - for (wafer = wafer_list; (NULL != tmp) && (NULL != wafer); wafer = wafer->next) - { - if (wafer != wafer_list) - { - /* As this isn't the first wafer, we need a delimiter. */ - string_append(&tmp, "; "); - } - string_join(&tmp, cookie_encode(wafer->str)); - } - - if (tmp == NULL) - { - return JB_ERR_MEMORY; - } - - log_error(LOG_LEVEL_HEADER, "addh: %s", tmp); - err = enlist(csp->headers, tmp); - free(tmp); - return err; -} - - -#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 @@ -3596,7 +3667,7 @@ static jb_err client_xtra_adder(struct client_state *csp) /********************************************************************* * - * Function : client_x_forwarded_adder + * Function : client_x_forwarded_for_adder * * Description : Used in the add_client_headers list. Called from `sed'. * @@ -3607,35 +3678,34 @@ static jb_err client_xtra_adder(struct client_state *csp) * JB_ERR_MEMORY on out-of-memory error. * *********************************************************************/ -static jb_err client_x_forwarded_adder(struct client_state *csp) +static jb_err client_x_forwarded_for_adder(struct client_state *csp) { - char *p = NULL; + char *header = NULL; jb_err err; - if ((csp->action->flags & ACTION_HIDE_FORWARDED) != 0) + if (!((csp->action->flags & ACTION_CHANGE_X_FORWARDED_FOR) + && (0 == strcmpic(csp->action->string[ACTION_STRING_CHANGE_X_FORWARDED_FOR], "add"))) + || (csp->flags & CSP_FLAG_X_FORWARDED_FOR_APPENDED)) { + /* + * If we aren't adding X-Forwarded-For headers, + * or we already appended an existing X-Forwarded-For + * header, there's nothing left to do here. + */ return JB_ERR_OK; } - if (csp->x_forwarded) - { - p = strdup(csp->x_forwarded); - string_append(&p, ", "); - } - else - { - p = strdup("X-Forwarded-For: "); - } - string_append(&p, csp->ip_addr_str); + header = strdup("X-Forwarded-For: "); + string_append(&header, csp->ip_addr_str); - if (p == NULL) + if (header == NULL) { return JB_ERR_MEMORY; } - log_error(LOG_LEVEL_HEADER, "addh: %s", p); - err = enlist(csp->headers, p); - free(p); + log_error(LOG_LEVEL_HEADER, "addh: %s", header); + err = enlist(csp->headers, header); + freez(header); return err; } @@ -3643,14 +3713,11 @@ static jb_err client_x_forwarded_adder(struct client_state *csp) /********************************************************************* * - * Function : connection_close_adder + * Function : server_connection_adder * - * Description : "Temporary" fix for the needed but missing HTTP/1.1 - * support. Adds a "Connection: close" header to csp->headers + * Description : Adds an appropriate "Connection:" header to csp->headers * unless the header was already present. Called from `sed'. * - * FIXME: This whole function shouldn't be neccessary! - * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) * @@ -3658,30 +3725,115 @@ static jb_err client_x_forwarded_adder(struct client_state *csp) * JB_ERR_MEMORY on out-of-memory error. * *********************************************************************/ -static jb_err connection_close_adder(struct client_state *csp) +static jb_err server_connection_adder(struct client_state *csp) { const unsigned int flags = csp->flags; + const char *response_status_line = csp->headers->first->str; + static const char connection_close[] = "Connection: close"; + + if ((flags & CSP_FLAG_CLIENT_HEADER_PARSING_DONE) + && (flags & CSP_FLAG_SERVER_CONNECTION_HEADER_SET)) + { + return JB_ERR_OK; + } /* - * Return right away if - * - * - we're parsing server headers and the server header - * "Connection: close" is already set, or if - * - * - we're parsing client headers and the client header - * "Connection: close" is already set. + * XXX: if we downgraded the response, this check will fail. */ - if ((flags & CSP_FLAG_CLIENT_HEADER_PARSING_DONE - && flags & CSP_FLAG_SERVER_CONNECTION_CLOSE_SET) - ||(!(flags & CSP_FLAG_CLIENT_HEADER_PARSING_DONE) - && flags & CSP_FLAG_CLIENT_CONNECTION_CLOSE_SET)) + if ((csp->config->feature_flags & + RUNTIME_FEATURE_CONNECTION_KEEP_ALIVE) + && (NULL != response_status_line) + && !strncmpic(response_status_line, "HTTP/1.1", 8) +#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."); + csp->flags |= CSP_FLAG_SERVER_CONNECTION_KEEP_ALIVE; + return JB_ERR_OK; + } + + log_error(LOG_LEVEL_HEADER, "Adding: %s", connection_close); + + return enlist(csp->headers, connection_close); +} + + +#ifdef FEATURE_CONNECTION_KEEP_ALIVE +/********************************************************************* + * + * Function : server_proxy_connection_adder + * + * Description : Adds a "Proxy-Connection: keep-alive" header to + * csp->headers when appropriate. + * + * 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 server_proxy_connection_adder(struct client_state *csp) +{ + static const char proxy_connection_header[] = "Proxy-Connection: keep-alive"; + jb_err err = JB_ERR_OK; + + 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_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); + } + + return err; +} +#endif /* FEATURE_CONNECTION_KEEP_ALIVE */ + + +/********************************************************************* + * + * Function : client_connection_header_adder + * + * Description : Adds a proper "Connection:" header to csp->headers + * unless the header was already present. Called from `sed'. + * + * 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_connection_header_adder(struct client_state *csp) +{ + static const char connection_close[] = "Connection: close"; + + if (!(csp->flags & CSP_FLAG_CLIENT_HEADER_PARSING_DONE) + && (csp->flags & CSP_FLAG_CLIENT_CONNECTION_HEADER_SET)) + { + 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")) { + csp->flags |= CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE; return JB_ERR_OK; } +#endif /* FEATURE_CONNECTION_KEEP_ALIVE */ - log_error(LOG_LEVEL_HEADER, "Adding: Connection: close"); + log_error(LOG_LEVEL_HEADER, "Adding: %s", connection_close); - return enlist(csp->headers, "Connection: close"); + return enlist(csp->headers, connection_close); } @@ -3694,6 +3846,7 @@ static jb_err connection_close_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...) @@ -3703,53 +3856,140 @@ static jb_err connection_close_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()"); + } +} + /********************************************************************* * * Function : server_set_cookie * * Description : Handle the server "cookie" header properly. - * Log cookie to the jar file. Then "crunch", - * accept or rewrite it to a session cookie. + * 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. @@ -3763,47 +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; - struct tm tm_now; - time(&now); - -#ifdef FEATURE_COOKIE_JAR - if (csp->config->jar) - { - /* - * Write timestamp into outbuf. - * - * Complex because not all OSs have tm_gmtoff or - * the %z field in strftime() - */ - char tempbuf[ BUFFER_SIZE ]; - -#ifdef HAVE_LOCALTIME_R - tm_now = *localtime_r(&now, &tm_now); -#elif FEATURE_PTHREAD - pthread_mutex_lock(&localtime_mutex); - tm_now = *localtime (&now); - pthread_mutex_unlock(&localtime_mutex); -#else - tm_now = *localtime (&now); -#endif - strftime(tempbuf, BUFFER_SIZE-6, "%b %d %H:%M:%S ", &tm_now); - - /* strlen("set-cookie: ") = 12 */ - fprintf(csp->config->jar, "%s %s\t%s\n", tempbuf, csp->http->host, *header + 12); - } -#endif /* def FEATURE_COOKIE_JAR */ - - 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; @@ -3812,11 +4028,25 @@ 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++; } + 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) { @@ -3828,7 +4058,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++; } @@ -3848,6 +4078,17 @@ static jb_err server_set_cookie(struct client_state *csp, char **header) { char *expiration_date = cur_tag + 8; /* Skip "[Ee]xpires=" */ + if ((expiration_date[0] == '"') + && (expiration_date[1] != '\0')) + { + /* + * Skip quotation mark. RFC 2109 10.1.2 seems to hint + * that the expiration date isn't supposed to be quoted, + * but some servers do it anyway. + */ + expiration_date++; + } + /* Did we detect the date properly? */ if (JB_ERR_OK != parse_header_time(expiration_date, &cookie_time)) { @@ -3858,8 +4099,8 @@ 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); - memmove(cur_tag, next_tag, strlen(next_tag) + 1); - changed = 1; + string_move(cur_tag, next_tag); + expiry_date_status = EXPIRY_DATE_UNACCEPTABLE; } else { @@ -3897,27 +4138,32 @@ 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 { /* * Still valid, delete expiration date by copying * the rest of the string over it. - * - * (Note that we cannot just use "strcpy(cur_tag, next_tag)", - * since the behaviour of strcpy is undefined for overlapping - * strings.) */ - memmove(cur_tag, next_tag, strlen(next_tag) + 1); + 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, @@ -3934,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); + } } } @@ -3951,7 +4205,7 @@ static jb_err server_set_cookie(struct client_state *csp, char **header) * * Function : strclean * - * Description : In-Situ-Eliminate all occurances of substring in + * Description : In-Situ-Eliminate all occurrences of substring in * string * * Parameters : @@ -3961,7 +4215,7 @@ static jb_err server_set_cookie(struct client_state *csp, char **header) * Returns : Number of eliminations * *********************************************************************/ -int strclean(const char *string, const char *substring) +int strclean(char *string, const char *substring) { int hits = 0; size_t len; @@ -3994,7 +4248,7 @@ int strclean(const 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 @@ -4004,34 +4258,125 @@ int strclean(const 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 time_formats[][22] = { + /* 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)); + + 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); + +#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; + } } - *result = timegm(&gmt); + return JB_ERR_PARSE; - return JB_ERR_OK; +} + +/********************************************************************* + * + * 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); } @@ -4050,9 +4395,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. * @@ -4063,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) @@ -4071,18 +4418,9 @@ jb_err get_destination_from_headers(const struct list *headers, struct http_requ return JB_ERR_PARSE; } - if (NULL == (p = strdup((host)))) - { - 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; @@ -4097,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) @@ -4113,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; } @@ -4123,8 +4481,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 @@ -4214,28 +4572,24 @@ 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); - const size_t hostlenght = strlen(host); - - if (NULL == referer) - { - freez(*header); - return JB_ERR_MEMORY; - } + char *referer = strdup_or_die(*header); + const size_t hostlength = strlen(host); + const char *referer_url = NULL; /* referer begins with 'Referer: http[s]://' */ - if (hostlenght < (strlen(referer)-17)) + if ((hostlength+17) < strlen(referer)) { /* * Shorten referer to make sure the referer is blocked * if www.example.org/www.example.com-shall-see-the-referer/ * links to www.example.com/ */ - referer[hostlenght+17] = '\0'; + referer[hostlength+17] = '\0'; } - if (NULL == strstr(referer, host)) + referer_url = strstr(referer, "http://"); + if ((NULL == referer_url) || (NULL == strstr(referer_url, host))) { - /* Host has changed */ + /* Host has changed, Referer is invalid or a https URL. */ if (parameter_conditional_block) { log_error(LOG_LEVEL_HEADER, "New host is: %s. Crunching %s!", host, *header); @@ -4254,6 +4608,62 @@ static jb_err handle_conditional_hide_referrer_parameter(char **header, } + +/********************************************************************* + * + * Function : create_content_length_header + * + * Description : Creates a Content-Length header. + * + * Parameters : + * 1 : content_length = The content length to be used in the header. + * 2 : header = Allocated space to safe the header. + * 3 : buffer_length = The length of the allocated space. + * + * Returns : void + * + *********************************************************************/ +static void create_content_length_header(unsigned long long content_length, + char *header, size_t buffer_length) +{ + snprintf(header, buffer_length, "Content-Length: %llu", 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