+ /*
+ * Finally, we can actually update the iob, since the
+ * decompression was successful. First, free the old
+ * buffer.
+ */
+ freez(csp->iob->buf);
+
+ /* Now, update the iob to use the new buffer. */
+ csp->iob->buf = buf;
+ 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.
+ */
+ if ((csp->iob->buf < csp->iob->cur)
+ && (csp->iob->cur <= csp->iob->eod)
+ && (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)
+ {
+ log_error(LOG_LEVEL_RE_FILTER,
+ "Decompression successful. Old size: %d, new size: %d.",
+ old_size, new_size);
+ }
+ else
+ {
+ /* zlib thinks this is OK, so lets do the same. */
+ log_error(LOG_LEVEL_INFO, "Decompression didn't result in any content.");
+ }
+ }
+ else
+ {
+ /* It seems that zlib did something weird. */
+ log_error(LOG_LEVEL_ERROR,
+ "Unexpected error decompressing the buffer (iob): %d==%d, %d>%d, %d<%d",
+ csp->iob->cur, csp->iob->buf + skip_size, csp->iob->eod, csp->iob->buf,
+ csp->iob->eod, csp->iob->buf + csp->iob->size);
+ return JB_ERR_COMPRESS;
+ }
+
+ return JB_ERR_OK;
+
+}
+#endif /* defined(FEATURE_ZLIB) */
+
+
+/*********************************************************************
+ *
+ * Function : string_move
+ *
+ * Description : memmove wrapper to move the last part of a string
+ * towards the beginning, overwriting the part in
+ * the middle. strlcpy() can't be used here as the
+ * strings overlap.
+ *
+ * Parameters :
+ * 1 : dst = Destination to overwrite
+ * 2 : src = Source to move.
+ *
+ * Returns : N/A
+ *
+ *********************************************************************/
+static void string_move(char *dst, char *src)
+{
+ assert(dst < src);
+
+ /* +1 to copy the terminating nul as well. */
+ memmove(dst, src, strlen(src)+1);
+}
+
+
+/*********************************************************************
+ *
+ * Function : normalize_lws
+ *
+ * Description : Reduces unquoted linear white space in headers
+ * to a single space in accordance with RFC 2616 2.2.
+ * 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.
+ *
+ * Returns : N/A
+ *
+ *********************************************************************/
+static void normalize_lws(char *header)
+{
+ char *p = header;
+
+ while (*p != '\0')
+ {
+ if (ijb_isspace(*p) && ijb_isspace(*(p+1)))
+ {
+ char *q = p+1;
+
+ while (ijb_isspace(*q))
+ {
+ q++;
+ }
+ log_error(LOG_LEVEL_HEADER, "Reducing white space 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) && ijb_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 : 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:
+ *
+ * 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.
+ *
+ *********************************************************************/
+static char *get_header_line(struct iob *iob)
+{
+ char *p, *q, *ret;
+
+ if ((iob->cur == NULL)
+ || ((p = strchr(iob->cur, '\n')) == NULL))
+ {
+ return(""); /* couldn't find a complete header */
+ }
+
+ *p = '\0';
+
+ ret = strdup(iob->cur);
+ if (ret == NULL)
+ {
+ /* FIXME No way to handle error properly */
+ log_error(LOG_LEVEL_FATAL, "Out of memory in get_header_line()");
+ }
+
+ iob->cur = p+1;
+
+ if ((q = strchr(ret, '\r')) != NULL) *q = '\0';
+
+ /* is this a blank line (i.e. the end of the header) ? */
+ if (*ret == '\0')
+ {
+ freez(ret);
+ return NULL;
+ }
+
+ return ret;
+
+}
+
+
+/*********************************************************************
+ *
+ * Function : get_header_value
+ *
+ * Description : Get the value of a given header from a chained list
+ * of header lines or return NULL if no such header is
+ * present in the list.
+ *
+ * Parameters :
+ * 1 : header_list = pointer to list
+ * 2 : header_name = string with name of header to look for.
+ * Trailing colon required, capitalization
+ * doesn't matter.
+ *
+ * Returns : NULL if not found, else value of header
+ *
+ *********************************************************************/
+char *get_header_value(const struct list *header_list, const char *header_name)
+{
+ struct list_entry *cur_entry;
+ char *ret = NULL;
+ size_t length = 0;
+
+ assert(header_list);
+ assert(header_name);
+ length = strlen(header_name);
+
+ for (cur_entry = header_list->first; cur_entry ; cur_entry = cur_entry->next)
+ {
+ if (cur_entry->str)
+ {
+ if (!strncmpic(cur_entry->str, header_name, length))
+ {
+ /*
+ * Found: return pointer to start of value
+ */
+ ret = cur_entry->str + length;
+ while (*ret && ijb_isspace(*ret)) ret++;
+ return ret;
+ }
+ }
+ }
+
+ /*
+ * Not found
+ */
+ return NULL;
+
+}
+
+
+/*********************************************************************
+ *
+ * Function : scan_headers
+ *
+ * Description : Scans headers, applies tags and updates action bits.
+ *
+ * Parameters :
+ * 1 : csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns : JB_ERR_OK
+ *
+ *********************************************************************/
+static jb_err scan_headers(struct client_state *csp)
+{
+ struct list_entry *h; /* Header */
+ jb_err err = JB_ERR_OK;
+
+ for (h = csp->headers->first; (err == JB_ERR_OK) && (h != NULL) ; h = h->next)
+ {
+ /* Header crunch()ed in previous run? -> ignore */
+ if (h->str == NULL) continue;
+ log_error(LOG_LEVEL_HEADER, "scan: %s", h->str);
+ err = header_tagger(csp, h->str);
+ }
+
+ return err;
+}
+
+
+/*********************************************************************
+ *
+ * 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 out-of-memory error.
+ *
+ *********************************************************************/
+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;
+
+ if (filter_server_headers)
+ {
+ v = server_patterns;
+ f = add_server_headers;
+ }
+ else
+ {
+ v = client_patterns;
+ f = add_client_headers;
+ }
+
+ scan_headers(csp);
+
+ while ((err == JB_ERR_OK) && (v->str != NULL))
+ {
+ 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) ||
+ (v->len == CHECK_EVERY_HEADER_REMAINING))
+ {
+ err = v->parser(csp, &(p->str));
+ }
+ }
+ v++;
+ }
+
+ /* place additional headers on the csp->headers list */
+ while ((err == JB_ERR_OK) && (*f))
+ {
+ err = (*f)(csp);
+ f++;
+ }
+
+ 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_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));
+ }
+ }
+ }
+ }
+
+ return err;
+}
+
+
+/*********************************************************************
+ *
+ * Function : header_tagger
+ *
+ * Description : Executes all text substitutions from applying
+ * tag actions and saves the result as tag.
+ *
+ * XXX: Shares enough code with filter_header() and
+ * pcrs_filter_response() to warrant some helper functions.
+ *
+ * Parameters :
+ * 1 : csp = Current client state (buffers, headers, etc...)
+ * 2 : header = Header that is used as tagger input
+ *
+ * Returns : JB_ERR_OK on success and always succeeds
+ *
+ *********************************************************************/
+static jb_err header_tagger(struct client_state *csp, char *header)
+{
+ int 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
+ {
+ wanted_filter_type = FT_CLIENT_HEADER_TAGGER;
+ multi_action_index = ACTION_MULTI_CLIENT_HEADER_TAGGER;
+ }
+
+ /* Check if there are any filters */
+ for (i = 0; i < MAX_AF_FILES; i++)
+ {
+ fl = csp->rlist[i];
+ if (NULL != fl)
+ {
+ if (NULL != fl->f)
+ {
+ found_filters = 1;
+ break;
+ }
+ }
+ }
+
+ if (0 == found_filters)
+ {
+ log_error(LOG_LEVEL_ERROR, "Inconsistent configuration: "
+ "tagging enabled, but no taggers available.");
+ return JB_ERR_OK;
+ }
+
+ for (i = 0; i < MAX_AF_FILES; i++)
+ {
+ 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;
+ }
+
+ /* 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);
+ }
+ }
+
+ 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 */
+
+ 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 file_list *fl;
+ struct re_filterfile_spec *b;
+ struct list_entry *filtername;
+
+ int i, found_filters = 0;
+ int 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;
+ }
+
+ /*
+ * Need to check the set of re_filterfiles...
+ */
+ for (i = 0; i < MAX_AF_FILES; i++)
+ {
+ fl = csp->rlist[i];
+ if (NULL != fl)
+ {
+ if (NULL != fl->f)
+ {
+ found_filters = 1;
+ break;
+ }
+ }
+ }
+
+ if (0 == found_filters)
+ {
+ log_error(LOG_LEVEL_ERROR, "Inconsistent configuration: "
+ "header filtering enabled, but no matching filters available.");
+ return JB_ERR_OK;
+ }
+
+ for (i = 0; i < MAX_AF_FILES; i++)
+ {
+ 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;
+ }
+
+ 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 that the value of the Connection: header
+ * is "close" and signals server_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.
+ *
+ *********************************************************************/
+static jb_err server_connection(struct client_state *csp, char **header)
+{
+ char *old_header = *header;
+
+ /* Do we have a 'Connection: close' header? */
+ if (strcmpic(*header, "Connection: close"))
+ {
+#ifdef FEATURE_CONNECTION_KEEP_ALIVE
+ if (!strcmpic(*header, "Connection: keep-alive"))
+ {
+ /* Remember to keep the connection alive. */
+ csp->flags |= CSP_FLAG_SERVER_CONNECTION_KEEP_ALIVE;
+ }
+#endif /* FEATURE_CONNECTION_KEEP_ALIVE */
+
+ *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 server_connection_close_adder() to return early. */
+ csp->flags |= CSP_FLAG_SERVER_CONNECTION_CLOSE_SET;
+
+ return JB_ERR_OK;
+}
+
+/*********************************************************************
+ *
+ * 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, or
+ * JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+static jb_err client_connection(struct client_state *csp, char **header)
+{
+ char *old_header = *header;
+#ifdef FEATURE_CONNECTION_KEEP_ALIVE
+ static const char wanted_header[] = "Connection: keep-alive";
+#else
+ static const char wanted_header[] = "Connection: close";
+#endif /* FEATURE_CONNECTION_KEEP_ALIVE */
+
+ if (strcmpic(*header, wanted_header))
+ {
+ *header = strdup(wanted_header);
+ if (header == NULL)
+ {
+ return JB_ERR_MEMORY;
+ }
+ log_error(LOG_LEVEL_HEADER,
+ "Replaced: \'%s\' with \'%s\'", old_header, *header);
+ freez(old_header);
+ }
+
+ /* Signal client_connection_close_adder() to return early. */
+ csp->flags |= CSP_FLAG_CLIENT_CONNECTION_HEADER_SET;
+
+ return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function : crumble
+ *
+ * Description : This is called if a header matches a pattern to "crunch"
+ *
+ * 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 crumble(struct client_state *csp, char **header)
+{
+ log_error(LOG_LEVEL_HEADER, "crumble crunched: %s!", *header);
+ freez(*header);
+ return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function : crunch_server_header
+ *
+ * Description : Crunch server header if it matches a string supplied by the
+ * user. Called from `sed'.
+ *
+ * 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 crunch_server_header(struct client_state *csp, char **header)
+{
+ const char *crunch_pattern;
+
+ /* 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? */
+ if (strstr(*header, crunch_pattern))
+ {
+ log_error(LOG_LEVEL_HEADER, "Crunching server header: %s (contains: %s)", *header, crunch_pattern);
+ freez(*header);
+ }
+ }
+
+ return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function : server_content_type
+ *
+ * Description : Set the content-type for filterable types (text/.*,
+ * .*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
+ * set CT_TEXT for it.
+ *
+ * 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_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);
+
+ return JB_ERR_OK;
+ }
+
+ /*
+ * Signal that the Content-Type has been set.
+ */
+ csp->content_type |= CT_DECLARED;
+
+ if (!(csp->content_type & CT_TABOO))
+ {
+ /*
+ * 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"))
+ {
+ csp->content_type |= CT_TEXT;
+ }
+ else if (strstr(*header, "image/gif"))
+ {
+ csp->content_type |= CT_GIF;
+ }
+ }
+
+ /*
+ * 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.
+ */
+ if ((csp->content_type & CT_TEXT) || (csp->action->flags & ACTION_FORCE_TEXT_MODE))
+ {
+ freez(*header);
+ *header = strdup("Content-Type: ");
+ string_append(header, csp->action->string[ACTION_STRING_CONTENT_TYPE]);
+
+ if (header == NULL)
+ {
+ log_error(LOG_LEVEL_HEADER, "Insufficient memory to replace Content-Type!");
+ return JB_ERR_MEMORY;
+ }
+ log_error(LOG_LEVEL_HEADER, "Modified: %s!", *header);
+ }
+ else
+ {
+ 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;
+}
+
+
+/*********************************************************************
+ *
+ * Function : server_transfer_coding
+ *
+ * Description : - Prohibit filtering (CT_TABOO) if transfer coding compresses
+ * - Raise the CSP_FLAG_CHUNKED flag if coding is "chunked"
+ * - Remove header if body was chunked but has been
+ * de-chunked for filtering.
+ *
+ * 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_transfer_coding(struct client_state *csp, char **header)
+{
+ /*
+ * Turn off pcrs and gif filtering if body compressed
+ */
+ if (strstr(*header, "gzip") || strstr(*header, "compress") || strstr(*header, "deflate"))
+ {
+#ifdef FEATURE_ZLIB
+ /*
+ * XXX: Added to test if we could use CT_GZIP and CT_DEFLATE here.
+ */
+ log_error(LOG_LEVEL_INFO, "Marking content type for %s as CT_TABOO because of %s.",
+ csp->http->cmd, *header);
+#endif /* def FEATURE_ZLIB */
+ csp->content_type = CT_TABOO;
+ }
+
+ /*
+ * Raise flag if body chunked
+ */
+ if (strstr(*header, "chunked"))
+ {
+ csp->flags |= CSP_FLAG_CHUNKED;
+
+ /*
+ * If the body was modified, it has been de-chunked first
+ * and the header must be removed.
+ *
+ * FIXME: If there is more than one transfer encoding,
+ * only the "chunked" part should be removed here.
+ */
+ if (csp->flags & CSP_FLAG_MODIFIED)
+ {
+ log_error(LOG_LEVEL_HEADER, "Removing: %s", *header);
+ freez(*header);
+ }
+ }
+
+ return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function : server_content_encoding
+ *
+ * Description : This function is run twice for each request,
+ * unless FEATURE_ZLIB and filtering are disabled.
+ *
+ * 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.
+ *
+ * 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_content_encoding(struct client_state *csp, char **header)
+{
+#ifdef FEATURE_ZLIB
+ 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);
+ }
+ else if (strstr(*header, "gzip"))
+ {
+ /* Mark for gzip decompression */
+ csp->content_type |= CT_GZIP;
+ }
+ else if (strstr(*header, "deflate"))
+ {
+ /* Mark for zlib decompression */
+ csp->content_type |= CT_DEFLATE;
+ }
+ else if (strstr(*header, "compress"))
+ {
+ /*
+ * We can't decompress this; therefore we can't filter
+ * it either.
+ */
+ csp->content_type |= CT_TABOO;
+ }
+#else /* !defined(FEATURE_ZLIB) */
+ if (strstr(*header, "gzip") || strstr(*header, "compress") || strstr(*header, "deflate"))
+ {
+ /*
+ * Body is compressed, turn off pcrs and gif filtering.
+ */
+ csp->content_type |= CT_TABOO;
+
+ /*
+ * 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])))
+ {
+ log_error(LOG_LEVEL_INFO,
+ "Compressed content detected, content filtering disabled. "
+ "Consider recompiling Privoxy with zlib support or "
+ "enable the prevent-compression action.");
+ }
+ }
+#endif /* defined(FEATURE_ZLIB) */
+
+ return JB_ERR_OK;
+
+}
+
+
+/*********************************************************************
+ *
+ * Function : server_adjust_content_length
+ *
+ * Description : Adjust Content-Length header if we modified
+ * the body.
+ *
+ * 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_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)
+ {
+ freez(*header);
+ *header = (char *) zalloc(max_header_length);
+ if (*header == NULL)
+ {
+ return JB_ERR_MEMORY;
+ }
+
+ 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);
+ }
+
+ 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 int content_length = 0;
+
+ assert(*(*header+14) == ':');
+
+ if (1 != sscanf(*header+14, ": %u", &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_CONTENT_LENGTH_SET;
+ }
+
+ return JB_ERR_OK;
+}
+#endif /* def FEATURE_CONNECTION_KEEP_ALIVE */
+
+
+/*********************************************************************
+ *
+ * Function : server_content_md5
+ *
+ * Description : Crumble any Content-MD5 headers if the document was
+ * modified. FIXME: Should we re-compute 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 on success, or
+ * JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+static jb_err server_content_md5(struct client_state *csp, char **header)
+{
+ if (csp->flags & CSP_FLAG_MODIFIED)
+ {
+ log_error(LOG_LEVEL_HEADER, "Crunching Content-MD5");
+ freez(*header);
+ }
+
+ return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function : server_content_disposition
+ *
+ * Description : If enabled, blocks or modifies the "Content-Disposition" header.
+ * Called from `sed'.
+ *
+ * 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_content_disposition(struct client_state *csp, char **header)
+{
+ const char *newval;
+
+ /*
+ * Are we messing with the Content-Disposition header?
+ */
+ if ((csp->action->flags & ACTION_HIDE_CONTENT_DISPOSITION) == 0)
+ {
+ /* Me tinks not */
+ return JB_ERR_OK;
+ }
+
+ newval = csp->action->string[ACTION_STRING_CONTENT_DISPOSITION];
+
+ if ((newval == NULL) || (0 == strcmpic(newval, "block")))
+ {
+ /*
+ * Blocking content-disposition header
+ */
+ log_error(LOG_LEVEL_HEADER, "Crunching %s!", *header);
+ freez(*header);
+ return JB_ERR_OK;
+ }
+ else
+ {
+ /*
+ * Replacing Content-Disposition header
+ */
+ freez(*header);
+ *header = strdup("Content-Disposition: ");
+ string_append(header, newval);
+
+ if (*header != NULL)
+ {
+ log_error(LOG_LEVEL_HEADER,
+ "Content-Disposition header crunched and replaced with: %s", *header);
+ }
+ }
+ return (*header == NULL) ? JB_ERR_MEMORY : JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function : server_last_modified
+ *
+ * Description : Changes Last-Modified header to the actual date
+ * to help hide-if-modified-since.
+ * Called from `sed'.
+ *
+ * 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_last_modified(struct client_state *csp, char **header)
+{
+ const char *newval;
+ char buf[BUFFER_SIZE];
+
+ 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?
+ */
+ if ((csp->action->flags & ACTION_OVERWRITE_LAST_MODIFIED) == 0)
+ {
+ /*Nope*/
+ return JB_ERR_OK;
+ }
+
+ newval = csp->action->string[ACTION_STRING_LAST_MODIFIED];
+
+ if (0 == strcmpic(newval, "block") )
+ {
+ /*
+ * Blocking Last-Modified header. Useless but why not.
+ */
+ log_error(LOG_LEVEL_HEADER, "Crunching %s!", *header);
+ freez(*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, sizeof(buf));
+ freez(*header);
+ *header = strdup("Last-Modified: ");
+ string_append(header, buf);
+
+ if (*header == NULL)
+ {
+ log_error(LOG_LEVEL_HEADER, "Insufficient memory. Last-Modified header got lost, boohoo.");
+ }
+ else
+ {
+ log_error(LOG_LEVEL_HEADER, "Reset to present time: %s", *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
+ privoxy_mutex_lock(&gmtime_mutex);
+ timeptr = gmtime(&now);
+ privoxy_mutex_unlock(&gmtime_mutex);
+#else
+ timeptr = gmtime(&now);
+#endif
+ if (JB_ERR_OK != parse_header_time(header_time, &last_modified))
+ {
+ log_error(LOG_LEVEL_HEADER, "Couldn't parse: %s in %s (crunching!)", header_time, *header);
+ freez(*header);
+ }
+ else
+ {
+ rtime = (long int)difftime(now, last_modified);
+ if (rtime)
+ {
+ int negative = 0;
+
+ if (rtime < 0)
+ {
+ rtime *= -1;
+ negative = 1;
+ log_error(LOG_LEVEL_HEADER, "Server time in the future.");
+ }
+ rtime = pick_from_range(rtime);
+ if (negative) rtime *= -1;
+ last_modified += rtime;
+#ifdef HAVE_GMTIME_R
+ timeptr = gmtime_r(&last_modified, &gmt);
+#elif FEATURE_PTHREAD
+ privoxy_mutex_lock(&gmtime_mutex);
+ timeptr = gmtime(&last_modified);
+ privoxy_mutex_unlock(&gmtime_mutex);
+#else
+ timeptr = gmtime(&last_modified);
+#endif
+ strftime(newheader, sizeof(newheader), "%a, %d %b %Y %H:%M:%S GMT", timeptr);
+ freez(*header);
+ *header = strdup("Last-Modified: ");
+ string_append(header, newheader);
+
+ if (*header == NULL)
+ {
+ log_error(LOG_LEVEL_ERROR, "Insufficient memory, header crunched without replacement.");
+ return JB_ERR_MEMORY;
+ }
+
+ 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
+ {
+ log_error(LOG_LEVEL_HEADER, "Randomized ... or not. No time difference to work with.");
+ }
+ }
+ }
+
+ return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function : client_accept_encoding
+ *
+ * Description : Rewrite the client's Accept-Encoding header so that
+ * if doesn't allow compression, if the action applies.
+ * Note: For HTTP/1.0 the absence of the header is enough.
+ *
+ * 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 client_accept_encoding(struct client_state *csp, char **header)
+{
+ if ((csp->action->flags & ACTION_NO_COMPRESSION) != 0)