Exemple #1
0
static int HttpGetHeaders(lua_State *luastate, int dir)
{
    if (!(LuaStateNeedProto(luastate, ALPROTO_HTTP)))
        return LuaCallbackError(luastate, "error: protocol not http");

    htp_tx_t *tx = LuaStateGetTX(luastate);
    if (tx == NULL)
        return LuaCallbackError(luastate, "internal error: no tx");

    htp_table_t *table = tx->request_headers;
    if (dir == 1)
        table = tx->response_headers;
    if (tx->request_headers == NULL)
        return LuaCallbackError(luastate, "no headers");

    lua_newtable(luastate);
    htp_header_t *h = NULL;
    size_t i = 0;
    size_t no_of_headers = htp_table_size(table);
    for (; i < no_of_headers; i++) {
        h = htp_table_get_index(table, i, NULL);
        LuaPushStringBuffer(luastate, bstr_ptr(h->name), bstr_len(h->name));
        LuaPushStringBuffer(luastate, bstr_ptr(h->value), bstr_len(h->value));
        lua_settable(luastate, -3);
    }
    return 1;
}
htp_param_t *htp_tx_req_get_param_ex(htp_tx_t *tx, enum htp_data_source_t source, const char *name, size_t name_len) {
    htp_param_t *p = NULL;

    for (int i = 0, n = htp_table_size(tx->request_params); i < n; i++) {
        p = htp_table_get_index(tx->request_params, i, NULL);
        if (p->source != source) continue;

        if (bstr_cmp_mem_nocase(p->name, name, name_len) == 0) return p;
    }

    return NULL;
}
/**
 * Parses request query string, if present.
 *
 * @param[in] connp
 * @param[in] raw_data
 * @param[in] raw_len
 * @return HTP_OK if query string was parsed, HTP_DECLINED if there was no query
 *         string, and HTP_ERROR on failure.
 */
htp_status_t htp_ch_urlencoded_callback_request_line(htp_tx_t *tx) {
    // Proceed only if there's something for us to parse.
    if ((tx->parsed_uri->query == NULL) || (bstr_len(tx->parsed_uri->query) == 0)) {
        return HTP_DECLINED;
    }

    // We have a non-zero length query string.

    tx->request_urlenp_query = htp_urlenp_create(tx);
    if (tx->request_urlenp_query == NULL) return HTP_ERROR;

    if (htp_urlenp_parse_complete(tx->request_urlenp_query, bstr_ptr(tx->parsed_uri->query),
            bstr_len(tx->parsed_uri->query)) != HTP_OK) {
        htp_urlenp_destroy(tx->request_urlenp_query);
        return HTP_ERROR;
    }

    // Add all parameters to the transaction.

    bstr *name = NULL;
    bstr *value = NULL;
    for (size_t i = 0, n = htp_table_size(tx->request_urlenp_query->params); i < n; i++) {
        value = htp_table_get_index(tx->request_urlenp_query->params, i, &name);

        htp_param_t *param = calloc(1, sizeof (htp_param_t));
        if (param == NULL) return HTP_ERROR;
        
        param->name = name;
        param->value = value;
        param->source = HTP_SOURCE_QUERY_STRING;
        param->parser_id = HTP_PARSER_URLENCODED;
        param->parser_data = NULL;

        if (htp_tx_req_add_param(tx, param) != HTP_OK) {
            free(param);
            return HTP_ERROR;
        }
    }

    // All the parameter data is now owned by the transaction, and
    // the parser table used to store it is no longer needed. The
    // line below will destroy just the table, leaving keys intact.
    htp_table_destroy_ex(tx->request_urlenp_query->params);
    tx->request_urlenp_query->params = NULL;

    htp_urlenp_destroy(tx->request_urlenp_query);
    tx->request_urlenp_query = NULL;

    return HTP_OK;
}
htp_status_t htp_tx_res_set_headers_clear(htp_tx_t *tx) {
    if ((tx == NULL) || (tx->response_headers == NULL)) return HTP_ERROR;

    htp_header_t *h = NULL;
    for (size_t i = 0, n = htp_table_size(tx->response_headers); i < n; i++) {
        h = htp_table_get_index(tx->response_headers, i, NULL);
        bstr_free(h->name);
        bstr_free(h->value);
        free(h);
    }

    htp_table_destroy(tx->response_headers);

    tx->response_headers = htp_table_create(32);
    if (tx->response_headers == NULL) return HTP_ERROR;

    return HTP_OK;
}
/**
 * This callback function feeds request body data to a Urlencoded parser
 * and, later, feeds the parsed parameters to the correct structures.
 *
 * @param[in] d
 * @return HTP_OK on success, HTP_ERROR on failure.
 */
