X-Git-Url: http://www.privoxy.org/gitweb/?p=privoxy.git;a=blobdiff_plain;f=parsers.c;h=1d8c07ae22cefab39225338f6a4763d853fdbc7d;hp=eb82e481d2c5a64a52eefa93618188fe973e9ef6;hb=7055dabf9dd0294cc8e1cf78e12a1b606e89d684;hpb=bfe821794c2fd5704c7ea7acd735b6581d95047e diff --git a/parsers.c b/parsers.c index eb82e481..1d8c07ae 100644 --- a/parsers.c +++ b/parsers.c @@ -1,4 +1,4 @@ -const char parsers_rcs[] = "$Id: parsers.c,v 1.85 2007/01/26 15:33:46 fabiankeil Exp $"; +const char parsers_rcs[] = "$Id: parsers.c,v 1.91 2007/02/24 12:27:32 fabiankeil Exp $"; /********************************************************************* * * File : $Source: /cvsroot/ijbswa/current/parsers.c,v $ @@ -45,6 +45,40 @@ const char parsers_rcs[] = "$Id: parsers.c,v 1.85 2007/01/26 15:33:46 fabiankeil * * Revisions : * $Log: parsers.c,v $ + * 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 @@ -650,7 +684,7 @@ const struct parsers client_patterns[] = { { "Host:", 5, client_host }, { "if-modified-since:", 18, client_if_modified_since }, { "Keep-Alive:", 11, crumble }, - { "connection:", 11, crumble }, + { "connection:", 11, connection }, { "proxy-connection:", 17, crumble }, { "max-forwards:", 13, client_max_forwards }, { "Accept-Language:", 16, client_accept_language }, @@ -664,9 +698,8 @@ const struct parsers client_patterns[] = { const struct parsers server_patterns[] = { { "HTTP", 4, server_http }, { "set-cookie:", 11, server_set_cookie }, - { "connection:", 11, crumble }, + { "connection:", 11, connection }, { "Content-Type:", 13, server_content_type }, - { "Content-Length:", 15, server_content_length }, { "Content-MD5:", 12, server_content_md5 }, { "Content-Encoding:", 17, server_content_encoding }, { "Transfer-Encoding:", 18, server_transfer_coding }, @@ -838,6 +871,8 @@ 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 + * iob->cur unmodified if we return early) */ size_t bufsize; /* allocated size of the new buffer */ size_t skip_size; /* Number of bytes at the beginning of the iob that we should NOT decompress. */ @@ -847,6 +882,8 @@ jb_err decompress_iob(struct client_state *csp) bufsize = csp->iob->size; skip_size = (size_t)(csp->iob->cur - csp->iob->buf); + cur = csp->iob->cur; + if (bufsize < 10) { /* @@ -871,16 +908,16 @@ 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 ((*csp->iob->cur++ != (char)0x1f) - || (*csp->iob->cur++ != (char)0x8b) - || (*csp->iob->cur++ != Z_DEFLATED)) + if ((*cur++ != (char)0x1f) + || (*cur++ != (char)0x8b) + || (*cur++ != Z_DEFLATED)) { log_error (LOG_LEVEL_ERROR, "Invalid gzip header when decompressing"); return JB_ERR_COMPRESS; } else { - int flags = *csp->iob->cur++; + int flags = *cur++; /* * XXX: These magic numbers should be replaced * with macros to give a better idea what they do. @@ -888,10 +925,10 @@ jb_err decompress_iob(struct client_state *csp) if (flags & 0xe0) { /* The gzip header has reserved bits set; bail out. */ - log_error (LOG_LEVEL_ERROR, "Invalid gzip header when decompressing"); + log_error (LOG_LEVEL_ERROR, "Invalid gzip header flags when decompressing"); return JB_ERR_COMPRESS; } - csp->iob->cur += 6; + cur += 6; /* Skip extra fields if necessary. */ if (flags & 0x04) @@ -900,26 +937,63 @@ jb_err decompress_iob(struct client_state *csp) * Skip a given number of bytes, specified * as a 16-bit little-endian value. */ - csp->iob->cur += *csp->iob->cur++ + (*csp->iob->cur++ << 8); + /* + * XXX: This code used to be: + * + * csp->iob->cur += *csp->iob->cur++ + (*csp->iob->cur++ << 8); + * + * which I had to change into: + * + * cur += *cur++ + (*cur++ << 8); + * + * at which point gcc43 finally noticed that the value + * of cur is undefined (it depends on which of the + * summands is evaluated first). + * + * I haven't come across a site where this + * code is actually executed yet, but I hope + * it works anyway. + */ + int skip_bytes; + skip_bytes = *cur++; + skip_bytes = *cur++ << 8; + + assert(skip_bytes == *csp->iob->cur-2 + ((*csp->iob->cur-1) << 8)); + + /* + * The number of bytes to skip should be positive + * and we'd like to stay in the buffer. + */ + if((skip_bytes < 0) || (skip_bytes >= (csp->iob->eod - cur))) + { + log_error (LOG_LEVEL_ERROR, + "Unreasonable amount of bytes to skip (%d). Stopping decompression", + skip_bytes); + return JB_ERR_COMPRESS; + } + log_error (LOG_LEVEL_INFO, + "Skipping %d bytes for gzip compression. Does this sound right?", + skip_bytes); + cur += skip_bytes; } /* Skip the filename if necessary. */ if (flags & 0x08) { /* A null-terminated string follows. */ - while (*csp->iob->cur++); + while (*cur++); } /* Skip the comment if necessary. */ if (flags & 0x10) { - while (*csp->iob->cur++); + while (*cur++); } /* Skip the CRC if necessary. */ if (flags & 0x02) { - csp->iob->cur += 2; + cur += 2; } } } @@ -929,7 +1003,7 @@ jb_err decompress_iob(struct client_state *csp) * XXX: The debug level should be lowered * before the next stable release. */ - log_error (LOG_LEVEL_INFO, "Decompressing deflated iob: %d", *csp->iob->cur); + log_error (LOG_LEVEL_INFO, "Decompressing deflated iob: %d", *cur); /* * In theory (that is, according to RFC 1950), deflate-compressed * data should begin with a two-byte zlib header and have an @@ -955,8 +1029,8 @@ jb_err decompress_iob(struct client_state *csp) } /* Set up the fields required by zlib. */ - zstr.next_in = (Bytef *)csp->iob->cur; - zstr.avail_in = (unsigned int)(csp->iob->eod - csp->iob->cur); + zstr.next_in = (Bytef *)cur; + zstr.avail_in = (unsigned int)(csp->iob->eod - cur); zstr.zalloc = Z_NULL; zstr.zfree = Z_NULL; zstr.opaque = Z_NULL; @@ -1474,7 +1548,7 @@ jb_err filter_header(struct client_state *csp, char **header) continue; } - log_error(LOG_LEVEL_RE_FILTER, "re_filtering %s (size %d) with filter %s...", + log_error(LOG_LEVEL_RE_FILTER, "filtering \'%s\' (size %d) with \'%s\' ...", *header, size, b->name); /* Apply all jobs from the joblist */ @@ -1505,7 +1579,7 @@ jb_err filter_header(struct client_state *csp, char **header) } } } - log_error(LOG_LEVEL_RE_FILTER, " ...produced %d hits (new size %d).", current_hits, size); + log_error(LOG_LEVEL_RE_FILTER, "... produced %d hits (new size %d).", current_hits, size); hits += current_hits; } } @@ -1527,6 +1601,56 @@ jb_err filter_header(struct client_state *csp, char **header) } +/********************************************************************* + * + * Function : connection + * + * Description : Makes sure that the value of the Connection: header + * is "close" and signals connection_close_adder + * to do nothing. + * + * Parameters : + * 1 : csp = Current client state (buffers, headers, etc...) + * 2 : header = On input, pointer to header to modify. + * On output, pointer to the modified header, or NULL + * to remove the header. This function frees the + * original string if necessary. + * + * Returns : JB_ERR_OK on success, or + * JB_ERR_MEMORY on out-of-memory error. + * + *********************************************************************/ +jb_err connection(struct client_state *csp, char **header) +{ + char *old_header = *header; + + /* Do we have a 'Connection: close' header? */ + if (strcmpic(*header, "Connection: close")) + { + /* No, create one */ + *header = strdup("Connection: close"); + if (header == NULL) + { + return JB_ERR_MEMORY; + } + log_error(LOG_LEVEL_HEADER, "Replaced: \'%s\' with \'%s\'", old_header, *header); + freez(old_header); + } + + /* Signal connection_close_adder() to return early. */ + if (csp->flags & CSP_FLAG_CLIENT_HEADER_PARSING_DONE) + { + csp->flags |= CSP_FLAG_SERVER_CONNECTION_CLOSE_SET; + } + else + { + csp->flags |= CSP_FLAG_CLIENT_CONNECTION_CLOSE_SET; + } + + return JB_ERR_OK; +} + + /********************************************************************* * * Function : crumble @@ -1571,13 +1695,13 @@ jb_err crumble(struct client_state *csp, char **header) jb_err crunch_server_header(struct client_state *csp, char **header) { const char *crunch_pattern; - /*Is there a header to crunch*/ + /* Do we feel like crunching? */ if ((csp->action->flags & ACTION_CRUNCH_SERVER_HEADER)) { crunch_pattern = csp->action->string[ACTION_STRING_SERVER_HEADER]; - /*Is the current header the lucky one?*/ + /* Is the current header the lucky one? */ if (strstr(*header, crunch_pattern)) { log_error(LOG_LEVEL_HEADER, "Crunching server header: %s (contains: %s)", *header, crunch_pattern); @@ -1792,8 +1916,8 @@ jb_err server_transfer_coding(struct client_state *csp, char **header) jb_err server_content_encoding(struct client_state *csp, char **header) { #ifdef FEATURE_ZLIB - /* XXX: Why would we modify the content if it was taboo? */ - if ((csp->flags & CSP_FLAG_MODIFIED) && !(csp->content_type & CT_TABOO)) + if ((csp->flags & CSP_FLAG_MODIFIED) + && (csp->content_type & (CT_GZIP | CT_DEFLATE))) { /* * We successfully decompressed the content, @@ -1860,12 +1984,10 @@ jb_err server_content_encoding(struct client_state *csp, char **header) jb_err server_content_length(struct client_state *csp, char **header) { const size_t max_header_length = 80; - if (csp->content_length != 0) /* Content length could have been modified */ + + /* Regenerate header if the content was modified. */ + if (csp->flags & CSP_FLAG_MODIFIED) { - /* - * XXX: Shouldn't we check if csp->content_length - * is different than the original value? - */ freez(*header); *header = (char *) zalloc(max_header_length); if (*header == NULL) @@ -2114,6 +2236,7 @@ jb_err server_last_modified(struct client_state *csp, char **header) return JB_ERR_OK; } + /********************************************************************* * * Function : client_accept_encoding @@ -2401,13 +2524,13 @@ jb_err client_accept_language(struct client_state *csp, char **header) jb_err crunch_client_header(struct client_state *csp, char **header) { const char *crunch_pattern; - /*Is there a header to crunch*/ - + + /* Do we feel like crunching? */ if ((csp->action->flags & ACTION_CRUNCH_CLIENT_HEADER)) { crunch_pattern = csp->action->string[ACTION_STRING_CLIENT_HEADER]; - /*Is the current header the lucky one?*/ + /* Is the current header the lucky one? */ if (strstr(*header, crunch_pattern)) { log_error(LOG_LEVEL_HEADER, "Crunching client header: %s (contains: %s)", *header, crunch_pattern); @@ -2543,9 +2666,10 @@ jb_err client_from(struct client_state *csp, char **header) * * Function : client_send_cookie * - * Description : Handle the "cookie" header properly. Called from `sed'. - * If cookie is accepted, add it to the cookie_list, - * else we crunch it. Mmmmmmmmmmm ... cookie ...... + * Description : Crunches the "cookie" header if necessary. + * Called from `sed'. + * + * XXX: Stupid name, doesn't send squat. * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) @@ -2560,25 +2684,13 @@ jb_err client_from(struct client_state *csp, char **header) *********************************************************************/ jb_err client_send_cookie(struct client_state *csp, char **header) { - jb_err result = JB_ERR_OK; - - if ((csp->action->flags & ACTION_NO_COOKIE_READ) == 0) + if (csp->action->flags & ACTION_NO_COOKIE_READ) { - /* strlen("cookie: ") == 8 */ - result = enlist(csp->cookie_list, *header + 8); - } - else - { - log_error(LOG_LEVEL_HEADER, "Crunched outgoing cookie -- yum!"); + log_error(LOG_LEVEL_HEADER, "Crunched outgoing cookie: %s", *header); + freez(*header); } - /* - * Always remove the cookie here. The cookie header - * will be sent at the end of the header. - */ - freez(*header); - - return result; + return JB_ERR_OK; } @@ -2644,25 +2756,31 @@ jb_err client_x_forwarded(struct client_state *csp, char **header) *********************************************************************/ jb_err client_max_forwards(struct client_state *csp, char **header) { - unsigned int max_forwards; + int max_forwards; if ((0 == strcmpic(csp->http->gpc, "trace")) || (0 == strcmpic(csp->http->gpc, "options"))) { - if (1 == sscanf(*header, "Max-Forwards: %u", &max_forwards)) + assert(*(*header+12) == ':'); + if (1 == sscanf(*header+12, ": %u", &max_forwards)) { if (max_forwards > 0) { snprintf(*header, strlen(*header)+1, "Max-Forwards: %u", --max_forwards); - log_error(LOG_LEVEL_HEADER, "Max-Forwards header for %s request replaced with: %s", - csp->http->gpc, *header); + log_error(LOG_LEVEL_HEADER, "Max-Forwards value for %s request reduced to %u.", + csp->http->gpc, max_forwards); + } + else if (max_forwards < 0) + { + log_error(LOG_LEVEL_ERROR, "Crunching invalid header: %s", *header); + freez(*header); } else { /* - * direct_response() which was called earlier - * in chat() should prevent that we ever get - * here. + * Not supposed to be reached. direct_response() which + * was already called earlier in chat() should have + * intercepted the request. */ log_error(LOG_LEVEL_ERROR, "Non-intercepted %s request with Max-Forwards zero!", csp->http->gpc); @@ -2753,6 +2871,9 @@ jb_err client_host(struct client_state *csp, char **header) csp->http->hostport, csp->http->host, csp->http->port); } + /* Signal client_host_adder() to return right away */ + csp->flags |= CSP_FLAG_HOST_HEADER_IS_SET; + return JB_ERR_OK; } @@ -2969,8 +3090,16 @@ jb_err client_host_adder(struct client_state *csp) char *p; jb_err err; + if (csp->flags & CSP_FLAG_HOST_HEADER_IS_SET) + { + /* Header already set by the client, nothing to do. */ + return JB_ERR_OK; + } + if ( !csp->http->hostport || !*(csp->http->hostport)) { + /* XXX: When does this happen and why is it OK? */ + log_error(LOG_LEVEL_INFO, "Weirdness in client_host_adder detected and ignored."); return JB_ERR_OK; } @@ -2986,6 +3115,7 @@ jb_err client_host_adder(struct client_state *csp) p = csp->http->hostport; } + /* XXX: Just add it, we already made sure that it will be unique */ log_error(LOG_LEVEL_HEADER, "addh-unique: Host: %s", p); err = enlist_unique_header(csp->headers, "Host", p); return err; @@ -2999,6 +3129,8 @@ jb_err client_host_adder(struct client_state *csp) * * Description : Used in the add_client_headers list. Called from `sed'. * + * XXX: Remove csp->cookie_list which is no longer used. + * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) * @@ -3079,6 +3211,8 @@ jb_err client_cookie_adder(struct client_state *csp) *********************************************************************/ jb_err client_accept_encoding_adder(struct client_state *csp) { + assert(0); /* Not in use */ + if ( ((csp->action->flags & ACTION_NO_COMPRESSION) != 0) && (!strcmpic(csp->http->ver, "HTTP/1.1")) ) { @@ -3174,9 +3308,10 @@ jb_err client_x_forwarded_adder(struct client_state *csp) * * Function : connection_close_adder * - * Description : Adds a "Connection: close" header to csp->headers - * as a temporary fix for the needed but missing HTTP/1.1 - * support. Called from `sed'. + * Description : "Temporary" fix for the needed but missing HTTP/1.1 + * support. Adds a "Connection: close" header to csp->headers + * unless the header was already present. Called from `sed'. + * * FIXME: This whole function shouldn't be neccessary! * * Parameters : @@ -3188,7 +3323,27 @@ jb_err client_x_forwarded_adder(struct client_state *csp) *********************************************************************/ jb_err connection_close_adder(struct client_state *csp) { + const unsigned int flags = csp->flags; + + /* + * Return right away if + * + * - we're parsing server headers and the server header + * "Connection: close" is already set, or if + * + * - we're parsing client headers and the client header + * "Connection: close" is already set. + */ + if ((flags & CSP_FLAG_CLIENT_HEADER_PARSING_DONE + && flags & CSP_FLAG_SERVER_CONNECTION_CLOSE_SET) + ||(!(flags & CSP_FLAG_CLIENT_HEADER_PARSING_DONE) + && flags & CSP_FLAG_CLIENT_CONNECTION_CLOSE_SET)) + { + return JB_ERR_OK; + } + log_error(LOG_LEVEL_HEADER, "Adding: Connection: close"); + return enlist(csp->headers, "Connection: close"); } @@ -3216,6 +3371,9 @@ jb_err connection_close_adder(struct client_state *csp) *********************************************************************/ jb_err server_http(struct client_state *csp, char **header) { + /* Signal that were now parsing server headers. */ + csp->flags |= CSP_FLAG_CLIENT_HEADER_PARSING_DONE; + sscanf(*header, "HTTP/%*d.%*d %d", &(csp->http->status)); if (csp->http->status == 206) { @@ -3279,10 +3437,10 @@ jb_err server_set_cookie(struct client_state *csp, char **header) tm_now = *localtime_r(&now, &tm_now); #elif FEATURE_PTHREAD pthread_mutex_lock(&localtime_mutex); - tm_now = *localtime (&now); + tm_now = *localtime (&now); pthread_mutex_unlock(&localtime_mutex); #else - tm_now = *localtime (&now); + tm_now = *localtime (&now); #endif strftime(tempbuf, BUFFER_SIZE-6, "%b %d %H:%M:%S ", &tm_now); @@ -3340,35 +3498,60 @@ jb_err server_set_cookie(struct client_state *csp, char **header) * if the cookie is still valid, if yes, * rewrite it to a session cookie. */ - if (strncmpic(cur_tag, "expires=", 8) == 0) + if ((strncmpic(cur_tag, "expires=", 8) == 0) && *(cur_tag + 8)) { char *match; + const char *expiration_date = cur_tag + 8; /* Skip "[Ee]xpires=" */ + memset(&tm_cookie, 0, sizeof(tm_cookie)); /* * Try the valid time formats we know about. * + * XXX: This should be moved to parse_header_time(). + * * XXX: Maybe the log messages should be removed * for the next stable release. They just exist to * see which time format gets the most hits and * should be checked for first. */ - if (NULL != (match = strptime(cur_tag, "expires=%a, %e-%b-%y %H:%M:%S ", &tm_cookie))) + if (NULL != (match = strptime(expiration_date, "%a, %e-%b-%y %H:%M:%S ", &tm_cookie))) { + /* 22-Feb-2008 12:01:18 GMT */ log_error(LOG_LEVEL_HEADER, "cookie \'%s\' send by %s appears to be using time format 1.", - csp->http->url, *header); + *header, csp->http->url); } - else if (NULL != (match = strptime(cur_tag, "expires=%A, %e-%b-%Y %H:%M:%S ", &tm_cookie))) + else if (NULL != (match = strptime(expiration_date, "%A, %e-%b-%Y %H:%M:%S ", &tm_cookie))) { + /* Tue, 02-Jun-2037 20:00:00 GMT */ log_error(LOG_LEVEL_HEADER, "cookie \'%s\' send by %s appears to be using time format 2.", - csp->http->url, *header); - + *header, csp->http->url); } - else if (NULL != (match = strptime(cur_tag, "expires=%a, %e-%b-%Y %H:%M:%S ", &tm_cookie))) + else if (NULL != (match = strptime(expiration_date, "%a, %e-%b-%Y %H:%M:%S ", &tm_cookie))) { + /* Tuesday, 02-Jun-2037 20:00:00 GMT */ + /* + * On FreeBSD this is never reached because it's handled + * by "format 2" as well. I am, however, not sure if all + * strptime() implementations behave that way. + */ log_error(LOG_LEVEL_HEADER, "cookie \'%s\' send by %s appears to be using time format 3.", - csp->http->url, *header); + *header, csp->http->url); + } + else if (NULL != (match = strptime(expiration_date, "%a, %e %b %Y %H:%M:%S ", &tm_cookie))) + { + /* Fri, 22 Feb 2008 19:20:05 GMT */ + log_error(LOG_LEVEL_HEADER, + "cookie \'%s\' send by %s appears to be using time format 4.", + *header, csp->http->url); + } + else if (NULL != (match = strptime(expiration_date, "%A %b %e %H:%M:%S %Y", &tm_cookie))) + { + /* Thu Mar 08 23:00:00 2007 GMT */ + log_error(LOG_LEVEL_HEADER, + "cookie \'%s\' send by %s appears to be using time format 5.", + *header, csp->http->url); } /* Did any of them match? */ @@ -3380,7 +3563,7 @@ jb_err server_set_cookie(struct client_state *csp, char **header) * XXX: Should we remove the whole cookie instead? */ log_error(LOG_LEVEL_ERROR, - "Can't parse %s. Unsupported time format?", cur_tag); + "Can't parse \'%s\', send by %s. Unsupported time format?", cur_tag, csp->http->url); memmove(cur_tag, next_tag, strlen(next_tag) + 1); changed = 1; } @@ -3430,11 +3613,10 @@ jb_err server_set_cookie(struct client_state *csp, char **header) } else { - log_error(LOG_LEVEL_HEADER, - "Cookie \'%s\' is still valid and has to be rewritten.", *header); - /* - * Delete the tag by copying the rest of the string over it. + * Still valid, delete expiration date by copying + * the rest of the string over it. + * * (Note that we cannot just use "strcpy(cur_tag, next_tag)", * since the behaviour of strcpy is undefined for overlapping * strings.)