+ if (0 == zstr.avail_in)
+ {
+ /*
+ * If zlib wants more data then there's a problem, because
+ * the complete compressed file should have been buffered.
+ */
+ log_error(LOG_LEVEL_ERROR,
+ "Unexpected end of compressed iob. Using what we got so far.");
+ break;
+ }
+
+ /*
+ * If we reached the buffer limit and still didn't have enough
+ * memory, just give up. Due to the ceiling enforced by the next
+ * if block we could actually check for equality here, but as it
+ * can be easily mistaken for a bug we don't.
+ */
+ if (bufsize >= csp->config->buffer_limit)
+ {
+ log_error(LOG_LEVEL_ERROR, "Buffer limit reached while decompressing iob");
+ freez(buf);
+ return JB_ERR_MEMORY;
+ }
+
+ /* Try doubling the buffer size each time. */
+ bufsize *= 2;
+
+ /* Don't exceed the buffer limit. */
+ if (bufsize > csp->config->buffer_limit)
+ {
+ bufsize = csp->config->buffer_limit;
+ }
+
+ /* Try to allocate the new buffer. */
+ tmpbuf = realloc(buf, bufsize);
+ if (NULL == tmpbuf)
+ {
+ log_error(LOG_LEVEL_ERROR, "Out of memory decompressing iob");
+ freez(buf);
+ return JB_ERR_MEMORY;
+ }
+ else
+ {
+ char *oldnext_out = (char *)zstr.next_out;
+
+ /*
+ * Update the fields for inflate() to use the new
+ * buffer, which may be in a location different from
+ * the old one.
+ */
+ zstr.avail_out += (uInt)(bufsize - oldbufsize);
+ zstr.next_out = (Bytef *)tmpbuf + bufsize - zstr.avail_out;
+
+ /*
+ * Compare with an uglier method of calculating these values
+ * that doesn't require the extra oldbufsize variable.
+ */
+ assert(zstr.avail_out == tmpbuf + bufsize - (char *)zstr.next_out);
+ assert((char *)zstr.next_out == tmpbuf + ((char *)oldnext_out - buf));
+
+ buf = tmpbuf;
+ }
+ }
+
+ if (Z_STREAM_ERROR == inflateEnd(&zstr))
+ {
+ log_error(LOG_LEVEL_ERROR,
+ "Inconsistent stream state after decompression: %s", zstr.msg);
+ /*
+ * XXX: Intentionally no return.
+ *
+ * According to zlib.h, Z_STREAM_ERROR is returned
+ * "if the stream state was inconsistent".
+ *
+ * I assume in this case inflate()'s status
+ * would also be something different than Z_STREAM_END
+ * so this check should be redundant, but lets see.
+ */
+ }
+
+ if ((status != Z_STREAM_END) && (0 != zstr.avail_in))
+ {
+ /*
+ * We failed to decompress the stream and it's
+ * not simply because of missing data.
+ */
+ log_error(LOG_LEVEL_ERROR,
+ "Unexpected error while decompressing to the buffer (iob): %s",
+ zstr.msg);
+ return JB_ERR_COMPRESS;
+ }
+
+ /*
+ * 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 > (size_t)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 : normalize_lws
+ *
+ * Description : Reduces unquoted linear whitespace in headers to
+ * a single space in accordance with RFC 7230 3.2.4.
+ * This simplifies parsing and filtering later on.
+ *
+ * Parameters :
+ * 1 : header = A header with linear whitespace to reduce.
+ *
+ * Returns : N/A
+ *
+ *********************************************************************/
+static void normalize_lws(char *header)
+{
+ char *p = header;
+
+ while (*p != '\0')
+ {
+ if (privoxy_isspace(*p) && privoxy_isspace(*(p+1)))
+ {
+ char *q = p+1;
+
+ while (privoxy_isspace(*q))
+ {
+ q++;
+ }
+ log_error(LOG_LEVEL_HEADER, "Reducing whitespace in '%s'", header);
+ string_move(p+1, q);
+ }
+
+ if (*p == '\t')
+ {
+ log_error(LOG_LEVEL_HEADER,
+ "Converting tab to space in '%s'", header);
+ *p = ' ';
+ }
+ else if (*p == '"')
+ {
+ char *end_of_token = strstr(p+1, "\"");
+
+ if (NULL != end_of_token)
+ {
+ /* Don't mess with quoted text. */
+ p = end_of_token;
+ }
+ else
+ {
+ log_error(LOG_LEVEL_HEADER,
+ "Ignoring single quote in '%s'", header);
+ }
+ }
+ p++;
+ }
+
+ p = strchr(header, ':');
+ if ((p != NULL) && (p != header) && privoxy_isspace(*(p-1)))
+ {
+ /*
+ * There's still space before the colon.
+ * We don't want it.
+ */
+ string_move(p-1, p);
+ }
+}
+
+
+/*********************************************************************
+ *
+ * Function : get_header
+ *
+ * Description : This (odd) routine will parse the csp->iob
+ * to get the next complete header.
+ *
+ * Parameters :
+ * 1 : 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()");
+ }
+ assert(ret != NULL);
+
+ 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 && privoxy_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 : enforce_header_order
+ *
+ * Description : Enforces a given header order.
+ *
+ * Parameters :
+ * 1 : headers = List of headers to order.
+ * 2 : ordered_headers = List of ordered header names.
+ *
+ * Returns : N/A
+ *
+ *********************************************************************/
+static void enforce_header_order(struct list *headers, const struct list *ordered_headers)
+{
+ struct list_entry *sorted_header;
+ struct list new_headers[1];
+ struct list_entry *header;
+
+ init_list(new_headers);
+
+ /* The request line is always the first "header" */
+
+ assert(NULL != headers->first->str);
+ enlist(new_headers, headers->first->str);
+ freez(headers->first->str)
+
+ /* Enlist the specified headers in the given order */
+
+ for (sorted_header = ordered_headers->first; sorted_header != NULL;
+ sorted_header = sorted_header->next)
+ {
+ const size_t sorted_header_length = strlen(sorted_header->str);
+ for (header = headers->first; header != NULL; header = header->next)
+ {
+ /* Header enlisted in previous run? -> ignore */
+ if (header->str == NULL) continue;
+
+ if (0 == strncmpic(sorted_header->str, header->str, sorted_header_length)
+ && (header->str[sorted_header_length] == ':'))
+ {
+ log_error(LOG_LEVEL_HEADER, "Enlisting sorted header %s", header->str);
+ if (JB_ERR_OK != enlist(new_headers, header->str))
+ {
+ log_error(LOG_LEVEL_HEADER, "Failed to enlist %s", header->str);
+ }
+ freez(header->str);
+ }
+ }
+ }
+
+ /* Enlist the rest of the headers behind the ordered ones */
+ for (header = headers->first; header != NULL; header = header->next)
+ {
+ /* Header enlisted in previous run? -> ignore */
+ if (header->str == NULL) continue;
+
+ log_error(LOG_LEVEL_HEADER,
+ "Enlisting left-over header %s", header->str);
+ if (JB_ERR_OK != enlist(new_headers, header->str))
+ {
+ log_error(LOG_LEVEL_HEADER, "Failed to enlist %s", header->str);
+ }
+ freez(header->str);
+ }
+
+ list_remove_all(headers);
+ list_duplicate(headers, new_headers);
+ list_remove_all(new_headers);
+
+ return;
+}
+
+
+/*********************************************************************
+ *
+ * Function : sed
+ *
+ * Description : add, delete or modify lines in the HTTP header streams.
+ * On entry, it receives a linked list of headers space
+ * that was allocated dynamically (both the list nodes
+ * and the header contents).
+ *
+ * As a side effect it frees the space used by the original
+ * header lines.
+ *
+ * Parameters :
+ * 1 : csp = Current client state (buffers, headers, etc...)
+ * 2 : filter_server_headers = Boolean to switch between
+ * server and header filtering.
+ *
+ * Returns : JB_ERR_OK in case off success, or
+ * JB_ERR_MEMORY on some out-of-memory errors, or
+ * JB_ERR_PARSE in case of fatal parse errors.
+ *
+ *********************************************************************/
+jb_err sed(struct client_state *csp, int filter_server_headers)
+{
+ /* XXX: use more descriptive names. */
+ struct list_entry *p;
+ const struct parsers *v;
+ const add_header_func_ptr *f;
+ jb_err err = JB_ERR_OK;
+
+ scan_headers(csp);
+
+ if (filter_server_headers)
+ {
+ v = server_patterns;
+ f = add_server_headers;
+ check_negative_tag_patterns(csp, PATTERN_SPEC_NO_RESPONSE_TAG_PATTERN);
+ }
+ else
+ {
+ v = client_patterns;
+ f = add_client_headers;
+ check_negative_tag_patterns(csp, PATTERN_SPEC_NO_REQUEST_TAG_PATTERN);
+ }
+
+ while (v->str != NULL)
+ {
+ for (p = csp->headers->first; p != NULL; p = p->next)
+ {
+ /* Header crunch()ed in previous run? -> ignore */
+ if (p->str == NULL) continue;
+
+ /* Does the current parser handle this header? */
+ if ((strncmpic(p->str, v->str, v->len) == 0) ||
+ (v->len == CHECK_EVERY_HEADER_REMAINING))
+ {
+ err = v->parser(csp, &(p->str));
+ if (err != JB_ERR_OK)
+ {
+ return err;
+ }
+ }
+ }
+ v++;
+ }
+
+ /* place additional headers on the csp->headers list */
+ while ((err == JB_ERR_OK) && (*f))
+ {
+ err = (*f)(csp);
+ f++;
+ }
+
+ if (!filter_server_headers && !list_is_empty(csp->config->ordered_client_headers))
+ {
+ enforce_header_order(csp->headers, csp->config->ordered_client_headers);
+ }
+
+ return err;
+}
+
+
+#ifdef FEATURE_HTTPS_INSPECTION
+/*********************************************************************
+ *
+ * Function : sed_https
+ *
+ * Description : add, delete or modify lines in the HTTPS client
+ * header streams. Wrapper around sed().
+ *
+ * Parameters :
+ * 1 : csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns : JB_ERR_OK in case off success, or
+ * JB_ERR_MEMORY on some out-of-memory errors, or
+ * JB_ERR_PARSE in case of fatal parse errors.
+ *
+ *********************************************************************/
+jb_err sed_https(struct client_state *csp)
+{
+ jb_err err;
+ struct list headers;
+
+ /*
+ * Temporarily replace csp->headers with csp->https_headers
+ * to trick sed() into filtering the https headers.
+ */
+ headers.first = csp->headers->first;
+ headers.last = csp->headers->last;
+ csp->headers->first = csp->https_headers->first;
+ csp->headers->last = csp->https_headers->last;
+
+ /*
+ * Start with fresh tags. Already existing tags may
+ * be set again. This is necessary to overrule
+ * URL-based patterns.
+ */
+ destroy_list(csp->tags);
+
+ /*
+ * We want client header filters and taggers
+ * so temporarily remove the flag.
+ */
+ csp->flags &= ~CSP_FLAG_CLIENT_HEADER_PARSING_DONE;
+ err = sed(csp, FILTER_CLIENT_HEADERS);
+ csp->flags |= CSP_FLAG_CLIENT_HEADER_PARSING_DONE;
+
+ /*
+ * Update the last header which may have changed
+ * due to header additions,
+ */
+ csp->https_headers->last = csp->headers->last;
+
+ csp->headers->first = headers.first;
+ csp->headers->last = headers.last;
+
+ return err;
+}
+#endif /* def FEATURE_HTTPS_INSPECTION */
+
+
+/*********************************************************************
+ *
+ * Function : update_server_headers
+ *
+ * Description : Updates server headers after the body has been modified.
+ *
+ * Parameters :
+ * 1 : csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns : JB_ERR_OK in case off success, or
+ * JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+jb_err update_server_headers(struct client_state *csp)
+{
+ jb_err err = JB_ERR_OK;
+
+ static const struct parsers server_patterns_light[] = {
+ { "Content-Length:", 15, server_adjust_content_length },
+ { "Transfer-Encoding:", 18, server_transfer_coding },
+#ifdef FEATURE_ZLIB
+ { "Content-Encoding:", 17, server_adjust_content_encoding },
+#endif /* def FEATURE_ZLIB */
+ { NULL, 0, NULL }
+ };
+
+ if (strncmpic(csp->http->cmd, "HEAD", 4))
+ {
+ const struct parsers *v;
+ struct list_entry *p;
+
+ for (v = server_patterns_light; (err == JB_ERR_OK) && (v->str != NULL); v++)
+ {
+ for (p = csp->headers->first; (err == JB_ERR_OK) && (p != NULL); p = p->next)
+ {
+ /* Header crunch()ed in previous run? -> ignore */
+ if (p->str == NULL) continue;
+
+ /* Does the current parser handle this header? */
+ if (strncmpic(p->str, v->str, v->len) == 0)
+ {
+ err = v->parser(csp, (char **)&(p->str));
+ }
+ }
+ }
+ }
+
+#ifdef FEATURE_CONNECTION_KEEP_ALIVE
+ if ((JB_ERR_OK == err)
+ && (csp->flags & CSP_FLAG_MODIFIED)
+ && (csp->flags & CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE)
+ && !(csp->flags & CSP_FLAG_SERVER_CONTENT_LENGTH_SET))
+ {
+ char header[50];
+
+ create_content_length_header(csp->content_length, header, sizeof(header));
+ err = enlist(csp->headers, header);
+ if (JB_ERR_OK == err)
+ {
+ log_error(LOG_LEVEL_HEADER,
+ "Content modified with no Content-Length header set. "
+ "Created: %s.", header);
+ csp->flags |= CSP_FLAG_SERVER_CONTENT_LENGTH_SET;
+ }
+ }
+#endif /* def FEATURE_CONNECTION_KEEP_ALIVE */
+
+#ifdef FEATURE_COMPRESSION
+ if ((JB_ERR_OK == err)
+ && (csp->flags & CSP_FLAG_BUFFERED_CONTENT_DEFLATED))
+ {
+ err = enlist_unique_header(csp->headers, "Content-Encoding", "deflate");
+ if (JB_ERR_OK == err)
+ {
+ log_error(LOG_LEVEL_HEADER, "Added header: Content-Encoding: deflate");
+ }
+ }
+#endif
+
+ return err;
+}
+
+
+/*********************************************************************
+ *
+ * Function : header_tagger
+ *
+ * Description : Executes all text substitutions from applying
+ * tag actions and saves the result as tag.
+ *
+ * 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)
+{
+ enum filter_type wanted_filter_type;
+ int multi_action_index;
+ pcrs_job *job;
+
+ struct re_filterfile_spec *b;
+ struct list_entry *tag_name;
+
+ 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;
+ }
+
+ if (list_is_empty(csp->action->multi[multi_action_index])
+ || filters_available(csp) == FALSE)
+ {
+ /* Return early if no taggers apply or if none are available. */
+ return JB_ERR_OK;
+ }
+
+ /* Execute all applying taggers */
+ for (tag_name = csp->action->multi[multi_action_index]->first;
+ NULL != tag_name; tag_name = tag_name->next)
+ {
+ char *modified_tag = NULL;
+ char *tag = header;
+ size_t size = header_length;
+ pcrs_job *joblist;
+
+ b = get_filter(csp, tag_name->str, wanted_filter_type);
+ if (b == NULL)
+ {
+ continue;
+ }
+
+ joblist = b->joblist;
+
+ if (b->dynamic) joblist = compile_dynamic_pcrs_job_list(csp, b);
+
+ if (NULL == joblist)
+ {
+ log_error(LOG_LEVEL_RE_FILTER,
+ "Tagger %s has empty joblist. Nothing to do.", b->name);
+ continue;
+ }
+
+ /* execute their pcrs_joblist on the header. */
+ for (job = joblist; NULL != job; job = job->next)
+ {
+ const int hits = pcrs_execute(job, tag, size, &modified_tag, &size);
+
+ if (0 < hits)
+ {
+ /* Success, continue with the modified version. */
+ if (tag != header)
+ {
+ freez(tag);
+ }
+ tag = modified_tag;
+ }
+ else
+ {
+ /* Tagger doesn't match */
+ if (0 > hits)
+ {
+ /* Regex failure, log it but continue anyway. */
+ assert(NULL != header);
+ log_error(LOG_LEVEL_ERROR,
+ "Problems with tagger \'%s\' and header \'%s\': %s",
+ b->name, *header, pcrs_strerror(hits));
+ }
+ freez(modified_tag);
+ }
+ }
+
+ if (b->dynamic) pcrs_free_joblist(joblist);
+
+ /* If this tagger matched */
+ if (tag != header)
+ {
+ if (0 == size)
+ {
+ /*
+ * There is no technical limitation which makes
+ * it impossible to use empty tags, but I assume
+ * no one would do it intentionally.
+ */
+ freez(tag);
+ log_error(LOG_LEVEL_INFO,
+ "Tagger \'%s\' created an empty tag. Ignored.", b->name);
+ continue;
+ }
+
+ if (!list_contains_item(csp->tags, tag))
+ {
+ if (JB_ERR_OK != enlist(csp->tags, tag))
+ {
+ log_error(LOG_LEVEL_ERROR,
+ "Insufficient memory to add tag \'%s\', "
+ "based on tagger \'%s\' and header \'%s\'",
+ tag, b->name, *header);
+ }
+ else
+ {
+ char *action_message;
+ /*
+ * update the action bits right away, to make
+ * tagging based on tags set by earlier taggers
+ * of the same kind possible.
+ */
+ if (update_action_bits_for_tag(csp, tag))
+ {
+ action_message = "Action bits updated accordingly.";
+ }
+ else
+ {
+ action_message = "No action bits update necessary.";
+ }
+
+ log_error(LOG_LEVEL_HEADER,
+ "Tagger \'%s\' added tag \'%s\'. %s",
+ b->name, tag, action_message);
+ }
+ }
+ else
+ {
+ /* XXX: Is this log-worthy? */
+ log_error(LOG_LEVEL_HEADER,
+ "Tagger \'%s\' didn't add tag \'%s\'. Tag already present",
+ b->name, tag);
+ }
+ freez(tag);
+ }
+ }
+
+ return JB_ERR_OK;
+}
+
+/* here begins the family of parser functions that reformat header lines */
+
+/*********************************************************************
+ *
+ * Function : filter_header
+ *
+ * Description : Executes all text substitutions from all applying
+ * +(server|client)-header-filter actions on the header.
+ * Most of the code was copied from pcrs_filter_response,
+ * including the rather short variable names
+ *
+ * Parameters :
+ * 1 : csp = Current client state (buffers, headers, etc...)
+ * 2 : header = On input, pointer to header to modify.
+ * On output, pointer to the modified header, or NULL
+ * to remove the header. This function frees the
+ * original string if necessary.
+ *
+ * Returns : JB_ERR_OK on success and always succeeds
+ *
+ *********************************************************************/
+static jb_err filter_header(struct client_state *csp, char **header)
+{
+ int hits=0;
+ int matches;
+ size_t size = strlen(*header);
+
+ char *newheader = NULL;
+ pcrs_job *job;
+
+ struct re_filterfile_spec *b;
+ struct list_entry *filtername;
+
+ enum filter_type wanted_filter_type;
+ int multi_action_index;
+
+ if (csp->flags & CSP_FLAG_NO_FILTERING)
+ {
+ return JB_ERR_OK;
+ }
+
+ if (csp->flags & CSP_FLAG_CLIENT_HEADER_PARSING_DONE)
+ {
+ wanted_filter_type = FT_SERVER_HEADER_FILTER;
+ multi_action_index = ACTION_MULTI_SERVER_HEADER_FILTER;
+ }
+ else
+ {
+ wanted_filter_type = FT_CLIENT_HEADER_FILTER;
+ multi_action_index = ACTION_MULTI_CLIENT_HEADER_FILTER;
+ }
+
+ if (list_is_empty(csp->action->multi[multi_action_index])
+ || filters_available(csp) == FALSE)
+ {
+ /* Return early if no filters apply or if none are available. */
+ return JB_ERR_OK;
+ }
+
+ /* Execute all applying header filters */
+ for (filtername = csp->action->multi[multi_action_index]->first;
+ filtername != NULL; filtername = filtername->next)
+ {
+ int current_hits = 0;
+ pcrs_job *joblist;
+
+ b = get_filter(csp, filtername->str, wanted_filter_type);
+ if (b == NULL)
+ {
+ continue;
+ }
+
+ joblist = b->joblist;
+
+ if (b->dynamic) joblist = compile_dynamic_pcrs_job_list(csp, b);
+
+ if (NULL == joblist)
+ {
+ log_error(LOG_LEVEL_RE_FILTER, "Filter %s has empty joblist. Nothing to do.", b->name);
+ continue;
+ }
+
+ log_error(LOG_LEVEL_RE_FILTER, "filtering \'%s\' (size %d) with \'%s\' ...",
+ *header, size, b->name);
+
+ /* Apply all jobs from the joblist */
+ for (job = joblist; NULL != job; job = job->next)
+ {
+ matches = pcrs_execute(job, *header, size, &newheader, &size);
+ if (0 < matches)
+ {
+ current_hits += matches;
+ log_error(LOG_LEVEL_HEADER, "Transforming \"%s\" to \"%s\"", *header, newheader);
+ freez(*header);
+ *header = newheader;
+ }
+ else if (0 == matches)
+ {
+ /* Filter doesn't change header */
+ freez(newheader);
+ }
+ else
+ {
+ /* RegEx failure */
+ log_error(LOG_LEVEL_ERROR, "Filtering \'%s\' with \'%s\' didn't work out: %s",
+ *header, b->name, pcrs_strerror(matches));
+ if (newheader != NULL)
+ {
+ log_error(LOG_LEVEL_ERROR, "Freeing what's left: %s", newheader);
+ freez(newheader);
+ }
+ }
+ }
+
+ if (b->dynamic) pcrs_free_joblist(joblist);
+
+ log_error(LOG_LEVEL_RE_FILTER, "... produced %d hits (new size %d).", current_hits, size);
+ hits += current_hits;
+ }
+
+ /*
+ * Additionally checking for hits is important because if
+ * the continue hack is triggered, server headers can
+ * arrive empty to separate multiple heads from each other.
+ */
+ if ((0 == size) && hits)
+ {
+ log_error(LOG_LEVEL_HEADER, "Removing empty header %s", *header);
+ freez(*header);
+ }
+
+ return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function : server_connection
+ *
+ * Description : Makes sure a proper "Connection:" header is
+ * set and signals connection_header_adder to
+ * do nothing.
+ *