+ if ((int)size == out->offset)
+ {
+ log_error(LOG_LEVEL_DEANIMATE, "GIF not changed.");
+ }
+ else
+ {
+ log_error(LOG_LEVEL_DEANIMATE,
+ "Success! GIF shrunk from %lu bytes to %lu.", size, out->offset);
+ }
+ csp->content_length = out->offset;
+ csp->flags |= CSP_FLAG_MODIFIED;
+ p = out->buffer;
+ freez(in);
+ freez(out);
+ return(p);
+ }
+
+}
+
+
+/*********************************************************************
+ *
+ * Function : get_filter_function
+ *
+ * Description : Decides which content filter function has
+ * to be applied (if any). Only considers functions
+ * for internal filters which are mutually-exclusive.
+ *
+ * Parameters :
+ * 1 : csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns : The content filter function to run, or
+ * NULL if no content filter is active
+ *
+ *********************************************************************/
+static filter_function_ptr get_filter_function(const struct client_state *csp)
+{
+ filter_function_ptr filter_function = NULL;
+
+ /*
+ * Choose the applying filter function based on
+ * the content type and action settings.
+ */
+ if ((csp->content_type & CT_TEXT) &&
+ (!list_is_empty(csp->action->multi[ACTION_MULTI_FILTER])))
+ {
+ filter_function = pcrs_filter_response_body;
+ }
+ else if ((csp->content_type & CT_GIF) &&
+ (csp->action->flags & ACTION_DEANIMATE))
+ {
+ filter_function = gif_deanimate_response;
+ }
+
+ return filter_function;
+}
+
+
+/*********************************************************************
+ *
+ * Function : get_bytes_to_next_chunk_start
+ *
+ * Description : Returns the number of bytes to the start of the
+ * next chunk in the buffer.
+ *
+ * Parameters :
+ * 1 : buffer = Pointer to the text buffer
+ * 2 : size = Number of bytes in the buffer.
+ * 3 : offset = Where to expect the beginning of the next chunk.
+ *
+ * Returns : -1 if the size can't be determined or data is missing,
+ * otherwise the number of bytes to the start of the next chunk
+ * or 0 if the last chunk has been fully buffered.
+ *
+ *********************************************************************/
+static int get_bytes_to_next_chunk_start(char *buffer, size_t size, size_t offset)
+{
+ char *chunk_start;
+ char *p;
+ unsigned int chunk_size = 0;
+ int bytes_to_skip;
+
+ if (size <= offset || size < 5)
+ {
+ /*
+ * Not enough bytes bufferd to figure
+ * out the size of the next chunk.
+ */
+ return -1;
+ }
+
+ chunk_start = buffer + offset;
+
+ p = strstr(chunk_start, "\r\n");
+ if (NULL == p)
+ {
+ /*
+ * The line with the chunk-size hasn't been completely received
+ * yet (or is invalid).
+ */
+ log_error(LOG_LEVEL_RE_FILTER,
+ "Not enough or invalid data in buffer in chunk size line.");
+ return -1;
+ }
+
+ if (sscanf(chunk_start, "%x", &chunk_size) != 1)
+ {
+ /* XXX: Write test case to trigger this. */
+ log_error(LOG_LEVEL_ERROR, "Failed to parse chunk size. "
+ "Size: %lu, offset: %lu. Chunk size start: %N", size, offset,
+ (size - offset), chunk_start);
+ return -1;
+ }
+
+ /*
+ * To get to the start of the next chunk size we have to skip
+ * the line with the current chunk size followed by "\r\n" followd
+ * by the actual data and another "\r\n" following the data.
+ */
+ bytes_to_skip = (int)(p - chunk_start) + 2 + (int)chunk_size + 2;
+
+ if (bytes_to_skip <= 0)
+ {
+ log_error(LOG_LEVEL_ERROR,
+ "Failed to figure out chunk offset. %u and %d seem dubious.",
+ chunk_size, bytes_to_skip);
+ return -1;
+ }
+ if (chunk_size == 0)
+ {
+ if (bytes_to_skip <= (size - offset))
+ {
+ return 0;
+ }
+ else
+ {
+ log_error(LOG_LEVEL_INFO,
+ "Last chunk detected but we're still missing data.");
+ return -1;
+ }
+ }
+
+ return bytes_to_skip;
+}
+
+
+/*********************************************************************
+ *
+ * Function : get_bytes_missing_from_chunked_data
+ *
+ * Description : Figures out how many bytes of data we need to get
+ * to the start of the next chunk of data (XXX: terminology).
+ * Due to the nature of chunk-encoded data we can only see
+ * how many data is missing according to the last chunk size
+ * buffered.
+ *
+ * Parameters :
+ * 1 : buffer = Pointer to the text buffer
+ * 2 : size = Number of bytes in the buffer.
+ * 3 : offset = Where to expect the beginning of the next chunk.
+ *
+ * Returns : -1 if the data can't be parsed (yet),
+ * 0 if the buffer is complete or a
+ * number of bytes that is missing.
+ *
+ *********************************************************************/
+int get_bytes_missing_from_chunked_data(char *buffer, size_t size, size_t offset)
+{
+ int ret = -1;
+ int last_valid_offset = -1;
+
+ if (size < offset || size < 5)
+ {
+ /* Not enough data buffered yet */
+ return -1;
+ }
+
+ do
+ {
+ ret = get_bytes_to_next_chunk_start(buffer, size, offset);
+ if (ret == -1)
+ {
+ return last_valid_offset;
+ }
+ if (ret == 0)
+ {
+ return 0;
+ }
+ if (offset != 0)
+ {
+ last_valid_offset = (int)offset;
+ }
+ offset += (size_t)ret;
+ } while (offset < size);
+
+ return (int)offset;
+
+}
+
+
+/*********************************************************************
+ *
+ * Function : chunked_data_is_complete
+ *
+ * Description : Detects if a buffer with chunk-encoded data looks
+ * complete.
+ *
+ * Parameters :
+ * 1 : buffer = Pointer to the text buffer
+ * 2 : size = Number of bytes in the buffer.
+ * 3 : offset = Where to expect the beginning of the
+ * first complete chunk.
+ *
+ * Returns : TRUE if it looks like the data is complete,
+ * FALSE otherwise.
+ *
+ *********************************************************************/
+int chunked_data_is_complete(char *buffer, size_t size, size_t offset)
+{
+ return (0 == get_bytes_missing_from_chunked_data(buffer, size, offset));
+
+}
+
+
+/*********************************************************************
+ *
+ * Function : remove_chunked_transfer_coding
+ *
+ * Description : In-situ remove the "chunked" transfer coding as defined
+ * in RFC 7230 4.1 from a buffer. XXX: The implementation
+ * is neither complete nor compliant (TODO #129).
+ *
+ * Parameters :
+ * 1 : buffer = Pointer to the text buffer
+ * 2 : size = In: Number of bytes to be processed,
+ * Out: Number of bytes after de-chunking.
+ * (undefined in case of errors)
+ *
+ * Returns : JB_ERR_OK for success,
+ * JB_ERR_PARSE otherwise
+ *
+ *********************************************************************/
+#ifdef FUZZ
+extern jb_err remove_chunked_transfer_coding(char *buffer, size_t *size)
+#else
+static jb_err remove_chunked_transfer_coding(char *buffer, size_t *size)
+#endif
+{
+ size_t newsize = 0;
+ unsigned int chunksize = 0;
+ char *from_p, *to_p;
+ const char *end_of_buffer = buffer + *size;
+
+ if (*size == 0)
+ {
+ log_error(LOG_LEVEL_FATAL, "Invalid chunked input. Buffer is empty.");
+ return JB_ERR_PARSE;
+ }
+
+ assert(buffer);
+ from_p = to_p = buffer;
+
+#ifndef FUZZ
+ /*
+ * Refuse to de-chunk invalid or incomplete data unless we're fuzzing.
+ */
+ if (!chunked_data_is_complete(buffer, *size, 0))
+ {
+ log_error(LOG_LEVEL_ERROR,
+ "Chunk-encoding appears to be invalid. Content can't be filtered.");
+ return JB_ERR_PARSE;
+ }
+#endif
+
+ if (sscanf(buffer, "%x", &chunksize) != 1)
+ {
+ log_error(LOG_LEVEL_ERROR, "Invalid first chunksize while stripping \"chunked\" transfer coding");
+ return JB_ERR_PARSE;
+ }
+
+ while (chunksize > 0U)
+ {
+ /*
+ * If the chunk-size is valid, we should have at least
+ * chunk-size bytes of chunk-data and five bytes of
+ * meta data (chunk-size, CRLF, CRLF) left in the buffer.
+ */
+ if (chunksize + 5 >= *size - newsize)
+ {
+ log_error(LOG_LEVEL_ERROR,
+ "Chunk size %u exceeds buffered data left. "
+ "Already digested %lu of %lu buffered bytes.",
+ chunksize, newsize, *size);
+ return JB_ERR_PARSE;
+ }
+
+ /*
+ * Skip the chunk-size, the optional chunk-ext and the CRLF
+ * that is supposed to be located directly before the start
+ * of chunk-data.
+ */
+ if (NULL == (from_p = strstr(from_p, "\r\n")))
+ {
+ log_error(LOG_LEVEL_ERROR,
+ "Failed to strip \"chunked\" transfer coding. "
+ "Line with chunk size doesn't seem to end properly.");
+ return JB_ERR_PARSE;
+ }
+ from_p += 2;
+
+ /*
+ * The previous strstr() does not enforce chunk-validity
+ * and is sattisfied as long a CRLF is left in the buffer.
+ *
+ * Make sure the bytes we consider chunk-data are within
+ * the valid range.
+ */
+ if (from_p + chunksize >= end_of_buffer)
+ {
+ log_error(LOG_LEVEL_ERROR,
+ "Failed to decode content for filtering. "
+ "One chunk end is beyond the end of the buffer.");
+ return JB_ERR_PARSE;
+ }
+
+ memmove(to_p, from_p, (size_t) chunksize);
+ newsize += chunksize;
+ to_p = buffer + newsize;
+ from_p += chunksize;
+
+ /*
+ * Not merging this check with the previous one allows us
+ * to keep chunks without trailing CRLF. It's not clear
+ * if we actually have to care about those, though.
+ */
+ if (from_p + 2 >= end_of_buffer)
+ {
+ log_error(LOG_LEVEL_ERROR, "Not enough room for trailing CRLF.");
+ return JB_ERR_PARSE;
+ }
+ from_p += 2;
+ if (sscanf(from_p, "%x", &chunksize) != 1)
+ {
+ log_error(LOG_LEVEL_INFO, "Invalid \"chunked\" transfer encoding detected and ignored.");
+ break;
+ }
+ }
+
+ /* XXX: Should get its own loglevel. */
+ log_error(LOG_LEVEL_RE_FILTER,
+ "De-chunking successful. Shrunk from %lu to %lu", *size, newsize);
+
+ *size = newsize;
+
+ return JB_ERR_OK;
+
+}
+
+
+/*********************************************************************
+ *
+ * Function : prepare_for_filtering
+ *
+ * Description : If necessary, de-chunks and decompresses
+ * the content so it can get filterd.
+ *
+ * Parameters :
+ * 1 : csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns : JB_ERR_OK for success,
+ * JB_ERR_PARSE otherwise
+ *
+ *********************************************************************/
+static jb_err prepare_for_filtering(struct client_state *csp)
+{
+ jb_err err = JB_ERR_OK;
+
+ /*
+ * If the body has a "chunked" transfer-encoding,
+ * get rid of it, adjusting size and iob->eod
+ */
+ if (csp->flags & CSP_FLAG_CHUNKED)
+ {
+ size_t size = (size_t)(csp->iob->eod - csp->iob->cur);
+
+ log_error(LOG_LEVEL_RE_FILTER, "Need to de-chunk first");
+ err = remove_chunked_transfer_coding(csp->iob->cur, &size);
+ if (JB_ERR_OK == err)
+ {
+ csp->iob->eod = csp->iob->cur + size;
+ csp->flags |= CSP_FLAG_MODIFIED;
+ }
+ else
+ {
+ return JB_ERR_PARSE;
+ }
+ }
+
+#ifdef FEATURE_ZLIB
+ /*
+ * If the body has a supported transfer-encoding,
+ * decompress it, adjusting size and iob->eod.
+ */
+ if ((csp->content_type & (CT_GZIP|CT_DEFLATE))
+#ifdef FEATURE_BROTLI
+ || (csp->content_type & CT_BROTLI)
+#endif
+ )
+ {
+ if (0 == csp->iob->eod - csp->iob->cur)
+ {
+ /* Nothing left after de-chunking. */
+ return JB_ERR_OK;
+ }
+
+ err = decompress_iob(csp);
+
+ if (JB_ERR_OK == err)
+ {
+ csp->flags |= CSP_FLAG_MODIFIED;
+ csp->content_type &= ~CT_TABOO;
+ }
+ else
+ {
+ /*
+ * Unset content types to remember not to
+ * modify the Content-Encoding header later.
+ */
+ csp->content_type &= ~CT_GZIP;
+ csp->content_type &= ~CT_DEFLATE;
+#ifdef FEATURE_BROTLI
+ csp->content_type &= ~CT_BROTLI;
+#endif
+ }
+ }
+#endif
+
+ return err;
+}
+
+
+/*********************************************************************
+ *
+ * Function : execute_content_filters
+ *
+ * Description : Executes a given content filter.
+ *
+ * Parameters :
+ * 1 : csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns : Pointer to the modified buffer, or
+ * NULL if filtering failed or wasn't necessary.
+ *
+ *********************************************************************/
+char *execute_content_filters(struct client_state *csp)
+{
+ char *content;
+ filter_function_ptr content_filter;
+
+ assert(content_filters_enabled(csp->action));
+
+ if (0 == csp->iob->eod - csp->iob->cur)
+ {
+ /*
+ * No content (probably status code 301, 302 ...),
+ * no filtering necessary.
+ */
+ return NULL;
+ }
+
+ if (JB_ERR_OK != prepare_for_filtering(csp))
+ {
+ /*
+ * We failed to de-chunk or decompress, don't accept
+ * another request on the client connection.
+ */
+ csp->flags &= ~CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE;
+ return NULL;
+ }
+
+ if (0 == csp->iob->eod - csp->iob->cur)
+ {
+ /*
+ * Clown alarm: chunked and/or compressed nothing delivered.
+ */
+ return NULL;
+ }
+
+ content_filter = get_filter_function(csp);
+ content = (content_filter != NULL) ? (*content_filter)(csp) : NULL;
+
+#ifdef FEATURE_EXTERNAL_FILTERS
+ if ((csp->content_type & CT_TEXT) &&
+ !list_is_empty(csp->action->multi[ACTION_MULTI_EXTERNAL_FILTER]))
+ {
+ struct list_entry *filtername;
+ size_t size = (size_t)csp->content_length;
+
+ if (content == NULL)
+ {
+ content = csp->iob->cur;
+ size = (size_t)(csp->iob->eod - csp->iob->cur);
+ }
+
+ for (filtername = csp->action->multi[ACTION_MULTI_EXTERNAL_FILTER]->first;
+ filtername ; filtername = filtername->next)
+ {
+ char *result = execute_external_filter(csp, filtername->str, content, &size);
+ if (result != NULL)
+ {
+ if (content != csp->iob->cur)
+ {
+ free(content);
+ }
+ content = result;
+ }
+ }
+ csp->flags |= CSP_FLAG_MODIFIED;
+ csp->content_length = size;
+ }
+#endif /* def FEATURE_EXTERNAL_FILTERS */
+
+ return content;
+
+}
+
+
+/*********************************************************************
+ *
+ * Function : execute_client_body_filters
+ *
+ * Description : Executes client body filters for the request that is buffered
+ * in the client_iob. The client_iob is updated with the filtered
+ * content.
+ *
+ * Parameters :
+ * 1 : csp = Current client state (buffers, headers, etc...)
+ * 2 : content_length = content length. Upon successful filtering
+ * the passed value is updated with the new content length.
+ *
+ * Returns : 1 if the content has been filterd. 0 if it hasn't.
+ *
+ *********************************************************************/
+int execute_client_body_filters(struct client_state *csp, size_t *content_length)
+{
+ char *filtered_content;
+
+ assert(client_body_filters_enabled(csp->action));
+
+ if (content_length == 0)
+ {
+ /*
+ * No content, no filtering necessary.
+ */
+ return 0;
+ }
+
+ filtered_content = pcrs_filter_request_body(csp, csp->client_iob->cur, content_length);
+ if (filtered_content != NULL)
+ {
+ freez(csp->client_iob->buf);
+ csp->client_iob->buf = filtered_content;
+ csp->client_iob->cur = csp->client_iob->buf;
+ csp->client_iob->eod = csp->client_iob->cur + *content_length;
+ csp->client_iob->size = *content_length;
+
+ return 1;
+ }
+
+ return 0;
+}
+
+
+/*********************************************************************
+ *
+ * Function : execute_client_body_taggers
+ *
+ * Description : Executes client body taggers for the request that is
+ * buffered in the client_iob.
+ * XXX: Lots of code shared with header_tagger
+ *
+ * Parameters :
+ * 1 : csp = Current client state (buffers, headers, etc...)
+ * 2 : content_length = content length.
+ *
+ * Returns : XXX
+ *
+ *********************************************************************/
+jb_err execute_client_body_taggers(struct client_state *csp, size_t content_length)
+{
+ enum filter_type wanted_filter_type = FT_CLIENT_BODY_TAGGER;
+ int multi_action_index = ACTION_MULTI_CLIENT_BODY_TAGGER;
+ pcrs_job *job;
+
+ struct re_filterfile_spec *b;
+ struct list_entry *tag_name;
+
+ assert(client_body_taggers_enabled(csp->action));
+
+ if (content_length == 0)
+ {
+ /*
+ * No content, no tagging necessary.
+ */
+ return JB_ERR_OK;
+ }
+
+ log_error(LOG_LEVEL_INFO, "Got to execute tagger on %N",
+ content_length, csp->client_iob->cur);
+
+ 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 = csp->client_iob->cur;
+ size_t size = content_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_TAGGING,
+ "Tagger %s has empty joblist. Nothing to do.", b->name);
+ continue;
+ }
+
+ /* execute their pcrs_joblist on the body. */
+ 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 != csp->client_iob->cur)
+ {
+ 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\': %s",
+ b->name, pcrs_strerror(hits));
+ }
+ freez(modified_tag);
+ }
+ }
+
+ if (b->dynamic) pcrs_free_joblist(joblist);
+
+ /* If this tagger matched */
+ if (tag != csp->client_iob->cur)
+ {
+ 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_TAGGING,
+ "Tagger \'%s\' created an empty tag. Ignored.", b->name);
+ continue;
+ }
+
+ if (list_contains_item(csp->action->multi[ACTION_MULTI_SUPPRESS_TAG], tag))
+ {
+ log_error(LOG_LEVEL_TAGGING,
+ "Tagger \'%s\' didn't add tag \'%s\': suppressed",
+ b->name, tag);
+ freez(tag);
+ 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\'",
+ tag, b->name);
+ }
+ 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_TAGGING,
+ "Tagger \'%s\' added tag \'%s\'. %s",
+ b->name, tag, action_message);
+ }
+ }
+ else
+ {
+ /* XXX: Is this log-worthy? */
+ log_error(LOG_LEVEL_TAGGING,
+ "Tagger \'%s\' didn't add tag \'%s\'. Tag already present",
+ b->name, tag);
+ }
+ freez(tag);
+ }
+ }
+
+ return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function : get_url_actions
+ *
+ * Description : Gets the actions for this URL.
+ *
+ * Parameters :
+ * 1 : csp = Current client state (buffers, headers, etc...)
+ * 2 : http = http_request request for blocked URLs
+ *
+ * Returns : N/A
+ *
+ *********************************************************************/
+void get_url_actions(struct client_state *csp, struct http_request *http)
+{
+ struct file_list *fl;
+ struct url_actions *b;
+ int i;
+
+ init_current_action(csp->action);
+
+ for (i = 0; i < MAX_AF_FILES; i++)
+ {
+ if (((fl = csp->actions_list[i]) == NULL) || ((b = fl->f) == NULL))
+ {
+ return;
+ }
+
+#ifdef FEATURE_CLIENT_TAGS
+ apply_url_actions(csp->action, http, csp->client_tags, b);
+#else
+ apply_url_actions(csp->action, http, b);
+#endif
+ }
+
+ return;
+}
+
+/*********************************************************************
+ *
+ * Function : apply_url_actions
+ *
+ * Description : Applies a list of URL actions.
+ *
+ * Parameters :
+ * 1 : action = Destination.
+ * 2 : http = Current URL
+ * 3 : client_tags = list of client tags
+ * 4 : b = list of URL actions to apply
+ *
+ * Returns : N/A
+ *
+ *********************************************************************/
+static void apply_url_actions(struct current_action_spec *action,
+ struct http_request *http,
+#ifdef FEATURE_CLIENT_TAGS
+ const struct list *client_tags,
+#endif
+ struct url_actions *b)
+{
+ if (b == NULL)
+ {
+ /* Should never happen */
+ return;
+ }
+
+ for (b = b->next; NULL != b; b = b->next)
+ {
+ if (url_match(b->url, http))
+ {
+ merge_current_action(action, b->action);
+ }
+#ifdef FEATURE_CLIENT_TAGS
+ if (client_tag_match(b->url, client_tags))
+ {
+ merge_current_action(action, b->action);
+ }
+#endif
+ }
+}
+
+
+/*********************************************************************
+ *
+ * Function : get_forward_override_settings
+ *
+ * Description : Returns forward settings as specified with the
+ * forward-override{} action. forward-override accepts
+ * forward lines similar to the one used in the
+ * configuration file, but without the URL pattern.
+ *
+ * For example:
+ *
+ * forward / .
+ *
+ * in the configuration file can be replaced with
+ * the action section:
+ *
+ * {+forward-override{forward .}}
+ * /
+ *
+ * Parameters :
+ * 1 : csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns : Pointer to forwarding structure in case of success.
+ * Invalid syntax is fatal.
+ *
+ *********************************************************************/
+static const struct forward_spec *get_forward_override_settings(struct client_state *csp)
+{
+ const char *forward_override_line = csp->action->string[ACTION_STRING_FORWARD_OVERRIDE];
+ char forward_settings[BUFFER_SIZE];
+ char *http_parent = NULL;
+ /* variable names were chosen for consistency reasons. */
+ struct forward_spec *fwd = NULL;
+ int vec_count;
+ char *vec[3];
+
+ assert(csp->action->flags & ACTION_FORWARD_OVERRIDE);
+ /* Should be enforced by load_one_actions_file() */
+ assert(strlen(forward_override_line) < sizeof(forward_settings) - 1);
+
+ /* Create a copy ssplit can modify */
+ strlcpy(forward_settings, forward_override_line, sizeof(forward_settings));
+
+ if (NULL != csp->fwd)
+ {
+ /*
+ * XXX: Currently necessary to prevent memory
+ * leaks when the show-url-info cgi page is visited.
+ */
+ unload_forward_spec(csp->fwd);
+ }
+
+ /*
+ * allocate a new forward node, valid only for
+ * the lifetime of this request. Save its location
+ * in csp as well, so sweep() can free it later on.
+ */
+ fwd = csp->fwd = zalloc_or_die(sizeof(*fwd));
+
+ vec_count = ssplit(forward_settings, " \t", vec, SZ(vec));
+ if ((vec_count == 2) && !strcasecmp(vec[0], "forward"))
+ {
+ fwd->type = SOCKS_NONE;
+
+ /* Parse the parent HTTP proxy host:port */
+ http_parent = vec[1];
+
+ }
+ else if ((vec_count == 2) && !strcasecmp(vec[0], "forward-webserver"))
+ {
+ fwd->type = FORWARD_WEBSERVER;
+
+ /* Parse the parent HTTP server host:port */
+ http_parent = vec[1];
+
+ }
+ else if (vec_count == 3)
+ {
+ char *socks_proxy = NULL;
+
+ if (!strcasecmp(vec[0], "forward-socks4"))
+ {
+ fwd->type = SOCKS_4;
+ socks_proxy = vec[1];
+ }
+ else if (!strcasecmp(vec[0], "forward-socks4a"))
+ {
+ fwd->type = SOCKS_4A;
+ socks_proxy = vec[1];
+ }
+ else if (!strcasecmp(vec[0], "forward-socks5"))
+ {
+ fwd->type = SOCKS_5;
+ socks_proxy = vec[1];
+ }
+ else if (!strcasecmp(vec[0], "forward-socks5t"))
+ {
+ fwd->type = SOCKS_5T;
+ socks_proxy = vec[1];
+ }
+
+ if (NULL != socks_proxy)
+ {
+ /* Parse the SOCKS proxy [user:pass@]host[:port] */
+ fwd->gateway_port = 1080;
+ parse_forwarder_address(socks_proxy,
+ &fwd->gateway_host, &fwd->gateway_port,
+ &fwd->auth_username, &fwd->auth_password);
+
+ http_parent = vec[2];
+ }
+ }
+
+ if (NULL == http_parent)
+ {
+ log_error(LOG_LEVEL_FATAL,
+ "Invalid forward-override syntax in: %s", forward_override_line);
+ /* Never get here - LOG_LEVEL_FATAL causes program exit */
+ }
+
+ /* Parse http forwarding settings */
+ if (strcmp(http_parent, ".") != 0)
+ {
+ fwd->forward_port = 8000;
+ parse_forwarder_address(http_parent,
+ &fwd->forward_host, &fwd->forward_port,
+ NULL, NULL);
+ }
+
+ assert (NULL != fwd);
+
+ log_error(LOG_LEVEL_CONNECT,
+ "Overriding forwarding settings based on \'%s\'", forward_override_line);
+
+ return fwd;
+}
+
+
+/*********************************************************************
+ *
+ * Function : forward_url
+ *
+ * Description : Should we forward this to another proxy?
+ *
+ * Parameters :
+ * 1 : csp = Current client state (buffers, headers, etc...)
+ * 2 : http = http_request request for current URL
+ *
+ * Returns : Pointer to forwarding information.
+ *
+ *********************************************************************/
+const struct forward_spec *forward_url(struct client_state *csp,
+ const struct http_request *http)
+{
+ static const struct forward_spec fwd_default[1]; /* Zero'ed due to being static. */
+ struct forward_spec *fwd = csp->config->forward;
+
+ if (csp->action->flags & ACTION_FORWARD_OVERRIDE)
+ {
+ return get_forward_override_settings(csp);
+ }
+
+ if (fwd == NULL)
+ {
+ return fwd_default;
+ }
+
+ while (fwd != NULL)
+ {
+ if (url_match(fwd->url, http))