-const char parsers_rcs[] = "$Id: parsers.c,v 1.282 2013/12/24 13:34:45 fabiankeil Exp $";
+const char parsers_rcs[] = "$Id: parsers.c,v 1.307 2016/01/17 14:31:47 fabiankeil Exp $";
/*********************************************************************
*
* File : $Source: /cvsroot/ijbswa/current/parsers.c,v $
*
* Purpose : Declares functions to parse/crunch headers and pages.
*
- * Copyright : Written by and Copyright (C) 2001-2012 the
+ * Copyright : Written by and Copyright (C) 2001-2016 the
* Privoxy team. http://www.privoxy.org/
*
* Based on the Internet Junkbuster originally written
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 filter_header (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);
#if 0
{ "Transfer-Encoding:", 18, client_transfer_encoding },
#endif
+ { "Expect:", 7, client_expect },
{ "*", 0, crunch_client_header },
{ "*", 0, filter_header },
{ NULL, 0, NULL }
void clear_iob(struct iob *iob)
{
free(iob->buf);
- memset(iob, '\0', sizeof(*iob));;
+ memset(iob, '\0', sizeof(*iob));
}
* 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;
}
*
* Function : normalize_lws
*
- * Description : Reduces unquoted linear white space in headers
- * to a single space in accordance with RFC 2616 2.2.
+ * 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.
*
- * XXX: Remove log messages before
- * the next stable release?
- *
* Parameters :
- * 1 : header = A header with linear white space to reduce.
+ * 1 : header = A header with linear whitespace to reduce.
*
* Returns : N/A
*
{
q++;
}
- log_error(LOG_LEVEL_HEADER, "Reducing white space in '%s'", header);
+ log_error(LOG_LEVEL_HEADER, "Reducing whitespace in '%s'", header);
string_move(p+1, q);
}
* server and header filtering.
*
* Returns : JB_ERR_OK in case off success, or
- * JB_ERR_MEMORY on out-of-memory error.
+ * 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)
check_negative_tag_patterns(csp, PATTERN_SPEC_NO_REQUEST_TAG_PATTERN);
}
- while ((err == JB_ERR_OK) && (v->str != NULL))
+ while (v->str != NULL)
{
- for (p = csp->headers->first; (err == JB_ERR_OK) && (p != NULL); p = p->next)
+ for (p = csp->headers->first; p != NULL; p = p->next)
{
/* Header crunch()ed in previous run? -> ignore */
if (p->str == NULL) continue;
(v->len == CHECK_EVERY_HEADER_REMAINING))
{
err = v->parser(csp, &(p->str));
+ if (err != JB_ERR_OK)
+ {
+ return err;
+ }
}
}
v++;
*********************************************************************/
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;
pcrs_job *job;
struct re_filterfile_spec *b;
struct list_entry *filtername;
- int wanted_filter_type;
+ enum filter_type wanted_filter_type;
int multi_action_index;
if (csp->flags & CSP_FLAG_NO_FILTERING)
csp->flags |= CSP_FLAG_SERVER_KEEP_ALIVE_TIMEOUT_SET;
}
+ freez(*header);
+
return JB_ERR_OK;
}
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;
}
log_error(LOG_LEVEL_HEADER,
"Client keep-alive timeout is %u. Sticking with %u.",
keep_alive_timeout, csp->config->keep_alive_timeout);
+ freez(*header);
}
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_UNSUPPORTED_CLIENT_EXPECTATION;
+ log_error(LOG_LEVEL_HEADER,
+ "Unsupported client expectaction: %s", *header);
+ }
+
+ return JB_ERR_OK;
+
+}
+
+
/*********************************************************************
*
* Function : crumble
*/
if ((csp->content_type & CT_TEXT) || (csp->action->flags & ACTION_FORCE_TEXT_MODE))
{
+ jb_err err;
freez(*header);
*header = strdup_or_die("Content-Type: ");
- string_append(header, csp->action->string[ACTION_STRING_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;
/*
* Log a warning if the user expects the content to be filtered.
*/
- if ((csp->rlist != NULL) &&
- (!list_is_empty(csp->action->multi[ACTION_MULTI_FILTER])))
+ if (content_filters_enabled(csp->action))
{
log_error(LOG_LEVEL_INFO,
"SDCH-compressed content detected, content filtering disabled. "
/*
* Log a warning if the user expects the content to be filtered.
*/
- if ((csp->rlist != NULL) &&
- (!list_is_empty(csp->action->multi[ACTION_MULTI_FILTER])))
+ if (content_filters_enabled(csp->action))
{
log_error(LOG_LEVEL_INFO,
"Compressed content detected, content filtering disabled. "
}
else if (0 == strcmpic(newval, "randomize"))
{
- const char *header_time = *header + sizeof("Last-Modified:");
-
log_error(LOG_LEVEL_HEADER, "Randomizing: %s", *header);
- 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
{
char *p, *q;
+ if (strlen(*header) < 7)
+ {
+ log_error(LOG_LEVEL_HEADER, "Removing empty Host header");
+ freez(*header);
+ return JB_ERR_OK;
+ }
+
if (!csp->http->hostport || (*csp->http->hostport == '*') ||
*csp->http->hostport == ' ' || *csp->http->hostport == '\0')
{
}
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
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;
}
/*
* 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...)
* 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 : 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);
+
+}
+
/*********************************************************************
*
char *p;
char *host;
+ assert(!http->ssl);
+
host = get_header_value(headers, "Host:");
if (NULL == host)
}
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)
return JB_ERR_MEMORY;
}
- log_error(LOG_LEVEL_HEADER, "Destination extracted from \"Host:\" header. New request URL: %s",
+ log_error(LOG_LEVEL_HEADER,
+ "Destination extracted from \"Host\" header. New request URL: %s",
http->url);
+ /*
+ * Regenerate request line in "proxy format"
+ * to make rewrites more convenient.
+ */
+ 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;
+ }
+
return JB_ERR_OK;
}