htp_status_t htp_ch_urlencoded_callback_request_body_data(htp_tx_data_t *d) {
    htp_tx_t *tx = d->tx;

    // Check that we were not invoked again after the finalization.
    if (tx->request_urlenp_body->params == NULL) return HTP_ERROR;

    if (d->data != NULL) {
        // Process one chunk of data.
        htp_urlenp_parse_partial(tx->request_urlenp_body, d->data, d->len);
    } else {
        // Finalize parsing.
        htp_urlenp_finalize(tx->request_urlenp_body);

        // Add all parameters to the transaction.
        bstr *name = NULL;
        bstr *value = NULL;

        for (size_t i = 0, n = htp_table_size(tx->request_urlenp_body->params); i < n; i++) {
            value = htp_table_get_index(tx->request_urlenp_body->params, i, &name);

            htp_param_t *param = calloc(1, sizeof (htp_param_t));
            if (param == NULL) return HTP_ERROR;

            param->name = name;
            param->value = value;
            param->source = HTP_SOURCE_BODY;
            param->parser_id = HTP_PARSER_URLENCODED;
            param->parser_data = NULL;

            if (htp_tx_req_add_param(tx, param) != HTP_OK) {
                free(param);
                return HTP_ERROR;
            }
        }

        // All the parameter data is now owned by the transaction, and
        // the parser table used to store it is no longer needed. The
        // line below will destroy just the table, leaving keys intact.
        htp_table_destroy_ex(tx->request_urlenp_body->params);
        tx->request_urlenp_body->params = NULL;
    }

    return HTP_OK;
}
Exemple #6
0
/**
 * Destroys an existing URLENCODED parser.
 * 
 * @param[in] urlenp
 */
