-const char parsers_rcs[] = "$Id: parsers.c,v 1.82 2007/01/01 19:36:37 fabiankeil Exp $";
+const char parsers_rcs[] = "$Id: parsers.c,v 1.92 2007/03/05 13:25:32 fabiankeil Exp $";
/*********************************************************************
*
* File : $Source: /cvsroot/ijbswa/current/parsers.c,v $
* `client_uagent', `client_x_forwarded',
* `client_x_forwarded_adder', `client_xtra_adder',
* `content_type', `crumble', `destroy_list', `enlist',
- * `flush_socket', ``get_header', `sed', `filter_server_header'
- * `filter_client_header', `filter_header', `crunch_server_header',
+ * `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' and `server_set_cookie'.
+ * `parse_header_time', `decompress_iob' and `server_set_cookie'.
*
- * Copyright : Written by and Copyright (C) 2001-2006 the SourceForge
+ * Copyright : Written by and Copyright (C) 2001-2007 the SourceForge
* Privoxy team. http://www.privoxy.org/
*
* Based on the Internet Junkbuster originally written
*
* Revisions :
* $Log: parsers.c,v $
+ * 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).
{ "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 },
{ "if-none-match:", 14, client_if_none_match },
{ "X-Filter:", 9, client_x_filter },
{ "*", 0, crunch_client_header },
- { "*", 0, filter_client_header },
+ { "*", 0, filter_header },
{ NULL, 0, NULL }
};
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 },
{ "content-disposition:", 20, server_content_disposition },
{ "Last-Modified:", 14, server_last_modified },
{ "*", 0, crunch_server_header },
- { "*", 0, filter_server_header },
+ { "*", 0, filter_header },
{ NULL, 0, NULL }
};
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. */
int status; /* return status of the inflate() call */
z_stream zstr; /* used by calls to zlib */
+ assert(csp->iob->cur - csp->iob->buf > 0);
+ assert(csp->iob->eod - csp->iob->cur > 0);
+
bufsize = csp->iob->size;
skip_size = (size_t)(csp->iob->cur - csp->iob->buf);
+ cur = csp->iob->cur;
+
if (bufsize < 10)
{
/*
* 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.
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)
* 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;
}
}
}
* 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
}
/* 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;
if (first_run) /* Parse and print */
{
+ log_error(LOG_LEVEL_HEADER, "scanning headers for: %s", csp->http->url);
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)
/* here begins the family of parser functions that reformat header lines */
-/*********************************************************************
- *
- * Function : filter_server_header
- *
- * Description : Checks if server header filtering is enabled.
- * If it is, filter_header is called to do the work.
- *
- * 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
- *
- *********************************************************************/
-jb_err filter_server_header(struct client_state *csp, char **header)
-{
- if (csp->action->flags & ACTION_FILTER_SERVER_HEADERS)
- {
- filter_header(csp, header);
- }
- return(JB_ERR_OK);
-}
-
-/*********************************************************************
- *
- * Function : filter_client_header
- *
- * Description : Checks if client header filtering is enabled.
- * If it is, filter_header is called to do the work.
- *
- * 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
- *
- *********************************************************************/
-jb_err filter_client_header(struct client_state *csp, char **header)
-{
- if (csp->action->flags & ACTION_FILTER_CLIENT_HEADERS)
- {
- filter_header(csp, header);
- }
- return(JB_ERR_OK);
-}
-
/*********************************************************************
*
* Function : filter_header
*
* Description : Executes all text substitutions from all applying
- * +filter actions on the header.
+ * +(server|client)-header-filter actions on the header.
* Most of the code was copied from pcrs_filter_response,
* including the rather short variable names
*
struct list_entry *filtername;
int i, found_filters = 0;
+ int wanted_filter_type;
+ int multi_action_index;
+
+ 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;
+ }
/*
* Need to check the set of re_filterfiles...
*/
for (b = fl->f; b; b = b->next)
{
- for (filtername = csp->action->multi[ACTION_MULTI_FILTER]->first;
+ 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)
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 */
}
}
}
- 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;
}
}
}
}
- if ( 0 == size )
+ /*
+ * 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);
}
+/*********************************************************************
+ *
+ * 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
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);
return JB_ERR_OK;
}
+
+
/*********************************************************************
*
* Function : server_content_type
*
* Description : Set the content-type for filterable types (text/.*,
- * javascript and image/gif) unless filtering has been
+ * .*xml.*, javascript 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
*********************************************************************/
jb_err server_content_type(struct client_state *csp, char **header)
{
- const char *newval;
-
- newval = csp->action->string[ACTION_STRING_CONTENT_TYPE];
+ /* Remove header if it isn't the first Content-Type header */
+ if(csp->content_type && (csp->content_type != CT_TABOO))
+ {
+ /*
+ * Another, slightly slower, way to see if
+ * we already parsed another Content-Type header.
+ */
+ assert(NULL != get_header_value(csp->headers, "Content-Type:"));
- assert(!csp->content_type || (csp->content_type == CT_TABOO));
+ log_error(LOG_LEVEL_ERROR,
+ "Multiple Content-Type headers. Removing and ignoring: \'%s\'",
+ *header);
+ freez(*header);
+
+ return JB_ERR_OK;
+ }
if (!(csp->content_type & CT_TABOO))
{
{
freez(*header);
*header = strdup("Content-Type: ");
- string_append(header, newval);
+ string_append(header, csp->action->string[ACTION_STRING_CONTENT_TYPE]);
if (header == NULL)
{
- log_error(LOG_LEVEL_HEADER,
- "Insufficient memory. Content-Type crunched without replacement!");
+ log_error(LOG_LEVEL_HEADER, "Insufficient memory to replace Content-Type!");
return JB_ERR_MEMORY;
}
log_error(LOG_LEVEL_HEADER, "Modified: %s!", *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,
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)
return JB_ERR_OK;
}
+
/*********************************************************************
*
* Function : client_accept_encoding
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);
*
* 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...)
*********************************************************************/
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)
- {
- /* strlen("cookie: ") == 8 */
- result = enlist(csp->cookie_list, *header + 8);
- }
- else
+ if (csp->action->flags & ACTION_NO_COOKIE_READ)
{
- 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;
}
*********************************************************************/
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)
+ 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.",
+ csp->http->gpc, max_forwards);
+ }
+ else 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_ERROR, "Crunching invalid header: %s", *header);
+ freez(*header);
}
else
{
- /* FIXME: Follow spec and intercept the request. */
+ /*
+ * 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
+ {
+ log_error(LOG_LEVEL_ERROR, "Crunching invalid header: %s", *header);
+ freez(*header);
+ }
}
return JB_ERR_OK;
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;
}
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;
}
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;
*
* 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...)
*
*********************************************************************/
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")) )
{
*
* 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 :
*********************************************************************/
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");
}
*********************************************************************/
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)
{
* Function : server_set_cookie
*
* Description : Handle the server "cookie" header properly.
- * Log cookie to the jar file. Then "crunch" it,
- * or accept it. Called from `sed'.
+ * Log cookie to the jar file. Then "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...)
*********************************************************************/
jb_err server_set_cookie(struct client_state *csp, char **header)
{
+ time_t now;
+ time_t cookie_time;
+ struct tm tm_now;
+ struct tm tm_cookie;
+ time(&now);
+
#ifdef FEATURE_COOKIE_JAR
if (csp->config->jar)
{
* the %z field in strftime()
*/
char tempbuf[ BUFFER_SIZE ];
- time_t now;
- struct tm tm_now;
- time (&now);
+
#ifdef HAVE_LOCALTIME_R
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);
next_tag = cur_tag + strlen(cur_tag);
}
- /* Is this the "Expires" tag? */
- if (strncmpic(cur_tag, "expires=", 8) == 0)
+ /*
+ * Check the expiration date to see
+ * if the cookie is still valid, if yes,
+ * rewrite it to a session cookie.
+ */
+ if ((strncmpic(cur_tag, "expires=", 8) == 0) && *(cur_tag + 8))
{
- /* Delete the tag 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.)
+ 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.
*/
- memmove(cur_tag, next_tag, strlen(next_tag) + 1);
+ 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.",
+ *header, csp->http->url);
+ }
+ 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.",
+ *header, csp->http->url);
+ }
+ 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.",
+ *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);
+ }
- /* That changed the header, need to issue a log message */
- changed = 1;
+ /* Did any of them match? */
+ if (NULL == match)
+ {
+ /*
+ * Nope, treat it as if it was still valid.
+ *
+ * XXX: Should we remove the whole cookie instead?
+ */
+ 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;
+ }
+ else
+ {
+ /*
+ * Yes. Check if the cookie is still valid.
+ *
+ * If the cookie is already expired it's probably
+ * a delete cookie and even if it isn't, the browser
+ * will discard it anyway.
+ */
+
+ /*
+ * XXX: timegm() isn't available on some AmigaOS
+ * versions and our replacement doesn't work.
+ *
+ * Our options are to either:
+ *
+ * - disable session-cookies-only completely if timegm
+ * is missing,
+ *
+ * - to simply remove all expired tags, like it has
+ * been done until Privoxy 3.0.6 and to live with
+ * the consequence that it can cause login/logout
+ * problems on servers that don't validate their
+ * input properly, or
+ *
+ * - to replace it with mktime in which
+ * case there is a slight chance of valid cookies
+ * passing as already expired.
+ *
+ * This is the way it's currently done and it's not
+ * as bad as it sounds. If the missing GMT offset is
+ * enough to change the result of the expiration check
+ * the cookie will be only valid for a few hours
+ * anyway, which in many cases will be shorter
+ * than a browser session.
+ */
+ cookie_time = timegm(&tm_cookie);
+ if (cookie_time - now < 0)
+ {
+ 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;
+ }
+ 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);
+
+ /* That changed the header, need to issue a log message */
+ changed = 1;
+
+ /*
+ * Note that the next tag has now been moved to *cur_tag,
+ * so we do not need to update the cur_tag pointer.
+ */
+ }
+ }
- /* Note that the next tag has now been moved to *cur_tag,
- * so we do not need to update the cur_tag pointer.
- */
}
else
{
if (changed)
{
- log_error(LOG_LEVEL_HEADER, "Changed cookie to a temporary one.");
+ assert(NULL != *header);
+ log_error(LOG_LEVEL_HEADER, "Cookie rewritten to a temporary one: %s",
+ *header);
}
}