/***************************************************************************
 * 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;
        }
    }
}