void htp_urlenp_destroy(htp_urlenp_t *urlenp) {
    if (urlenp == NULL) return;

    if (urlenp->_name != NULL) {
        bstr_free(urlenp->_name);
    }

    bstr_builder_destroy(urlenp->_bb);

    if (urlenp->params != NULL) {
        // Destroy parameters.
        for (size_t i = 0, n = htp_table_size(urlenp->params); i < n; i++) {
            bstr *b = htp_table_get_index(urlenp->params, i, NULL);
            // Parameter name will be freed by the table code.
            bstr_free(b);
        }

        htp_table_destroy(urlenp->params);
    }

    free(urlenp);
}
Exemple #7
0
/**
 * 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 tunnelling mode.
            connp->in_status = HTP_STREAM_TUNNEL;
            connp->out_status = HTP_STREAM_TUNNEL;
            connp->out_state = htp_connp_RES_FINALIZE;
            return HTP_OK;
        } else {
            // This is a failed CONNECT stream, which means that
            // we can unblock request parsing
            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;
        }
    }

    // Check for an interim "100 Continue" response. Ignore it if found, and revert back to RES_FIRST_LINE.
    if (connp->out_tx->response_status_number == 100) {
        if (connp->out_tx->seen_100continue != 0) {
            htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "Already seen 100-Continue.");
            return HTP_ERROR;
        }

        // Ignore any response headers seen so far.
        htp_header_t *h = NULL;
        for (int 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->progress = HTP_RESPONSE_LINE;
        connp->out_tx->seen_100continue++;

        return HTP_OK;
    }

    // 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->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)
            || (connp->out_tx->request_method_number == HTP_M_HEAD)) {
        // There's no response body
        connp->out_tx->response_transfer_coding = HTP_CODING_NO_BODY;
        connp->out_state = htp_connp_RES_FINALIZE;
    } else {
        // We have a response body

        htp_header_t *ct = htp_table_get_c(connp->out_tx->response_headers, "content-type");
        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");

        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_cmp_c(te->value, "chunked") == 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->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);
            if (connp->out_tx->response_content_length < 0) {
                htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "Invalid C-L field in response: %d",
                        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->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->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

    int rc = htp_tx_state_response_headers(connp->out_tx);
    if (rc != HTP_OK) return rc;

    return HTP_OK;
}
Exemple #8
0
static uint8_t *GetBufferForTX(htp_tx_t *tx, uint64_t tx_id,
        DetectEngineThreadCtx *det_ctx,
        Flow *f, uint8_t flags, uint32_t *buffer_len)
{
    *buffer_len = 0;

    HttpHeaderThreadData *hdr_td = NULL;
    HttpHeaderBuffer *buf = HttpHeaderGetBufferSpaceForTXID(det_ctx, f, flags,
            tx_id, g_keyword_thread_id, &hdr_td);
    if (unlikely(buf == NULL)) {
        return NULL;
    } else if (buf->len > 0) {
        /* already filled buf, reuse */
        *buffer_len = buf->len;
        return buf->buffer;
    }

    bstr *line = NULL;
    htp_table_t *headers;
    if (flags & STREAM_TOSERVER) {
        if (AppLayerParserGetStateProgress(IPPROTO_TCP, ALPROTO_HTTP, tx, flags) <=
                HTP_REQUEST_HEADERS)
            return NULL;
        line = tx->request_line;
        headers = tx->request_headers;
    } else {
        if (AppLayerParserGetStateProgress(IPPROTO_TCP, ALPROTO_HTTP, tx, flags) <=
                HTP_RESPONSE_HEADERS)
            return NULL;
        headers = tx->response_headers;
        line = tx->response_line;
    }
    if (line == NULL || headers == NULL)
        return NULL;

    size_t line_size = bstr_len(line) + 2;
    if (line_size + buf->len > buf->size) {
        if (HttpHeaderExpandBuffer(hdr_td, buf, line_size) != 0) {
            return NULL;
        }
    }
    memcpy(buf->buffer + buf->len, bstr_ptr(line), bstr_size(line));
    buf->len += bstr_size(line);
    buf->buffer[buf->len++] = '\r';
    buf->buffer[buf->len++] = '\n';

    size_t i = 0;
    size_t no_of_headers = htp_table_size(headers);
    for (; i < no_of_headers; i++) {
        htp_header_t *h = htp_table_get_index(headers, i, NULL);
        size_t size1 = bstr_size(h->name);
        size_t size2 = bstr_size(h->value);
        size_t size = size1 + size2 + 4;
        if (i + 1 == no_of_headers)
            size += 2;
        if (size + buf->len > buf->size) {
            if (HttpHeaderExpandBuffer(hdr_td, buf, size) != 0) {
                return NULL;
            }
        }

        memcpy(buf->buffer + buf->len, bstr_ptr(h->name), bstr_size(h->name));
        buf->len += bstr_size(h->name);
        buf->buffer[buf->len++] = ':';
        buf->buffer[buf->len++] = ' ';
        memcpy(buf->buffer + buf->len, bstr_ptr(h->value), bstr_size(h->value));
        buf->len += bstr_size(h->value);
        buf->buffer[buf->len++] = '\r';
        buf->buffer[buf->len++] = '\n';
        if (i + 1 == no_of_headers) {
            buf->buffer[buf->len++] = '\r';
            buf->buffer[buf->len++] = '\n';
        }
    }

    *buffer_len = buf->len;
    return buf->buffer;
}
Exemple #9
0
/**
 * Transcode all parameters supplied in the table.
 *
 * @param[in] connp
 * @param[in] params
 * @param[in] destroy_old
 */
