summaryrefslogtreecommitdiffstats
path: root/htp/htp_response.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--htp/htp_response.c1436
1 files changed, 1436 insertions, 0 deletions
diff --git a/htp/htp_response.c b/htp/htp_response.c
new file mode 100644
index 0000000..121004c
--- /dev/null
+++ b/htp/htp_response.c
@@ -0,0 +1,1436 @@
+/***************************************************************************
+ * Copyright (c) 2009-2010 Open Information Security Foundation
+ * Copyright (c) 2010-2013 Qualys, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+
+ * - Neither the name of the Qualys, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ ***************************************************************************/
+
+/**
+ * @file
+ * @author Ivan Ristic <ivanr@webkreator.com>
+ */
+
+#include "htp_config_auto.h"
+
+#include "htp_private.h"
+
+#define OUT_TEST_NEXT_BYTE_OR_RETURN(X) \
+if ((X)->out_current_read_offset >= (X)->out_current_len) { \
+ return HTP_DATA; \
+}
+
+#define OUT_PEEK_NEXT(X) \
+if ((X)->out_current_read_offset >= (X)->out_current_len) { \
+ (X)->out_next_byte = -1; \
+} else { \
+ (X)->out_next_byte = (X)->out_current_data[(X)->out_current_read_offset]; \
+}
+
+#define OUT_NEXT_BYTE(X) \
+if ((X)->out_current_read_offset < (X)->out_current_len) { \
+ (X)->out_next_byte = (X)->out_current_data[(X)->out_current_read_offset]; \
+ (X)->out_current_read_offset++; \
+ (X)->out_current_consume_offset++; \
+ (X)->out_stream_offset++; \
+} else { \
+ (X)->out_next_byte = -1; \
+}
+
+#define OUT_NEXT_BYTE_OR_RETURN(X) \
+if ((X)->out_current_read_offset < (X)->out_current_len) { \
+ (X)->out_next_byte = (X)->out_current_data[(X)->out_current_read_offset]; \
+ (X)->out_current_read_offset++; \
+ (X)->out_current_consume_offset++; \
+ (X)->out_stream_offset++; \
+} else { \
+ return HTP_DATA; \
+}
+
+#define OUT_COPY_BYTE_OR_RETURN(X) \
+if ((X)->out_current_read_offset < (X)->out_current_len) { \
+ (X)->out_next_byte = (X)->out_current_data[(X)->out_current_read_offset]; \
+ (X)->out_current_read_offset++; \
+ (X)->out_stream_offset++; \
+} else { \
+ return HTP_DATA_BUFFER; \
+}
+
+#define REQUEST_URI_NOT_SEEN "/libhtp::request_uri_not_seen"
+
+/**
+ * Sends outstanding connection data to the currently active data receiver hook.
+ *
+ * @param[in] connp
+ * @param[in] is_last
+ * @return HTP_OK, or a value returned from a callback.
+ */
+static htp_status_t htp_connp_res_receiver_send_data(htp_connp_t *connp, int is_last) {
+ if (connp->out_data_receiver_hook == NULL) return HTP_OK;
+
+ htp_tx_data_t d;
+ d.tx = connp->out_tx;
+ d.data = connp->out_current_data + connp->out_current_receiver_offset;
+ d.len = connp->out_current_read_offset - connp->out_current_receiver_offset;
+ d.is_last = is_last;
+
+ htp_status_t rc = htp_hook_run_all(connp->out_data_receiver_hook, &d);
+ if (rc != HTP_OK) return rc;
+
+ connp->out_current_receiver_offset = connp->out_current_read_offset;
+
+ return HTP_OK;
+}
+
+/**
+ * Finalizes an existing data receiver hook by sending any outstanding data to it. The
+ * hook is then removed so that it receives no more data.
+ *
+ * @param[in] connp
+ * @return HTP_OK, or a value returned from a callback.
+ */
+htp_status_t htp_connp_res_receiver_finalize_clear(htp_connp_t *connp) {
+ if (connp->out_data_receiver_hook == NULL) return HTP_OK;
+
+ htp_status_t rc = htp_connp_res_receiver_send_data(connp, 1 /* last */);
+
+ connp->out_data_receiver_hook = NULL;
+
+ return rc;
+}
+
+/**
+ * Configures the data receiver hook. If there is a previous hook, it will be finalized and cleared.
+ *
+ * @param[in] connp
+ * @param[in] data_receiver_hook
+ * @return HTP_OK, or a value returned from a callback.
+ */
+static htp_status_t htp_connp_res_receiver_set(htp_connp_t *connp, htp_hook_t *data_receiver_hook) {
+ htp_status_t rc = htp_connp_res_receiver_finalize_clear(connp);
+
+ connp->out_data_receiver_hook = data_receiver_hook;
+ connp->out_current_receiver_offset = connp->out_current_read_offset;
+
+ return rc;
+}
+
+/**
+ * Handles request parser state changes. At the moment, this function is used only
+ * to configure data receivers, which are sent raw connection data.
+ *
+ * @param[in] connp
+ * @return HTP_OK, or a value returned from a callback.
+ */
+static htp_status_t htp_res_handle_state_change(htp_connp_t *connp) {
+ if (connp->out_state_previous == connp->out_state) return HTP_OK;
+
+ if (connp->out_state == htp_connp_RES_HEADERS) {
+ htp_status_t rc = HTP_OK;
+
+ switch (connp->out_tx->response_progress) {
+ case HTP_RESPONSE_HEADERS:
+ rc = htp_connp_res_receiver_set(connp, connp->out_tx->cfg->hook_response_header_data);
+ break;
+
+ case HTP_RESPONSE_TRAILER:
+ rc = htp_connp_res_receiver_set(connp, connp->out_tx->cfg->hook_response_trailer_data);
+ break;
+
+ default:
+ // Do nothing; receivers are currently used only for header blocks.
+ break;
+ }
+
+ if (rc != HTP_OK) return rc;
+ }
+
+ // Same comment as in htp_req_handle_state_change(). Below is a copy.
+
+ // Initially, I had the finalization of raw data sending here, but that
+ // caused the last REQUEST_HEADER_DATA hook to be invoked after the
+ // REQUEST_HEADERS hook -- which I thought made no sense. For that reason,
+ // the finalization is now initiated from the request header processing code,
+ // which is less elegant but provides a better user experience. Having some
+ // (or all) hooks to be invoked on state change might work better.
+
+ connp->out_state_previous = connp->out_state;
+
+ return HTP_OK;
+}
+
+/**
+ * If there is any data left in the outbound data chunk, this function will preserve
+ * it for later consumption. The maximum amount accepted for buffering is controlled
+ * by htp_config_t::field_limit_hard.
+ *
+ * @param[in] connp
+ * @return HTP_OK, or HTP_ERROR on fatal failure.
+ */
+static htp_status_t htp_connp_res_buffer(htp_connp_t *connp) {
+ if (connp->out_current_data == NULL) return HTP_OK;
+
+ unsigned char *data = connp->out_current_data + connp->out_current_consume_offset;
+ size_t len = connp->out_current_read_offset - connp->out_current_consume_offset;
+
+ // Check the hard (buffering) limit.
+
+ size_t newlen = connp->out_buf_size + len;
+
+ // When calculating the size of the buffer, take into account the
+ // space we're using for the response header buffer.
+ if (connp->out_header != NULL) {
+ newlen += bstr_len(connp->out_header);
+ }
+
+ if (newlen > connp->out_tx->cfg->field_limit_hard) {
+ htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "Response the buffer limit: size %zd limit %zd.",
+ newlen, connp->out_tx->cfg->field_limit_hard);
+ return HTP_ERROR;
+ }
+
+ // Copy the data remaining in the buffer.
+
+ if (connp->out_buf == NULL) {
+ connp->out_buf = malloc(len);
+ if (connp->out_buf == NULL) return HTP_ERROR;
+ memcpy(connp->out_buf, data, len);
+ connp->out_buf_size = len;
+ } else {
+ size_t newsize = connp->out_buf_size + len;
+ unsigned char *newbuf = realloc(connp->out_buf, newsize);
+ if (newbuf == NULL) return HTP_ERROR;
+ connp->out_buf = newbuf;
+ memcpy(connp->out_buf + connp->out_buf_size, data, len);
+ connp->out_buf_size = newsize;
+ }
+
+ // Reset the consumer position.
+ connp->out_current_consume_offset = connp->out_current_read_offset;
+
+ return HTP_OK;
+}
+
+/**
+ * Returns to the caller the memory region that should be processed next. This function
+ * hides away the buffering process from the rest of the code, allowing it to work with
+ * non-buffered data that's in the outbound chunk, or buffered data that's in our structures.
+ *
+ * @param[in] connp
+ * @param[out] data
+ * @param[out] len
+ * @return HTP_OK
+ */
+static htp_status_t htp_connp_res_consolidate_data(htp_connp_t *connp, unsigned char **data, size_t *len) {
+ if (connp->out_buf == NULL) {
+ // We do not have any data buffered; point to the current data chunk.
+ *data = connp->out_current_data + connp->out_current_consume_offset;
+ *len = connp->out_current_read_offset - connp->out_current_consume_offset;
+ } else {
+ // We do have data in the buffer. Add data from the current
+ // chunk, and point to the consolidated buffer.
+ if (htp_connp_res_buffer(connp) != HTP_OK) {
+ return HTP_ERROR;
+ }
+
+ *data = connp->out_buf;
+ *len = connp->out_buf_size;
+ }
+
+ return HTP_OK;
+}
+
+/**
+ * Clears buffered outbound data and resets the consumer position to the reader position.
+ *
+ * @param[in] connp
+ */
+static void htp_connp_res_clear_buffer(htp_connp_t *connp) {
+ connp->out_current_consume_offset = connp->out_current_read_offset;
+
+ if (connp->out_buf != NULL) {
+ free(connp->out_buf);
+ connp->out_buf = NULL;
+ connp->out_buf_size = 0;
+ }
+}
+
+/**
+ * Consumes bytes until the end of the current line.
+ *
+ * @param[in] connp
+ * @returns HTP_OK on state change, HTP_ERROR on error, or HTP_DATA when more data is needed.
+ */
+htp_status_t htp_connp_RES_BODY_CHUNKED_DATA_END(htp_connp_t *connp) {
+ // TODO We shouldn't really see anything apart from CR and LF,
+ // so we should warn about anything else.
+
+ for (;;) {
+ OUT_NEXT_BYTE_OR_RETURN(connp);
+
+ connp->out_tx->response_message_len++;
+
+ if (connp->out_next_byte == LF) {
+ connp->out_state = htp_connp_RES_BODY_CHUNKED_LENGTH;
+
+ return HTP_OK;
+ }
+ }
+
+ return HTP_ERROR;
+}
+
+/**
+ * Processes a chunk of data.
+ *
+ * @param[in] connp
+ * @returns HTP_OK on state change, HTP_ERROR on error, or HTP_DATA when more data is needed.
+ */
+htp_status_t htp_connp_RES_BODY_CHUNKED_DATA(htp_connp_t *connp) {
+ size_t bytes_to_consume;
+
+ // Determine how many bytes we can consume.
+ if (connp->out_current_len - connp->out_current_read_offset >= connp->out_chunked_length) {
+ bytes_to_consume = connp->out_chunked_length;
+ } else {
+ bytes_to_consume = connp->out_current_len - connp->out_current_read_offset;
+ }
+
+ if (bytes_to_consume == 0) return HTP_DATA;
+
+ // Consume the data.
+ htp_status_t rc = htp_tx_res_process_body_data_ex(connp->out_tx, connp->out_current_data + connp->out_current_read_offset, bytes_to_consume);
+ if (rc != HTP_OK) return rc;
+
+ // Adjust the counters.
+ connp->out_current_read_offset += bytes_to_consume;
+ connp->out_current_consume_offset += bytes_to_consume;
+ connp->out_stream_offset += bytes_to_consume;
+ connp->out_chunked_length -= bytes_to_consume;
+
+ // Have we seen the entire chunk?
+ if (connp->out_chunked_length == 0) {
+ connp->out_state = htp_connp_RES_BODY_CHUNKED_DATA_END;
+ return HTP_OK;
+ }
+
+ return HTP_DATA;
+}
+
+static inline int is_chunked_ctl_char(const unsigned char c) {
+ switch (c) {
+ case 0x0d:
+ case 0x0a:
+ case 0x20:
+ case 0x09:
+ case 0x0b:
+ case 0x0c:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+/**
+ * Peeks ahead into the data to try to see if it starts with a valid Chunked
+ * length field.
+ *
+ * @returns 1 if it looks valid, 0 if it looks invalid
+ */
+static inline int data_probe_chunk_length(htp_connp_t *connp) {
+ if (connp->out_current_read_offset - connp->out_current_consume_offset < 8) {
+ // not enough data so far, consider valid still
+ return 1;
+ }
+
+ unsigned char *data = connp->out_current_data + connp->out_current_consume_offset;
+ size_t len = connp->out_current_read_offset - connp->out_current_consume_offset;
+
+ size_t i = 0;
+ while (i < len) {
+ unsigned char c = data[i];
+
+ if (is_chunked_ctl_char(c)) {
+ // ctl char, still good.
+ } else if (isdigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) {
+ // real chunklen char
+ return 1;
+ } else {
+ // leading junk, bad
+ return 0;
+ }
+ i++;
+ }
+ return 1;
+}
+
+/**
+ * Extracts chunk length.
+ *
+ * @param[in] connp
+ * @returns HTP_OK on state change, HTP_ERROR on error, or HTP_DATA when more data is needed.
+ */
+htp_status_t htp_connp_RES_BODY_CHUNKED_LENGTH(htp_connp_t *connp) {
+ for (;;) {
+ OUT_COPY_BYTE_OR_RETURN(connp);
+
+ // Have we reached the end of the line? Or is this not chunked after all?
+ if (connp->out_next_byte == LF ||
+ (!is_chunked_ctl_char((unsigned char) connp->out_next_byte) && !data_probe_chunk_length(connp))) {
+ unsigned char *data;
+ size_t len;
+
+ if (htp_connp_res_consolidate_data(connp, &data, &len) != HTP_OK) {
+ return HTP_ERROR;
+ }
+
+ connp->out_tx->response_message_len += len;
+
+ #ifdef HTP_DEBUG
+ fprint_raw_data(stderr, "Chunk length line", data, len);
+ #endif
+
+ int chunk_ext = 0;
+ connp->out_chunked_length = htp_parse_chunked_length(data, len, &chunk_ext);
+ if (chunk_ext == 1) {
+ htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Request chunk extension");
+ }
+ // empty chunk length line, lets try to continue
+ if (connp->out_chunked_length == -1004) {
+ connp->out_current_consume_offset = connp->out_current_read_offset;
+ continue;
+ }
+ if (connp->out_chunked_length < 0) {
+ // reset out_current_read_offset so htp_connp_RES_BODY_IDENTITY_STREAM_CLOSE
+ // doesn't miss the first bytes
+
+ if (len > (size_t)connp->out_current_read_offset) {
+ connp->out_current_read_offset = 0;
+ } else {
+ connp->out_current_read_offset -= len;
+ }
+
+ connp->out_state = htp_connp_RES_BODY_IDENTITY_STREAM_CLOSE;
+ connp->out_tx->response_transfer_coding = HTP_CODING_IDENTITY;
+
+ htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
+ "Response chunk encoding: Invalid chunk length: %"PRId64"",
+ connp->out_chunked_length);
+ return HTP_OK;
+ }
+ htp_connp_res_clear_buffer(connp);
+
+ // Handle chunk length
+ if (connp->out_chunked_length > 0) {
+ // More data available
+ connp->out_state = htp_connp_RES_BODY_CHUNKED_DATA;
+ } else if (connp->out_chunked_length == 0) {
+ // End of data
+ connp->out_state = htp_connp_RES_HEADERS;
+ connp->out_tx->response_progress = HTP_RESPONSE_TRAILER;
+ }
+
+ return HTP_OK;
+ }
+ }
+
+ return HTP_ERROR;
+}
+
+/**
+ * Processes an identity response body of known length.
+ *
+ * @param[in] connp
+ * @returns HTP_OK on state change, HTP_ERROR on error, or HTP_DATA when more data is needed.
+ */
+htp_status_t htp_connp_RES_BODY_IDENTITY_CL_KNOWN(htp_connp_t *connp) {
+ size_t bytes_to_consume;
+
+ // Determine how many bytes we can consume.
+ if (connp->out_current_len - connp->out_current_read_offset >= connp->out_body_data_left) {
+ bytes_to_consume = connp->out_body_data_left;
+ } else {
+ bytes_to_consume = connp->out_current_len - connp->out_current_read_offset;
+ }
+
+ if (connp->out_status == HTP_STREAM_CLOSED) {
+ connp->out_state = htp_connp_RES_FINALIZE;
+ // Sends close signal to decompressors
+ htp_status_t rc = htp_tx_res_process_body_data_ex(connp->out_tx, NULL, 0);
+ return rc;
+ }
+ if (bytes_to_consume == 0) return HTP_DATA;
+
+ // Consume the data.
+ htp_status_t rc = htp_tx_res_process_body_data_ex(connp->out_tx, connp->out_current_data + connp->out_current_read_offset, bytes_to_consume);
+ if (rc != HTP_OK) return rc;
+
+ // Adjust the counters.
+ connp->out_current_read_offset += bytes_to_consume;
+ connp->out_current_consume_offset += bytes_to_consume;
+ connp->out_stream_offset += bytes_to_consume;
+ connp->out_body_data_left -= bytes_to_consume;
+
+ // Have we seen the entire response body?
+ if (connp->out_body_data_left == 0) {
+ connp->out_state = htp_connp_RES_FINALIZE;
+ // Tells decompressors to output partially decompressed data
+ rc = htp_tx_res_process_body_data_ex(connp->out_tx, NULL, 0);
+ return rc;
+ }
+
+ return HTP_DATA;
+}
+
+/**
+ * Processes identity response body of unknown length. In this case, we assume the
+ * response body consumes all data until the end of the stream.
+ *
+ * @param[in] connp
+ * @returns HTP_OK on state change, HTP_ERROR on error, or HTP_DATA when more data is needed.
+ */
+htp_status_t htp_connp_RES_BODY_IDENTITY_STREAM_CLOSE(htp_connp_t *connp) {
+ // Consume all data from the input buffer.
+ size_t bytes_to_consume = connp->out_current_len - connp->out_current_read_offset;
+
+ #ifdef HTP_DEBUG
+ fprintf(stderr, "bytes_to_consume %"PRIuMAX, (uintmax_t)bytes_to_consume);
+ #endif
+ if (bytes_to_consume != 0) {
+ htp_status_t rc = htp_tx_res_process_body_data_ex(connp->out_tx, connp->out_current_data + connp->out_current_read_offset, bytes_to_consume);
+ if (rc != HTP_OK) return rc;
+
+ // Adjust the counters.
+ connp->out_current_read_offset += bytes_to_consume;
+ connp->out_current_consume_offset += bytes_to_consume;
+ connp->out_stream_offset += bytes_to_consume;
+ }
+
+ // Have we seen the entire response body?
+ if (connp->out_status == HTP_STREAM_CLOSED) {
+ connp->out_state = htp_connp_RES_FINALIZE;
+ return HTP_OK;
+ }
+
+ return HTP_DATA;
+}
+
+/**
+ * Determines presence (and encoding) of a response body.
+ *
+ * @param[in] connp
+ * @returns HTP_OK on state change, HTP_ERROR on error, or HTP_DATA when more data is needed.
+ */
+htp_status_t htp_connp_RES_BODY_DETERMINE(htp_connp_t *connp) {
+ // If the request uses the CONNECT method, then not only are we
+ // to assume there's no body, but we need to ignore all
+ // subsequent data in the stream.
+ if (connp->out_tx->request_method_number == HTP_M_CONNECT) {
+ if ((connp->out_tx->response_status_number >= 200)
+ && (connp->out_tx->response_status_number <= 299)) {
+ // This is a successful CONNECT stream, which means
+ // we need to switch into tunneling mode: on the
+ // request side we'll now probe the tunnel data to see
+ // if we need to parse or ignore it. So on the response
+ // side we wrap up the tx and wait.
+ connp->out_state = htp_connp_RES_FINALIZE;
+
+ // we may have response headers
+ htp_status_t rc = htp_tx_state_response_headers(connp->out_tx);
+ return rc;
+ } else if (connp->out_tx->response_status_number == 407) {
+ // proxy telling us to auth
+ if (connp->in_status != HTP_STREAM_ERROR)
+ connp->in_status = HTP_STREAM_DATA;
+ } else {
+ // This is a failed CONNECT stream, which means that
+ // we can unblock request parsing
+ if (connp->in_status != HTP_STREAM_ERROR)
+ connp->in_status = HTP_STREAM_DATA;
+
+ // We are going to continue processing this transaction,
+ // adding a note for ourselves to stop at the end (because
+ // we don't want to see the beginning of a new transaction).
+ connp->out_data_other_at_tx_end = 1;
+ }
+ }
+
+ htp_header_t *cl = htp_table_get_c(connp->out_tx->response_headers, "content-length");
+ htp_header_t *te = htp_table_get_c(connp->out_tx->response_headers, "transfer-encoding");
+
+ // Check for "101 Switching Protocol" response.
+ // If it's seen, it means that traffic after empty line following headers
+ // is no longer HTTP. We can treat it similarly to CONNECT.
+ // Unlike CONNECT, however, upgrades from HTTP to HTTP seem
+ // rather unlikely, so don't try to probe tunnel for nested HTTP,
+ // and switch to tunnel mode right away.
+ if (connp->out_tx->response_status_number == 101) {
+ if (te == NULL && cl == NULL) {
+ connp->out_state = htp_connp_RES_FINALIZE;
+
+ if (connp->in_status != HTP_STREAM_ERROR)
+ connp->in_status = HTP_STREAM_TUNNEL;
+ connp->out_status = HTP_STREAM_TUNNEL;
+
+ // we may have response headers
+ htp_status_t rc = htp_tx_state_response_headers(connp->out_tx);
+ return rc;
+ } else {
+ htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Switching Protocol with Content-Length");
+ }
+ }
+
+ // Check for an interim "100 Continue" response. Ignore it if found, and revert back to RES_LINE.
+ if (connp->out_tx->response_status_number == 100 && te == NULL) {
+ int is100continue = 1;
+ if (cl != NULL){
+ if (htp_parse_content_length(cl->value, connp) > 0) {
+ is100continue = 0;
+ }
+ }
+ if (is100continue) {
+ if (connp->out_tx->seen_100continue != 0) {
+ htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "Already seen 100-Continue.");
+ }
+
+ // Ignore any response headers seen so far.
+ htp_header_t *h = NULL;
+ for (size_t i = 0, n = htp_table_size(connp->out_tx->response_headers); i < n; i++) {
+ h = htp_table_get_index(connp->out_tx->response_headers, i, NULL);
+ bstr_free(h->name);
+ bstr_free(h->value);
+ free(h);
+ }
+
+ htp_table_clear(connp->out_tx->response_headers);
+
+ // Expecting to see another response line next.
+ connp->out_state = htp_connp_RES_LINE;
+ connp->out_tx->response_progress = HTP_RESPONSE_LINE;
+ connp->out_tx->seen_100continue++;
+
+ return HTP_OK;
+ }
+ }
+
+ // A request can indicate it waits for headers validation
+ // before sending its body cf
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Expect
+ if (connp->out_tx->response_status_number >= 400 &&
+ connp->out_tx->response_status_number <= 499 &&
+ connp->in_content_length > 0 &&
+ connp->in_body_data_left == connp->in_content_length) {
+ htp_header_t *exp = htp_table_get_c(connp->out_tx->request_headers, "expect");
+ if ((exp != NULL) && (bstr_cmp_c_nocase(exp->value, "100-continue") == 0)) {
+ connp->in_state = htp_connp_REQ_FINALIZE;
+ }
+ }
+
+ // 1. Any response message which MUST NOT include a message-body
+ // (such as the 1xx, 204, and 304 responses and any response to a HEAD
+ // request) is always terminated by the first empty line after the
+ // header fields, regardless of the entity-header fields present in the
+ // message.
+ if (connp->out_tx->request_method_number == HTP_M_HEAD) {
+ // There's no response body whatsoever
+ connp->out_tx->response_transfer_coding = HTP_CODING_NO_BODY;
+ connp->out_state = htp_connp_RES_FINALIZE;
+ }
+ else if (((connp->out_tx->response_status_number >= 100) && (connp->out_tx->response_status_number <= 199))
+ || (connp->out_tx->response_status_number == 204) || (connp->out_tx->response_status_number == 304)) {
+ // There should be no response body
+ // but browsers interpret content sent by the server as such
+ if (te == NULL && cl == NULL) {
+ connp->out_tx->response_transfer_coding = HTP_CODING_NO_BODY;
+ connp->out_state = htp_connp_RES_FINALIZE;
+ } else {
+ htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Unexpected Response body");
+ }
+ }
+ // Hack condition to check that we do not assume "no body"
+ if (connp->out_state != htp_connp_RES_FINALIZE) {
+ // We have a response body
+ htp_header_t *ct = htp_table_get_c(connp->out_tx->response_headers, "content-type");
+ if (ct != NULL) {
+ connp->out_tx->response_content_type = bstr_dup_lower(ct->value);
+ if (connp->out_tx->response_content_type == NULL) return HTP_ERROR;
+
+ // Ignore parameters
+ unsigned char *data = bstr_ptr(connp->out_tx->response_content_type);
+ size_t len = bstr_len(ct->value);
+ size_t newlen = 0;
+ while (newlen < len) {
+ // TODO Some platforms may do things differently here.
+ if (htp_is_space(data[newlen]) || (data[newlen] == ';')) {
+ bstr_adjust_len(connp->out_tx->response_content_type, newlen);
+ break;
+ }
+
+ newlen++;
+ }
+ }
+
+ // 2. If a Transfer-Encoding header field (section 14.40) is present and
+ // indicates that the "chunked" transfer coding has been applied, then
+ // the length is defined by the chunked encoding (section 3.6).
+ if ((te != NULL) && (bstr_index_of_c_nocasenorzero(te->value, "chunked") != -1)) {
+ if (bstr_cmp_c_nocase(te->value, "chunked") != 0) {
+ htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0,
+ "Transfer-encoding has abnormal chunked value");
+ }
+
+ // spec says chunked is HTTP/1.1 only, but some browsers accept it
+ // with 1.0 as well
+ if (connp->out_tx->response_protocol_number < HTP_PROTOCOL_1_1) {
+ htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0,
+ "Chunked transfer-encoding on HTTP/0.9 or HTTP/1.0");
+ }
+
+ // If the T-E header is present we are going to use it.
+ connp->out_tx->response_transfer_coding = HTP_CODING_CHUNKED;
+
+ // We are still going to check for the presence of C-L
+ if (cl != NULL) {
+ // This is a violation of the RFC
+ connp->out_tx->flags |= HTP_REQUEST_SMUGGLING;
+ }
+
+ connp->out_state = htp_connp_RES_BODY_CHUNKED_LENGTH;
+ connp->out_tx->response_progress = HTP_RESPONSE_BODY;
+ }// 3. If a Content-Length header field (section 14.14) is present, its
+ // value in bytes represents the length of the message-body.
+ else if (cl != NULL) {
+ // We know the exact length
+ connp->out_tx->response_transfer_coding = HTP_CODING_IDENTITY;
+
+ // Check for multiple C-L headers
+ if (cl->flags & HTP_FIELD_REPEATED) {
+ connp->out_tx->flags |= HTP_REQUEST_SMUGGLING;
+ }
+
+ // Get body length
+ connp->out_tx->response_content_length = htp_parse_content_length(cl->value, connp);
+ if (connp->out_tx->response_content_length < 0) {
+ htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "Invalid C-L field in response: %"PRId64"",
+ connp->out_tx->response_content_length);
+ return HTP_ERROR;
+ } else {
+ connp->out_content_length = connp->out_tx->response_content_length;
+ connp->out_body_data_left = connp->out_content_length;
+
+ if (connp->out_content_length != 0) {
+ connp->out_state = htp_connp_RES_BODY_IDENTITY_CL_KNOWN;
+ connp->out_tx->response_progress = HTP_RESPONSE_BODY;
+ } else {
+ connp->out_state = htp_connp_RES_FINALIZE;
+ }
+ }
+ } else {
+ // 4. If the message uses the media type "multipart/byteranges", which is
+ // self-delimiting, then that defines the length. This media type MUST
+ // NOT be used unless the sender knows that the recipient can parse it;
+ // the presence in a request of a Range header with multiple byte-range
+ // specifiers implies that the client can parse multipart/byteranges
+ // responses.
+ if (ct != NULL) {
+ // TODO Handle multipart/byteranges
+ if (bstr_index_of_c_nocase(ct->value, "multipart/byteranges") != -1) {
+ htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
+ "C-T multipart/byteranges in responses not supported");
+ return HTP_ERROR;
+ }
+ }
+
+ // 5. By the server closing the connection. (Closing the connection
+ // cannot be used to indicate the end of a request body, since that
+ // would leave no possibility for the server to send back a response.)
+ connp->out_state = htp_connp_RES_BODY_IDENTITY_STREAM_CLOSE;
+ connp->out_tx->response_transfer_coding = HTP_CODING_IDENTITY;
+ connp->out_tx->response_progress = HTP_RESPONSE_BODY;
+ connp->out_body_data_left = -1;
+ }
+ }
+
+ // NOTE We do not need to check for short-style HTTP/0.9 requests here because
+ // that is done earlier, before response line parsing begins
+
+ htp_status_t rc = htp_tx_state_response_headers(connp->out_tx);
+ if (rc != HTP_OK) return rc;
+
+ return HTP_OK;
+}
+
+/**
+ * Parses response headers.
+ *
+ * @param[in] connp
+ * @returns HTP_OK on state change, HTP_ERROR on error, or HTP_DATA when more data is needed.
+ */
+htp_status_t htp_connp_RES_HEADERS(htp_connp_t *connp) {
+ int endwithcr;
+ int lfcrending = 0;
+
+ for (;;) {
+ if (connp->out_status == HTP_STREAM_CLOSED) {
+ // Finalize sending raw trailer data.
+ htp_status_t rc = htp_connp_res_receiver_finalize_clear(connp);
+ if (rc != HTP_OK) return rc;
+
+ // Run hook response_TRAILER.
+ rc = htp_hook_run_all(connp->cfg->hook_response_trailer, connp->out_tx);
+ if (rc != HTP_OK) return rc;
+
+ connp->out_state = htp_connp_RES_FINALIZE;
+ return HTP_OK;
+ }
+ OUT_COPY_BYTE_OR_RETURN(connp);
+
+ // Have we reached the end of the line?
+ if (connp->out_next_byte != LF && connp->out_next_byte != CR) {
+ lfcrending = 0;
+ } else {
+ endwithcr = 0;
+ if (connp->out_next_byte == CR) {
+ OUT_PEEK_NEXT(connp);
+ if (connp->out_next_byte == -1) {
+ return HTP_DATA_BUFFER;
+ } else if (connp->out_next_byte == LF) {
+ OUT_COPY_BYTE_OR_RETURN(connp);
+ if (lfcrending) {
+ // Handling LFCRCRLFCRLF
+ // These 6 characters mean only 2 end of lines
+ OUT_PEEK_NEXT(connp);
+ if (connp->out_next_byte == CR) {
+ OUT_COPY_BYTE_OR_RETURN(connp);
+ connp->out_current_consume_offset++;
+ OUT_PEEK_NEXT(connp);
+ if (connp->out_next_byte == LF) {
+ OUT_COPY_BYTE_OR_RETURN(connp);
+ connp->out_current_consume_offset++;
+ htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0,
+ "Weird response end of lines mix");
+ }
+ }
+ }
+ } else if (connp->out_next_byte == CR) {
+ continue;
+ }
+ lfcrending = 0;
+ endwithcr = 1;
+ } else {
+ // connp->out_next_byte == LF
+ OUT_PEEK_NEXT(connp);
+ lfcrending = 0;
+ if (connp->out_next_byte == CR) {
+ // hanldes LF-CR sequence as end of line
+ OUT_COPY_BYTE_OR_RETURN(connp);
+ lfcrending = 1;
+ }
+ }
+
+ unsigned char *data;
+ size_t len;
+
+ if (htp_connp_res_consolidate_data(connp, &data, &len) != HTP_OK) {
+ return HTP_ERROR;
+ }
+
+ // CRCRLF is not an empty line
+ if (endwithcr && len < 2) {
+ continue;
+ }
+
+ #ifdef HTP_DEBUG
+ fprint_raw_data(stderr, __func__, data, len);
+ #endif
+
+ int next_no_lf = 0;
+ if (connp->out_current_read_offset < connp->out_current_len &&
+ connp->out_current_data[connp->out_current_read_offset] != LF) {
+ next_no_lf = 1;
+ }
+ // Should we terminate headers?
+ if (htp_connp_is_line_terminator(connp, data, len, next_no_lf)) {
+ // Parse previous header, if any.
+ if (connp->out_header != NULL) {
+ if (connp->cfg->process_response_header(connp, bstr_ptr(connp->out_header),
+ bstr_len(connp->out_header)) != HTP_OK) return HTP_ERROR;
+
+ bstr_free(connp->out_header);
+ connp->out_header = NULL;
+ }
+
+ htp_connp_res_clear_buffer(connp);
+
+ // We've seen all response headers.
+ if (connp->out_tx->response_progress == HTP_RESPONSE_HEADERS) {
+ // Response headers.
+
+ // The next step is to determine if this response has a body.
+ connp->out_state = htp_connp_RES_BODY_DETERMINE;
+ } else {
+ // Response trailer.
+
+ // Finalize sending raw trailer data.
+ htp_status_t rc = htp_connp_res_receiver_finalize_clear(connp);
+ if (rc != HTP_OK) return rc;
+
+ // Run hook response_TRAILER.
+ rc = htp_hook_run_all(connp->cfg->hook_response_trailer, connp->out_tx);
+ if (rc != HTP_OK) return rc;
+
+ // The next step is to finalize this response.
+ connp->out_state = htp_connp_RES_FINALIZE;
+ }
+
+ return HTP_OK;
+ }
+
+ htp_chomp(data, &len);
+
+ // Check for header folding.
+ if (htp_connp_is_line_folded(data, len) == 0) {
+ // New header line.
+
+ // Parse previous header, if any.
+ if (connp->out_header != NULL) {
+ if (connp->cfg->process_response_header(connp, bstr_ptr(connp->out_header),
+ bstr_len(connp->out_header)) != HTP_OK) return HTP_ERROR;
+
+ bstr_free(connp->out_header);
+ connp->out_header = NULL;
+ }
+
+ OUT_PEEK_NEXT(connp);
+
+ if (htp_is_folding_char(connp->out_next_byte) == 0) {
+ // Because we know this header is not folded, we can process the buffer straight away.
+ if (connp->cfg->process_response_header(connp, data, len) != HTP_OK) return HTP_ERROR;
+ } else {
+ // Keep the partial header data for parsing later.
+ connp->out_header = bstr_dup_mem(data, len);
+ if (connp->out_header == NULL) return HTP_ERROR;
+ }
+ } else {
+ // Folding; check that there's a previous header line to add to.
+ if (connp->out_header == NULL) {
+ // Invalid folding.
+
+ // Warn only once per transaction.
+ if (!(connp->out_tx->flags & HTP_INVALID_FOLDING)) {
+ connp->out_tx->flags |= HTP_INVALID_FOLDING;
+ htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Invalid response field folding");
+ }
+
+ // Keep the header data for parsing later.
+ size_t trim = 0;
+ while(trim < len) {
+ if (!htp_is_folding_char(data[trim])) {
+ break;
+ }
+ trim++;
+ }
+ connp->out_header = bstr_dup_mem(data + trim, len - trim);
+ if (connp->out_header == NULL) return HTP_ERROR;
+ } else {
+ size_t colon_pos = 0;
+ while ((colon_pos < len) && (data[colon_pos] != ':')) colon_pos++;
+
+ if (colon_pos < len &&
+ bstr_chr(connp->out_header, ':') >= 0 &&
+ connp->out_tx->response_protocol_number == HTP_PROTOCOL_1_1) {
+ // Warn only once per transaction.
+ if (!(connp->out_tx->flags & HTP_INVALID_FOLDING)) {
+ connp->out_tx->flags |= HTP_INVALID_FOLDING;
+ htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Invalid response field folding");
+ }
+ if (connp->cfg->process_response_header(connp, bstr_ptr(connp->out_header),
+ bstr_len(connp->out_header)) != HTP_OK)
+ return HTP_ERROR;
+ bstr_free(connp->out_header);
+ connp->out_header = bstr_dup_mem(data+1, len-1);
+ if (connp->out_header == NULL)
+ return HTP_ERROR;
+ } else {
+ // Add to the existing header.
+ if (bstr_len(connp->out_header) < HTP_MAX_HEADER_FOLDED) {
+ bstr *new_out_header = bstr_add_mem(connp->out_header, data, len);
+ if (new_out_header == NULL)
+ return HTP_ERROR;
+ connp->out_header = new_out_header;
+ } else {
+ htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Response field length exceeds folded maximum");
+ }
+ }
+ }
+ }
+
+ htp_connp_res_clear_buffer(connp);
+ }
+ }
+
+ return HTP_ERROR;
+}
+
+/**
+ * Parses response line.
+ *
+ * @param[in] connp
+ * @returns HTP_OK on state change, HTP_ERROR on error, or HTP_DATA when more data is needed.
+ */
+htp_status_t htp_connp_RES_LINE(htp_connp_t *connp) {
+ for (;;) {
+ // Don't try to get more data if the stream is closed. If we do, we'll return, asking for more data.
+ if (connp->out_status != HTP_STREAM_CLOSED) {
+ // Get one byte
+ OUT_COPY_BYTE_OR_RETURN(connp);
+ }
+
+ // Have we reached the end of the line? We treat stream closure as end of line in
+ // order to handle the case when the first line of the response is actually response body
+ // (and we wish it processed as such).
+ if (connp->out_next_byte == CR) {
+ OUT_PEEK_NEXT(connp);
+ if (connp->out_next_byte == -1) {
+ return HTP_DATA_BUFFER;
+ } else if (connp->out_next_byte == LF) {
+ continue;
+ }
+ connp->out_next_byte = LF;
+ }
+ if ((connp->out_next_byte == LF)||(connp->out_status == HTP_STREAM_CLOSED)) {
+ unsigned char *data;
+ size_t len;
+
+ if (htp_connp_res_consolidate_data(connp, &data, &len) != HTP_OK) {
+ return HTP_ERROR;
+ }
+
+ #ifdef HTP_DEBUG
+ fprint_raw_data(stderr, __func__, data, len);
+ #endif
+
+ // Is this a line that should be ignored?
+ if (htp_connp_is_line_ignorable(connp, data, len)) {
+ if (connp->out_status == HTP_STREAM_CLOSED) {
+ connp->out_state = htp_connp_RES_FINALIZE;
+ }
+ // We have an empty/whitespace line, which we'll note, ignore and move on
+ connp->out_tx->response_ignored_lines++;
+
+ // TODO How many lines are we willing to accept?
+
+ // Start again
+ htp_connp_res_clear_buffer(connp);
+
+ return HTP_OK;
+ }
+
+ // Deallocate previous response line allocations, which we would have on a 100 response.
+
+ if (connp->out_tx->response_line != NULL) {
+ bstr_free(connp->out_tx->response_line);
+ connp->out_tx->response_line = NULL;
+ }
+
+ if (connp->out_tx->response_protocol != NULL) {
+ bstr_free(connp->out_tx->response_protocol);
+ connp->out_tx->response_protocol = NULL;
+ }
+
+ if (connp->out_tx->response_status != NULL) {
+ bstr_free(connp->out_tx->response_status);
+ connp->out_tx->response_status = NULL;
+ }
+
+ if (connp->out_tx->response_message != NULL) {
+ bstr_free(connp->out_tx->response_message);
+ connp->out_tx->response_message = NULL;
+ }
+
+ // Process response line.
+
+ int chomp_result = htp_chomp(data, &len);
+
+ // If the response line is invalid, determine if it _looks_ like
+ // a response line. If it does not look like a line, process the
+ // data as a response body because that is what browsers do.
+
+ if (htp_treat_response_line_as_body(data, len)) {
+ // if we have a next line beginning with H, skip this one
+ if (connp->out_current_read_offset+1 < connp->out_current_len && (connp->out_current_data[connp->out_current_read_offset] == 'H' || len <= 2)) {
+ connp->out_tx->response_ignored_lines++;
+ htp_connp_res_clear_buffer(connp);
+ return HTP_OK;
+ }
+ connp->out_tx->response_content_encoding_processing = HTP_COMPRESSION_NONE;
+
+ connp->out_current_consume_offset = connp->out_current_read_offset;
+ htp_status_t rc = htp_tx_res_process_body_data_ex(connp->out_tx, data, len + chomp_result);
+ htp_connp_res_clear_buffer(connp);
+ if (rc != HTP_OK) return rc;
+
+ // Continue to process response body. Because we don't have
+ // any headers to parse, we assume the body continues until
+ // the end of the stream.
+
+ // Have we seen the entire response body?
+ if (connp->out_current_len <= connp->out_current_read_offset) {
+ connp->out_tx->response_transfer_coding = HTP_CODING_IDENTITY;
+ connp->out_tx->response_progress = HTP_RESPONSE_BODY;
+ connp->out_body_data_left = -1;
+ connp->out_state = htp_connp_RES_FINALIZE;
+ }
+
+ return HTP_OK;
+ }
+
+ connp->out_tx->response_line = bstr_dup_mem(data, len);
+ if (connp->out_tx->response_line == NULL) return HTP_ERROR;
+
+ if (connp->cfg->parse_response_line(connp) != HTP_OK) return HTP_ERROR;
+
+ htp_status_t rc = htp_tx_state_response_line(connp->out_tx);
+ if (rc != HTP_OK) return rc;
+
+ htp_connp_res_clear_buffer(connp);
+
+ // Move on to the next phase.
+ connp->out_state = htp_connp_RES_HEADERS;
+ connp->out_tx->response_progress = HTP_RESPONSE_HEADERS;
+
+ return HTP_OK;
+ }
+ }
+
+ return HTP_ERROR;
+}
+
+size_t htp_connp_res_data_consumed(htp_connp_t *connp) {
+ return connp->out_current_read_offset;
+}
+
+htp_status_t htp_connp_RES_FINALIZE(htp_connp_t *connp) {
+ if (connp->out_status != HTP_STREAM_CLOSED) {
+ OUT_PEEK_NEXT(connp);
+ if (connp->out_next_byte == -1) {
+ return htp_tx_state_response_complete_ex(connp->out_tx, 0);
+ }
+ if (connp->out_next_byte != LF || connp->out_current_consume_offset >= connp->out_current_read_offset) {
+ for (;;) {//;i < max_read; i++) {
+ OUT_COPY_BYTE_OR_RETURN(connp);
+ // Have we reached the end of the line? For some reason
+ // we can't test after IN_COPY_BYTE_OR_RETURN */
+ if (connp->out_next_byte == LF)
+ break;
+ }
+ }
+ }
+ size_t bytes_left;
+ unsigned char * data;
+
+ if (htp_connp_res_consolidate_data(connp, &data, &bytes_left) != HTP_OK) {
+ return HTP_ERROR;
+ }
+#ifdef HTP_DEBUG
+ fprint_raw_data(stderr, "PROBING response finalize", data, bytes_left);
+#endif
+ if (bytes_left == 0) {
+ //closing
+ return htp_tx_state_response_complete_ex(connp->out_tx, 0);
+ }
+
+ if (htp_treat_response_line_as_body(data, bytes_left)) {
+ // Interpret remaining bytes as body data
+ htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Unexpected response body");
+ htp_status_t rc = htp_tx_res_process_body_data_ex(connp->out_tx, data, bytes_left);
+ htp_connp_res_clear_buffer(connp);
+ return rc;
+ }
+
+ //unread last end of line so that RES_LINE works
+ if (connp->out_current_read_offset < (int64_t)bytes_left) {
+ connp->out_current_read_offset=0;
+ } else {
+ connp->out_current_read_offset-=bytes_left;
+ }
+ if (connp->out_current_read_offset < connp->out_current_consume_offset) {
+ connp->out_current_consume_offset=connp->out_current_read_offset;
+ }
+ return htp_tx_state_response_complete_ex(connp->out_tx, 0 /* not hybrid mode */);
+}
+
+/**
+ * The response idle state will initialize response processing, as well as
+ * finalize each transactions after we are done with it.
+ *
+ * @param[in] connp
+ * @returns HTP_OK on state change, HTP_ERROR on error, or HTP_DATA when more data is needed.
+ */
+htp_status_t htp_connp_RES_IDLE(htp_connp_t *connp) {
+
+ // We want to start parsing the next response (and change
+ // the state from IDLE) only if there's at least one
+ // byte of data available. Otherwise we could be creating
+ // new structures even if there's no more data on the
+ // connection.
+ OUT_TEST_NEXT_BYTE_OR_RETURN(connp);
+
+ // Parsing a new response
+
+ // Find the next outgoing transaction
+ // If there is none, we just create one so that responses without
+ // request can still be processed.
+ connp->out_tx = htp_list_get(connp->conn->transactions, connp->out_next_tx_index);
+ if (connp->out_tx == NULL) {
+ htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "Unable to match response to request");
+ // finalize dangling request waiting for next request or body
+ if (connp->in_state == htp_connp_REQ_FINALIZE) {
+ htp_tx_state_request_complete(connp->in_tx);
+ }
+ connp->out_tx = htp_connp_tx_create(connp);
+ if (connp->out_tx == NULL) {
+ return HTP_ERROR;
+ }
+ connp->out_tx->parsed_uri = htp_uri_alloc();
+ if (connp->out_tx->parsed_uri == NULL) {
+ return HTP_ERROR;
+ }
+ connp->out_tx->parsed_uri->path = bstr_dup_c(REQUEST_URI_NOT_SEEN);
+ if (connp->out_tx->parsed_uri->path == NULL) {
+ return HTP_ERROR;
+ }
+ connp->out_tx->request_uri = bstr_dup_c(REQUEST_URI_NOT_SEEN);
+ if (connp->out_tx->request_uri == NULL) {
+ return HTP_ERROR;
+ }
+
+ connp->in_state = htp_connp_REQ_FINALIZE;
+#ifdef HTP_DEBUG
+ fprintf(stderr, "picked up response w/o request");
+#endif
+ // We've used one transaction
+ connp->out_next_tx_index++;
+ } else {
+ // We've used one transaction
+ connp->out_next_tx_index++;
+
+ // TODO Detect state mismatch
+
+ connp->out_content_length = -1;
+ connp->out_body_data_left = -1;
+ }
+
+ htp_status_t rc = htp_tx_state_response_start(connp->out_tx);
+ if (rc != HTP_OK) return rc;
+
+ return HTP_OK;
+}
+
+int htp_connp_res_data(htp_connp_t *connp, const htp_time_t *timestamp, const void *data, size_t len) {
+ #ifdef HTP_DEBUG
+ fprintf(stderr, "htp_connp_res_data(connp->out_status %x)\n", connp->out_status);
+ fprint_raw_data(stderr, __func__, data, len);
+ #endif
+
+ // Return if the connection is in stop state
+ if (connp->out_status == HTP_STREAM_STOP) {
+ htp_log(connp, HTP_LOG_MARK, HTP_LOG_INFO, 0, "Outbound parser is in HTP_STREAM_STOP");
+
+ return HTP_STREAM_STOP;
+ }
+
+ // Return if the connection has had a fatal error
+ if (connp->out_status == HTP_STREAM_ERROR) {
+ htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "Outbound parser is in HTP_STREAM_ERROR");
+
+ #ifdef HTP_DEBUG
+ fprintf(stderr, "htp_connp_res_data: returning HTP_STREAM_DATA (previous error)\n");
+ #endif
+
+ return HTP_STREAM_ERROR;
+ }
+
+ // Sanity check: we must have a transaction pointer if the state is not IDLE (no outbound transaction)
+ if ((connp->out_tx == NULL)&&(connp->out_state != htp_connp_RES_IDLE)) {
+ connp->out_status = HTP_STREAM_ERROR;
+
+ htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "Missing outbound transaction data");
+
+ return HTP_STREAM_ERROR;
+ }
+
+ // If the length of the supplied data chunk is zero, proceed
+ // only if the stream has been closed. We do not allow zero-sized
+ // chunks in the API, but we use it internally to force the parsers
+ // to finalize parsing.
+ if (len == 0 && connp->out_status != HTP_STREAM_CLOSED) {
+ htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "Zero-length data chunks are not allowed");
+
+ #ifdef HTP_DEBUG
+ fprintf(stderr, "htp_connp_res_data: returning HTP_STREAM_DATA (zero-length chunk)\n");
+ #endif
+
+ return HTP_STREAM_CLOSED;
+ }
+
+ // Remember the timestamp of the current response data chunk
+ if (timestamp != NULL) {
+ memcpy(&connp->out_timestamp, timestamp, sizeof (*timestamp));
+ }
+
+ // Store the current chunk information
+ connp->out_current_data = (unsigned char *) data;
+ connp->out_current_len = len;
+ connp->out_current_read_offset = 0;
+ connp->out_current_consume_offset = 0;
+ connp->out_current_receiver_offset = 0;
+
+ htp_conn_track_outbound_data(connp->conn, len, timestamp);
+
+ // Return without processing any data if the stream is in tunneling
+ // mode (which it would be after an initial CONNECT transaction.
+ if (connp->out_status == HTP_STREAM_TUNNEL) {
+ #ifdef HTP_DEBUG
+ fprintf(stderr, "htp_connp_res_data: returning HTP_STREAM_TUNNEL\n");
+ #endif
+
+ return HTP_STREAM_TUNNEL;
+ }
+
+ // Invoke a processor, in a loop, until an error
+ // occurs or until we run out of data. Many processors
+ // will process a request, each pointing to the next
+ // processor that needs to run.
+ for (;;) {
+ #ifdef HTP_DEBUG
+ fprintf(stderr, "htp_connp_res_data: out state=%s, progress=%s\n",
+ htp_connp_out_state_as_string(connp),
+ htp_tx_response_progress_as_string(connp->out_tx));
+ #endif
+
+ // Return if there's been an error
+ // or if we've run out of data. We are relying
+ // on processors to add error messages, so we'll
+ // keep quiet here.
+ htp_status_t rc;
+
+ //handle gap
+ if (data == NULL && len > 0) {
+ if (connp->out_state == htp_connp_RES_BODY_IDENTITY_CL_KNOWN ||
+ connp->out_state == htp_connp_RES_BODY_IDENTITY_STREAM_CLOSE) {
+ rc = connp->out_state(connp);
+ } else if (connp->out_state == htp_connp_RES_FINALIZE) {
+ rc = htp_tx_state_response_complete_ex(connp->out_tx, 0);
+ } else {
+ htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "Gaps are not allowed during this state");
+ return HTP_STREAM_CLOSED;
+ }
+ } else {
+ rc = connp->out_state(connp);
+ }
+ if (rc == HTP_OK) {
+ if (connp->out_status == HTP_STREAM_TUNNEL) {
+ #ifdef HTP_DEBUG
+ fprintf(stderr, "htp_connp_res_data: returning HTP_STREAM_TUNNEL\n");
+ #endif
+
+ return HTP_STREAM_TUNNEL;
+ }
+
+ rc = htp_res_handle_state_change(connp);
+ }
+
+ if (rc != HTP_OK) {
+ // Do we need more data?
+ if ((rc == HTP_DATA) || (rc == HTP_DATA_BUFFER)) {
+ htp_connp_res_receiver_send_data(connp, 0 /* not last */);
+
+ if (rc == HTP_DATA_BUFFER) {
+ if (htp_connp_res_buffer(connp) != HTP_OK) {
+ connp->out_status = HTP_STREAM_ERROR;
+ return HTP_STREAM_ERROR;
+ }
+ }
+
+ #ifdef HTP_DEBUG
+ fprintf(stderr, "htp_connp_res_data: returning HTP_STREAM_DATA\n");
+ #endif
+
+ connp->out_status = HTP_STREAM_DATA;
+
+ return HTP_STREAM_DATA;
+ }
+
+ // Check for stop
+ if (rc == HTP_STOP) {
+ #ifdef HTP_DEBUG
+ fprintf(stderr, "htp_connp_res_data: returning HTP_STREAM_STOP\n");
+ #endif
+
+ connp->out_status = HTP_STREAM_STOP;
+
+ return HTP_STREAM_STOP;
+ }
+
+ // Check for suspended parsing
+ if (rc == HTP_DATA_OTHER) {
+ // We might have actually consumed the entire data chunk?
+ if (connp->out_current_read_offset >= connp->out_current_len) {
+ #ifdef HTP_DEBUG
+ fprintf(stderr, "htp_connp_res_data: returning HTP_STREAM_DATA (suspended parsing)\n");
+ #endif
+
+ connp->out_status = HTP_STREAM_DATA;
+
+ // Do not send STREAM_DATE_DATA_OTHER if we've
+ // consumed the entire chunk
+ return HTP_STREAM_DATA;
+ } else {
+ #ifdef HTP_DEBUG
+ fprintf(stderr, "htp_connp_res_data: returning HTP_STREAM_DATA_OTHER\n");
+ #endif
+
+ connp->out_status = HTP_STREAM_DATA_OTHER;
+
+ // Partial chunk consumption
+ return HTP_STREAM_DATA_OTHER;
+ }
+ }
+
+ #ifdef HTP_DEBUG
+ fprintf(stderr, "htp_connp_res_data: returning HTP_STREAM_ERROR\n");
+ #endif
+
+ // Permanent stream error.
+ connp->out_status = HTP_STREAM_ERROR;
+
+ return HTP_STREAM_ERROR;
+ }
+ }
+}