+}
+
+
+/*********************************************************************
+ *
+ * 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.
+ */