int htp_transcode_params(htp_connp_t *connp, htp_table_t **params, int destroy_old) {
    htp_table_t *input_params = *params;

    // No transcoding unless necessary
    if ((connp->cfg->internal_encoding == NULL)||(connp->cfg->request_encoding == NULL)) return HTP_OK;

    // Create a new table that will hold transcoded parameters
    htp_table_t *output_params = htp_table_create(htp_table_size(input_params));
    if (output_params == NULL) return HTP_ERROR;
    
    // Initialize iconv
    iconv_t cd = iconv_open(connp->cfg->internal_encoding, connp->cfg->request_encoding);
    if (cd == (iconv_t) -1) {        
        htp_table_destroy(output_params);
        return HTP_ERROR;
    }

    #if (_LIBICONV_VERSION >= 0x0108)
    int iconv_param = 0;
    iconvctl(cd, ICONV_SET_TRANSLITERATE, &iconv_param);
    iconv_param = 1;
    iconvctl(cd, ICONV_SET_DISCARD_ILSEQ, &iconv_param);
    #endif
    
    // Convert the parameters, one by one
    bstr *name = NULL;
    bstr *value = NULL;    
    for (int i = 0, n = htp_table_size(input_params); i < n; i++) {
        value = htp_table_get_index(input_params, i, &name);
        
        bstr *new_name = NULL, *new_value = NULL;        
        
        // Convert name
        htp_transcode_bstr(cd, name, &new_name);
        if (new_name == NULL) {
            iconv_close(cd);

            bstr *b = NULL;
            for (int j = 0, k = htp_table_size(output_params); j < k; j++) {
                b = htp_table_get_index(output_params, j, NULL);
                bstr_free(b);
            }
            
            htp_table_destroy(output_params);
            return HTP_ERROR;
        }
        
        // Convert value        
        htp_transcode_bstr(cd, value, &new_value);
        if (new_value == NULL) {
            bstr_free(new_name);
            iconv_close(cd);

            bstr *b = NULL;
            for (int j = 0, k = htp_table_size(output_params); j < k; j++) {
                b = htp_table_get_index(output_params, j, NULL);
                bstr_free(b);
            }
            
            htp_table_destroy(output_params);
            return HTP_ERROR;
        }
        
        // Add to new table
        htp_table_addn(output_params, new_name, new_value);
    }
    
    // Replace the old parameter table
    *params = output_params;

    // Destroy the old parameter table if necessary
    if (destroy_old) {
        bstr *b = NULL;
        for (int i = 0, n = htp_table_size(input_params); i < n; i++) {
            b = htp_table_get_index(input_params, i, NULL);
            bstr_free(b);
        }      
    
        htp_table_destroy(input_params);
    }
    
    iconv_close(cd);

    return HTP_OK;
}
void htp_tx_destroy_incomplete(htp_tx_t *tx) {
    if (tx == NULL) return;

    // Disconnect transaction from other structures.
    htp_conn_remove_tx(tx->conn, tx);
    htp_connp_tx_remove(tx->connp, tx);

    // Request fields.

    bstr_free(tx->request_line);
    bstr_free(tx->request_method);
    bstr_free(tx->request_uri);
    bstr_free(tx->request_protocol);
    bstr_free(tx->request_content_type);
    bstr_free(tx->request_hostname);
    htp_uri_free(tx->parsed_uri_raw);
    htp_uri_free(tx->parsed_uri);

    // Request_headers.
    if (tx->request_headers != NULL) {
        htp_header_t *h = NULL;
        for (size_t i = 0, n = htp_table_size(tx->request_headers); i < n; i++) {
            h = htp_table_get_index(tx->request_headers, i, NULL);
            bstr_free(h->name);
            bstr_free(h->value);
            free(h);
        }

        htp_table_destroy(tx->request_headers);
    }

    // Request parsers.

    htp_urlenp_destroy(tx->request_urlenp_query);
    htp_urlenp_destroy(tx->request_urlenp_body);
    htp_mpartp_destroy(tx->request_mpartp);

    // Request parameters.

    htp_param_t *param = NULL;
    for (size_t i = 0, n = htp_table_size(tx->request_params); i < n; i++) {
        param = htp_table_get_index(tx->request_params, i, NULL);
        free(param->name);
        free(param->value);
        free(param);
    }

    htp_table_destroy(tx->request_params);

    // Request cookies.

    if (tx->request_cookies != NULL) {
        bstr *b = NULL;
        for (size_t i = 0, n = htp_table_size(tx->request_cookies); i < n; i++) {
            b = htp_table_get_index(tx->request_cookies, i, NULL);
            bstr_free(b);
        }

        htp_table_destroy(tx->request_cookies);
    }

    htp_hook_destroy(tx->hook_request_body_data);

    // Response fields.

    bstr_free(tx->response_line);
    bstr_free(tx->response_protocol);
    bstr_free(tx->response_status);
    bstr_free(tx->response_message);
    bstr_free(tx->response_content_type);

    // Destroy response headers.
    if (tx->response_headers != NULL) {
        htp_header_t *h = NULL;
        for (size_t i = 0, n = htp_table_size(tx->response_headers); i < n; i++) {
            h = htp_table_get_index(tx->response_headers, i, NULL);
            bstr_free(h->name);
            bstr_free(h->value);
            free(h);
        }

        htp_table_destroy(tx->response_headers);
    }

    // If we're using a private configuration structure, destroy it.
    if (tx->is_config_shared == HTP_CONFIG_PRIVATE) {
        htp_config_destroy(tx->cfg);
    }

    free(tx);
}
Exemple #11
0
void htp_tx_destroy(htp_tx_t *tx) {
    bstr_free(tx->request_line);
    bstr_free(tx->request_line_raw);
    bstr_free(tx->request_method);
    bstr_free(tx->request_uri);
    bstr_free(tx->request_uri_normalized);
    bstr_free(tx->request_protocol);
    bstr_free(tx->request_headers_sep);

    if (tx->parsed_uri != NULL) {
        bstr_free(tx->parsed_uri->scheme);
        bstr_free(tx->parsed_uri->username);
        bstr_free(tx->parsed_uri->password);
        bstr_free(tx->parsed_uri->hostname);
        bstr_free(tx->parsed_uri->port);
        bstr_free(tx->parsed_uri->path);
        bstr_free(tx->parsed_uri->query);
        bstr_free(tx->parsed_uri->fragment);

        free(tx->parsed_uri);
    }

    if (tx->parsed_uri_incomplete != NULL) {
        bstr_free(tx->parsed_uri_incomplete->scheme);
        bstr_free(tx->parsed_uri_incomplete->username);
        bstr_free(tx->parsed_uri_incomplete->password);
        bstr_free(tx->parsed_uri_incomplete->hostname);
        bstr_free(tx->parsed_uri_incomplete->port);
        bstr_free(tx->parsed_uri_incomplete->path);
        bstr_free(tx->parsed_uri_incomplete->query);
        bstr_free(tx->parsed_uri_incomplete->fragment);
        free(tx->parsed_uri_incomplete);
    }

    // Destroy request_header_lines.
    if (tx->request_header_lines != NULL) {
        for (int i = 0, n = htp_list_size(tx->request_header_lines); i < n; i++) {
            htp_header_line_t *hl = htp_list_get(tx->request_header_lines, i);
            bstr_free(hl->line);
            // No need to destroy hl->header because
            // htp_header_line_t does not own it.
            free(hl);
        }

        htp_list_destroy(tx->request_header_lines);
        tx->request_header_lines = NULL;
    }

    // Destroy request_headers.
    if (tx->request_headers != NULL) {
        htp_header_t *h = NULL;
        for (int i = 0, n = htp_table_size(tx->request_headers); i < n; i++) {
            h = htp_table_get_index(tx->request_headers, i, NULL);
            bstr_free(h->name);
            bstr_free(h->value);
            free(h);
        }

        htp_table_destroy(tx->request_headers);
    }

    if (tx->request_headers_raw != NULL) {
        bstr_free(tx->request_headers_raw);
    }
    if (tx->response_headers_raw != NULL) {
        bstr_free(tx->response_headers_raw);
    }

    bstr_free(tx->response_line);
    bstr_free(tx->response_line_raw);
    bstr_free(tx->response_protocol);
    bstr_free(tx->response_status);
    bstr_free(tx->response_message);
    bstr_free(tx->response_headers_sep);

    // Destroy response_header_lines.
    if (tx->response_header_lines != NULL) {
        for (int i = 0, n = htp_list_size(tx->response_header_lines); i < n; i++) {
            htp_header_line_t *hl = htp_list_get(tx->response_header_lines, i);
            bstr_free(hl->line);
            // No need to destroy hl->header because
            // htp_header_line_t does not own it.
            free(hl);
        }

        htp_list_destroy(tx->response_header_lines);
        tx->response_header_lines = NULL;
    }

    // Destroy response headers.
    if (tx->response_headers != NULL) {
        htp_header_t *h = NULL;
        for (int i = 0, n = htp_table_size(tx->response_headers); i < n; i++) {
            h = htp_table_get_index(tx->response_headers, i, NULL);
            bstr_free(h->name);
            bstr_free(h->value);
            free(h);
        }

        htp_table_destroy(tx->response_headers);
    }

    // Tell the connection to remove this transaction from the list.
    htp_conn_remove_tx(tx->conn, tx);

    // Invalidate the pointer to this transactions held
    // by the connection parser. This is to allow a transaction
    // to be destroyed from within the final response callback.
    if (tx->connp != NULL) {
        if (tx->connp->out_tx == tx) {
            tx->connp->out_tx = NULL;
        }
    }

    bstr_free(tx->request_content_type);
    bstr_free(tx->response_content_type);

    // Parsers

    htp_urlenp_destroy(tx->request_urlenp_query);
    htp_urlenp_destroy(tx->request_urlenp_body);
    htp_mpartp_destroy(tx->request_mpartp);

    // Request parameters

    htp_param_t *param = NULL;
    for (int i = 0, n = htp_table_size(tx->request_params); i < n; i++) {
        param = htp_table_get_index(tx->request_params, i, NULL);
        free(param->name);
        free(param->value);
        free(param);
    }

    htp_table_destroy(tx->request_params);

    // Request cookies

    if (tx->request_cookies != NULL) {
        bstr *b = NULL;
        for (int i = 0, n = htp_table_size(tx->request_cookies); i < n; i++) {
            b = htp_table_get_index(tx->request_cookies, i, NULL);
            bstr_free(b);
        }

        htp_table_destroy(tx->request_cookies);
    }

    htp_hook_destroy(tx->hook_request_body_data);

    // If we're using a private configuration, destroy it.
    if (tx->is_config_shared == HTP_CONFIG_PRIVATE) {
        htp_config_destroy(tx->cfg);
    }

    free(tx);
}