You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
suricata/libhtp/htp/htp_request.c

862 lines
30 KiB
C

/*
* LibHTP (http://www.libhtp.org)
* Copyright 2009,2010 Ivan Ristic <ivanr@webkreator.com>
*
* LibHTP is an open source product, released under terms of the General Public Licence
* version 2 (GPLv2). Please refer to the file LICENSE, which contains the complete text
* of the license.
*
* In addition, there is a special exception that allows LibHTP to be freely
* used with any OSI-approved open source licence. Please refer to the file
* LIBHTP_LICENSING_EXCEPTION for the full text of the exception.
*
*/
#include <stdlib.h>
#include "htp.h"
/**
* Performs check for a CONNECT transaction to decide whether inbound
* parsing needs to be suspended.
*
* @param connp
* @return HTP_OK if the request does not use CONNECT, HTP_DATA_OTHER if
* inbound parsing needs to be suspended until we hear from the
* other side
*/
int htp_connp_REQ_CONNECT_CHECK(htp_connp_t *connp) {
// If the request uses the CONNECT method, then there will
// not be a request body, but first we need to wait to see the
// response in order to determine if the tunneling request
// was a success.
if (connp->in_tx->request_method_number == M_CONNECT) {
connp->in_state = htp_connp_REQ_CONNECT_WAIT_RESPONSE;
connp->in_status = STREAM_STATE_DATA_OTHER;
connp->in_tx->progress = TX_PROGRESS_WAIT;
return HTP_DATA_OTHER;
}
// Continue to the next step to determine the presence
// of the request body
connp->in_state = htp_connp_REQ_BODY_DETERMINE;
return HTP_OK;
}
/**
* Determines whether inbound parsing, which was suspended after
* encountering a CONNECT transaction, can proceed (after receiving
* the response).
*
* @param connp
* @return HTP_OK if the parser can resume parsing, HTP_DATA_OTHER if
* it needs to continue waiting.
*/
int htp_connp_REQ_CONNECT_WAIT_RESPONSE(htp_connp_t *connp) {
// Check that we saw the response line of the current
// inbound transaction.
if (connp->in_tx->progress <= TX_PROGRESS_RES_LINE) {
return HTP_DATA_OTHER;
}
// A 2xx response means a tunnel was established. Anything
// else means we continue to follow the HTTP stream.
if ((connp->in_tx->response_status_number >= 200) && (connp->in_tx->response_status_number <= 299)) {
// TODO Check that the server did not accept a connection
// to itself.
// The requested tunnel was established: we are going
// to ignore the remaining data on this stream
connp->in_status = STREAM_STATE_TUNNEL;
connp->in_state = htp_connp_REQ_IDLE;
} else {
// No tunnel; continue to the next transaction
connp->in_state = htp_connp_REQ_IDLE;
}
return HTP_OK;
}
/**
* Consumes bytes until the end of the current line.
*
* @param connp
* @returns HTP_OK on state change, HTTP_ERROR on error, or HTP_DATA when more data is needed.
*/
int htp_connp_REQ_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 (;;) {
IN_NEXT_BYTE_OR_RETURN(connp);
connp->in_tx->request_message_len++;
if (connp->in_next_byte == LF) {
connp->in_state = htp_connp_REQ_BODY_CHUNKED_LENGTH;
return HTP_OK;
}
}
}
/**
* Processes a chunk of data.
*
* @param connp
* @returns HTP_OK on state change, HTTP_ERROR on error, or HTP_DATA when more data is needed.
*/
int htp_connp_REQ_BODY_CHUNKED_DATA(htp_connp_t *connp) {
htp_tx_data_t d;
d.tx = connp->in_tx;
d.data = &connp->in_current_data[connp->in_current_offset];
d.len = 0;
for (;;) {
IN_NEXT_BYTE(connp);
if (connp->in_next_byte == -1) {
// Send data to callbacks
int rc = hook_run_all(connp->cfg->hook_request_body_data, &d);
if (rc != HOOK_OK) {
htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
"Request body data callback returned error (%d)", rc);
return HTP_ERROR;
}
// Ask for more data
return HTP_DATA;
} else {
connp->in_tx->request_message_len++;
connp->in_tx->request_entity_len++;
connp->in_chunked_length--;
d.len++;
if (connp->in_chunked_length == 0) {
// End of data chunk
// Send data to callbacks
int rc = hook_run_all(connp->cfg->hook_request_body_data, &d);
if (rc != HOOK_OK) {
htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
"Request body data callback returned error (%d)", rc);
return HTP_ERROR;
}
connp->in_state = htp_connp_REQ_BODY_CHUNKED_DATA_END;
return HTP_OK;
}
}
}
}
/**
* Extracts chunk length.
*
* @param connp
* @returns HTP_OK on state change, HTTP_ERROR on error, or HTP_DATA when more data is needed.
*/
int htp_connp_REQ_BODY_CHUNKED_LENGTH(htp_connp_t *connp) {
for (;;) {
IN_COPY_BYTE_OR_RETURN(connp);
connp->in_tx->request_message_len++;
// Have we reached the end of the line?
if (connp->in_next_byte == LF) {
htp_chomp(connp->in_line, &connp->in_line_len);
// Extract chunk length
connp->in_chunked_length = htp_parse_chunked_length(connp->in_line, connp->in_line_len);
// Cleanup for the next line
connp->in_line_len = 0;
// Handle chunk length
if (connp->in_chunked_length > 0) {
// More data available
// TODO Add a check for chunk length
connp->in_state = htp_connp_REQ_BODY_CHUNKED_DATA;
} else if (connp->in_chunked_length == 0) {
// End of data
connp->in_state = htp_connp_REQ_HEADERS;
connp->in_tx->progress = TX_PROGRESS_REQ_TRAILER;
} else {
// Invalid chunk length
htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
"Request chunk encoding: Invalid chunk length");
return HTP_ERROR;
}
return HTP_OK;
}
}
}
/**
* Processes identity request body.
*
* @param connp
* @returns HTP_OK on state change, HTTP_ERROR on error, or HTP_DATA when more data is needed.
*/
int htp_connp_REQ_BODY_IDENTITY(htp_connp_t *connp) {
htp_tx_data_t d;
d.tx = connp->in_tx;
d.data = &connp->in_current_data[connp->in_current_offset];
d.len = 0;
for (;;) {
IN_NEXT_BYTE(connp);
if (connp->in_next_byte == -1) {
// End of chunk
if (d.len != 0) {
int rc = hook_run_all(connp->cfg->hook_request_body_data, &d);
if (rc != HOOK_OK) {
htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
"Request body data callback returned error (%d)", rc);
return HTP_ERROR;
}
}
// Ask for more data
return HTP_DATA;
} else {
connp->in_tx->request_message_len++;
connp->in_tx->request_entity_len++;
connp->in_body_data_left--;
d.len++;
if (connp->in_body_data_left == 0) {
// End of body
if (d.len != 0) {
int rc = hook_run_all(connp->cfg->hook_request_body_data, &d);
if (rc != HOOK_OK) {
htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
"Request body data callback returned error (%d)", rc);
return HTP_ERROR;
}
}
// Done
connp->in_state = htp_connp_REQ_IDLE;
connp->in_tx->progress = TX_PROGRESS_WAIT;
return HTP_OK;
}
}
}
}
/**
* Determines presence (and encoding) of a request body.
*
* @param connp
* @returns HTP_OK on state change, HTTP_ERROR on error, or HTP_DATA when more data is needed.
*/
int htp_connp_REQ_BODY_DETERMINE(htp_connp_t *connp) {
htp_header_t *cl = table_getc(connp->in_tx->request_headers, "content-length");
htp_header_t *te = table_getc(connp->in_tx->request_headers, "transfer-encoding");
// Check for the Transfer-Encoding header, which
// would indicate a chunked request body
if (te != NULL) {
// Make sure it contains "chunked" only
if (bstr_cmpc(te->value, "chunked") != 0) {
// Invalid T-E header value
htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
"Invalid T-E value in request");
}
// Chunked encoding is a HTTP/1.1 feature. Check
// that some other protocol is not used. The flag will
// also be set if the protocol could not be parsed.
//
// TODO IIS 7.0, for example, would ignore the T-E header when it
// it is used with a protocol below HTTP 1.1.
if (connp->in_tx->request_protocol_number < HTTP_1_1) {
connp->in_tx->flags |= HTP_INVALID_CHUNKING;
// TODO Log
}
// If the T-E header is present we are going to use it.
connp->in_tx->request_transfer_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->in_tx->flags |= HTP_REQUEST_SMUGGLING;
// TODO Log
}
connp->in_state = htp_connp_REQ_BODY_CHUNKED_LENGTH;
connp->in_tx->progress = TX_PROGRESS_REQ_BODY;
} else
// Next check for the presence of the Content-Length header
if (cl != NULL) {
// It seems that we have a request body.
connp->in_tx->request_transfer_coding = IDENTITY;
// Check for a folded C-L header
if (cl->flags & HTP_FIELD_FOLDED) {
connp->in_tx->flags |= HTP_REQUEST_SMUGGLING;
// TODO Log
}
// Check for multiple C-L headers
if (cl->flags & HTP_FIELD_REPEATED) {
connp->in_tx->flags |= HTP_REQUEST_SMUGGLING;
// TODO Log
}
// Get body length
int i = htp_parse_content_length(cl->value);
if (i < 0) {
htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "Invalid C-L field in request");
return HTP_ERROR;
} else {
connp->in_content_length = i;
connp->in_body_data_left = connp->in_content_length;
if (connp->in_content_length != 0) {
connp->in_state = htp_connp_REQ_BODY_IDENTITY;
connp->in_tx->progress = TX_PROGRESS_REQ_BODY;
} else {
connp->in_state = htp_connp_REQ_IDLE;
connp->in_tx->progress = TX_PROGRESS_WAIT;
}
}
} else {
// This request does not have a body, which
// means that we're done with it
connp->in_state = htp_connp_REQ_IDLE;
connp->in_tx->progress = TX_PROGRESS_WAIT;
}
// Host resolution
htp_header_t *h = table_getc(connp->in_tx->request_headers, "host");
if (h == NULL) {
// No host information in the headers
// HTTP/1.1 requires host information in the headers
if (connp->in_tx->request_protocol_number >= HTTP_1_1) {
connp->in_tx->flags |= HTP_HOST_MISSING;
htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0,
"Host information in request headers required by HTTP/1.1");
}
} else {
// Host information available in the headers
// Is there host information in the URI?
if (connp->in_tx->parsed_uri->hostname == NULL) {
// There is no host information in the URI. Place the
// hostname from the headers into the parsed_uri structure.
htp_replace_hostname(connp, connp->in_tx->parsed_uri, h->value);
} else {
// The host information is present both in the
// headers and the URI. The HTTP RFC states that
// we should ignore the headers copy.
connp->in_tx->flags |= HTP_AMBIGUOUS_HOST;
htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Host information ambiguous");
}
}
// Run hook REQUEST_HEADERS
int rc = hook_run_all(connp->cfg->hook_request_headers, connp);
if (rc != HOOK_OK) {
htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
"Request headers callback returned error (%d)", rc);
return HTP_ERROR;
}
return HTP_OK;
}
/**
* Parses request headers.
*
* @param connp
* @returns HTP_OK on state change, HTTP_ERROR on error, or HTP_DATA when more data is needed.
*/
int htp_connp_REQ_HEADERS(htp_connp_t *connp) {
for (;;) {
IN_COPY_BYTE_OR_RETURN(connp);
if (connp->in_header_line == NULL) {
connp->in_header_line = calloc(1, sizeof (htp_header_line_t));
if (connp->in_header_line == NULL) return HTP_ERROR;
connp->in_header_line->first_nul_offset = -1;
}
// Keep track of NUL bytes
if (connp->in_next_byte == 0) {
// Store the offset of the first NUL
if (connp->in_header_line->has_nulls == 0) {
connp->in_header_line->first_nul_offset = connp->in_line_len;
}
// Remember how many NULs there were
connp->in_header_line->flags |= HTP_FIELD_NUL_BYTE;
connp->in_header_line->has_nulls++;
}
// Have we reached the end of the line?
if (connp->in_next_byte == LF) {
#ifdef HTP_DEBUG
fprint_raw_data(stderr, __FUNCTION__, connp->in_line, connp->in_line_len);
#endif
// Should we terminate headers?
if (htp_connp_is_line_terminator(connp, connp->in_line, connp->in_line_len)) {
// Terminator line
// Parse previous header, if any
if (connp->in_header_line_index != -1) {
if (connp->cfg->process_request_header(connp) != HTP_OK) {
// Note: downstream responsible for error logging
return HTP_ERROR;
}
// Reset index
connp->in_header_line_index = -1;
}
// Cleanup
free(connp->in_header_line);
connp->in_line_len = 0;
connp->in_header_line = NULL;
connp->in_header_line_index = -1;
connp->in_header_line_counter = 0;
// We've seen all request headers
if (connp->in_chunk_count != connp->in_chunk_request_index) {
connp->in_tx->flags |= HTP_MULTI_PACKET_HEAD;
}
// Move onto the next processing phase
if (connp->in_tx->progress == TX_PROGRESS_REQ_HEADERS) {
// Determine if this request has a body
//connp->in_state = htp_connp_REQ_BODY_DETERMINE;
connp->in_state = htp_connp_REQ_CONNECT_CHECK;
} else {
// Run hook REQUEST_TRAILER
int rc = hook_run_all(connp->cfg->hook_request_trailer, connp);
if (rc != HOOK_OK) {
htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
"Request trailer callback returned error (%d)", rc);
return HTP_ERROR;
}
// We've completed parsing this request
connp->in_state = htp_connp_REQ_IDLE;
connp->in_tx->progress = TX_PROGRESS_WAIT;
}
return HTP_OK;
}
// Prepare line for consumption
htp_chomp(connp->in_line, &connp->in_line_len);
// Check for header folding
if (htp_connp_is_line_folded(connp->in_line, connp->in_line_len) == 0) {
// New header line
// Parse previous header, if any
if (connp->in_header_line_index != -1) {
if (connp->cfg->process_request_header(connp) != HTP_OK) {
// Note: downstream responsible for error logging
return HTP_ERROR;
}
// Reset index
connp->in_header_line_index = -1;
}
// Remember the index of the fist header line
connp->in_header_line_index = connp->in_header_line_counter;
} else {
// Folding; check that there's a previous header line to add to
if (connp->in_header_line_index == -1) {
if (!(connp->in_tx->flags & HTP_INVALID_FOLDING)) {
connp->in_tx->flags |= HTP_INVALID_FOLDING;
htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0,
"Invalid request field folding");
}
}
}
// Add the raw header line to the list
connp->in_header_line->line = bstr_memdup((char *) connp->in_line, connp->in_line_len);
list_add(connp->in_tx->request_header_lines, connp->in_header_line);
connp->in_header_line = NULL;
// Cleanup for the next line
connp->in_line_len = 0;
if (connp->in_header_line_index == -1) {
connp->in_header_line_index = connp->in_header_line_counter;
}
connp->in_header_line_counter++;
}
}
}
/**
* Determines request protocol.
*
* @param connp
* @returns HTP_OK on state change, HTTP_ERROR on error, or HTP_DATA when more data is needed.
*/
int htp_connp_REQ_PROTOCOL(htp_connp_t *connp) {
// Is this a short-style HTTP/0.9 request? If it is,
// we will not want to parse request headers.
if (connp->in_tx->protocol_is_simple == 0) {
// Switch to request header parsing.
connp->in_state = htp_connp_REQ_HEADERS;
connp->in_tx->progress = TX_PROGRESS_REQ_HEADERS;
} else {
// We're done with this request.
connp->in_state = htp_connp_REQ_IDLE;
connp->in_tx->progress = TX_PROGRESS_WAIT;
}
return HTP_OK;
}
/**
* Parses request line.
*
* @param connp
* @returns HTP_OK on state change, HTTP_ERROR on error, or HTP_DATA when more data is needed.
*/
int htp_connp_REQ_LINE(htp_connp_t *connp) {
for (;;) {
// Get one byte
IN_COPY_BYTE_OR_RETURN(connp);
// Keep track of NUL bytes
if (connp->in_next_byte == 0) {
// Remember how many NULs there were
connp->in_tx->request_line_nul++;
// Store the offset of the first NUL byte
if (connp->in_tx->request_line_nul_offset == -1) {
connp->in_tx->request_line_nul_offset = connp->in_line_len;
}
}
// Have we reached the end of the line?
if (connp->in_next_byte == LF) {
#ifdef HTP_DEBUG
fprint_raw_data(stderr, __FUNCTION__, connp->in_line, connp->in_line_len);
#endif
// Is this a line that should be ignored?
if (htp_connp_is_line_ignorable(connp, connp->in_line, connp->in_line_len)) {
// We have an empty/whitespace line, which we'll note, ignore and move on
connp->in_tx->request_ignored_lines++;
// TODO How many empty lines are we willing to accept?
// Start again
connp->in_line_len = 0;
return HTP_OK;
}
// Process request line
htp_chomp(connp->in_line, &connp->in_line_len);
connp->in_tx->request_line = bstr_memdup((char *) connp->in_line, connp->in_line_len);
// Parse request line
if (connp->cfg->parse_request_line(connp) != HTP_OK) {
// Note: downstream responsible for error logging
return HTP_ERROR;
}
if (connp->in_tx->request_method_number == M_CONNECT) {
// Parse authority
if (htp_parse_authority(connp, connp->in_tx->request_uri, &(connp->in_tx->parsed_uri_incomplete)) != HTP_OK) {
// Note: downstream responsible for error logging
return HTP_ERROR;
}
} else {
// Parse the request URI
if (htp_parse_uri(connp->in_tx->request_uri, &(connp->in_tx->parsed_uri_incomplete)) != HTP_OK) {
// Note: downstream responsible for error logging
return HTP_ERROR;
}
// Keep the original URI components, but
// create a copy which we can normalize and use internally
if (htp_normalize_parsed_uri(connp, connp->in_tx->parsed_uri_incomplete, connp->in_tx->parsed_uri)) {
// Note: downstream responsible for error logging
return HTP_ERROR;
}
// Now is a good time to generate request_uri_normalized, before we finalize
// parsed_uri (and lose the information which parts were provided in the request and
// which parts we added).
if (connp->cfg->generate_request_uri_normalized) {
connp->in_tx->request_uri_normalized = htp_unparse_uri_noencode(connp->in_tx->parsed_uri);
if (connp->in_tx->request_uri_normalized == NULL) {
// There's no sense in logging anything on a memory allocation failure
return HTP_ERROR;
}
#ifdef HTP_DEBUG
fprint_raw_data(stderr, "request_uri_normalized",
(unsigned char *) bstr_ptr(connp->in_tx->request_uri_normalized),
bstr_len(connp->in_tx->request_uri_normalized));
#endif
}
// Finalize parsed_uri
// Scheme
if (connp->in_tx->parsed_uri->scheme != NULL) {
if (bstr_cmpc(connp->in_tx->parsed_uri->scheme, "http") != 0) {
// TODO Invalid scheme
}
} else {
connp->in_tx->parsed_uri->scheme = bstr_cstrdup("http");
}
// Port
if (connp->in_tx->parsed_uri->port != NULL) {
if (connp->in_tx->parsed_uri->port_number != -1) {
// Check that the port in the URI is the same
// as the port on which the client is talking
// to the server
if (connp->in_tx->parsed_uri->port_number != connp->conn->local_port) {
// Incorrect port; use the real port instead
connp->in_tx->parsed_uri->port_number = connp->conn->local_port;
// TODO Log
}
} else {
// Invalid port; use the real port instead
connp->in_tx->parsed_uri->port_number = connp->conn->local_port;
// TODO Log
}
} else {
connp->in_tx->parsed_uri->port_number = connp->conn->local_port;
}
// Path
if (connp->in_tx->parsed_uri->path == NULL) {
connp->in_tx->parsed_uri->path = bstr_cstrdup("/");
}
}
// Run hook REQUEST_LINE
int rc = hook_run_all(connp->cfg->hook_request_line, connp);
if (rc != HOOK_OK) {
htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
"Request line callback returned error (%d)", rc);
return HTP_ERROR;
}
// Clean up.
connp->in_line_len = 0;
// Move on to the next phase.
connp->in_state = htp_connp_REQ_PROTOCOL;
return HTP_OK;
}
}
}
/**
* The idle state is invoked before and after every transaction. Consequently,
* it will start a new transaction when data is available and finalise a transaction
* which has been processed.
*
* @param connp
* @returns HTP_OK on state change, HTTP_ERROR on error, or HTP_DATA when more data is needed.
*/
int htp_connp_REQ_IDLE(htp_connp_t * connp) {
// If we're here and a transaction object exists that
// means we've just completed parsing a request. We need
// to run the final hook and start over.
if (connp->in_tx != NULL) {
// Run hook REQUEST
int rc = hook_run_all(connp->cfg->hook_request, connp);
if (rc != HOOK_OK) {
htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
"Request callback returned error (%d)", rc);
return HTP_ERROR;
}
// Start afresh
connp->in_tx = NULL;
}
// We want to start parsing the next request (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.
IN_TEST_NEXT_BYTE_OR_RETURN(connp);
// Detect pipelining
if (list_size(connp->conn->transactions) > connp->out_next_tx_index) {
connp->conn->flags |= PIPELINED_CONNECTION;
}
// Parsing a new request
connp->in_tx = htp_tx_create(connp->cfg, CFG_SHARED, connp->conn);
if (connp->in_tx == NULL) return HTP_ERROR;
connp->in_tx->connp = connp;
list_add(connp->conn->transactions, connp->in_tx);
connp->in_content_length = -1;
connp->in_body_data_left = -1;
connp->in_header_line_index = -1;
connp->in_header_line_counter = 0;
connp->in_chunk_request_index = connp->in_chunk_count;
// Run hook TRANSACTION_START
int rc = hook_run_all(connp->cfg->hook_transaction_start, connp);
if (rc != HOOK_OK) {
htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0,
"Transaction start callback returned error (%d)", rc);
return HTP_ERROR;
}
// Change state into request line parsing
connp->in_state = htp_connp_REQ_LINE;
connp->in_tx->progress = TX_PROGRESS_REQ_LINE;
return HTP_OK;
}
size_t htp_connp_req_data_consumed(htp_connp_t *connp) {
return connp->in_current_offset;
}
/**
* Process a chunk of inbound (client or request) data.
*
* @param connp
* @param timestamp
* @param data
* @param len
* @return HTP_OK on state change, HTTP_ERROR on error, or HTP_DATA when more data is needed.
*/
int htp_connp_req_data(htp_connp_t *connp, htp_time_t timestamp, unsigned char *data, size_t len) {
#ifdef HTP_DEBUG
fprintf(stderr, "htp_connp_req_data(connp->in_status %x)\n", connp->in_status);
fprint_raw_data(stderr, __FUNCTION__, data, len);
#endif
// Return if the connection has had a fatal error
if (connp->in_status == STREAM_STATE_ERROR) {
htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "Inbound parser is in STREAM_STATE_ERROR");
#ifdef HTP_DEBUG
fprintf(stderr, "htp_connp_req_data: returning STREAM_STATE_DATA (previous error)\n");
#endif
return STREAM_STATE_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->in_status != STREAM_STATE_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_req_data: returning STREAM_STATE_DATA (zero-length chunk)\n");
#endif
return STREAM_STATE_ERROR;
}
// Store the current chunk information
connp->in_timestamp = timestamp;
connp->in_current_data = data;
connp->in_current_len = len;
connp->in_current_offset = 0;
connp->in_chunk_count++;
connp->conn->in_data_counter += len;
connp->conn->in_packet_counter++;
// Return without processing any data if the stream is in tunneling
// mode (which it would be after an initial CONNECT transaction).
if (connp->in_status == STREAM_STATE_TUNNEL) {
#ifdef HTP_DEBUG
fprintf(stderr, "htp_connp_req_data: returning STREAM_STATE_DATA (tunnel)\n");
#endif
return STREAM_STATE_DATA;
}
// 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_req_data: in state=%s, progress=%s\n",
htp_connp_in_state_as_string(connp),
htp_tx_progress_as_string(connp->in_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.
int rc = connp->in_state(connp);
if (rc != HTP_OK) {
// Do we need more data?
if (rc == HTP_DATA) {
#ifdef HTP_DEBUG
fprintf(stderr, "htp_connp_req_data: returning STREAM_STATE_DATA\n");
#endif
return STREAM_STATE_DATA;
}
// Check for suspended parsing
if (rc == HTP_DATA_OTHER) {
// We might have actually consumed the entire data chunk?
if (connp->in_current_offset >= connp->in_current_len) {
// Do not send STREAM_DATE_DATA_OTHER if we've
// consumed the entire chunk
#ifdef HTP_DEBUG
fprintf(stderr, "htp_connp_req_data: returning STREAM_STATE_DATA (suspended parsing)\n");
#endif
return STREAM_STATE_DATA;
} else {
// Partial chunk consumption
#ifdef HTP_DEBUG
fprintf(stderr, "htp_connp_req_data: returning STREAM_STATE_DATA_OTHER\n");
#endif
return STREAM_STATE_DATA_OTHER;
}
}
// Remember that we've had an error. Errors are
// (at least at present) not possible to recover from.
connp->in_status = STREAM_STATE_ERROR;
#ifdef HTP_DEBUG
fprintf(stderr, "htp_connp_req_data: returning STREAM_STATE_ERROR (state response)\n");
#endif
return STREAM_STATE_ERROR;
}
}
}