htp_status_t htp_tx_state_request_line(htp_tx_t *tx) { if (tx == NULL) return HTP_ERROR; // Determine how to process the request URI. if (tx->request_method_number == HTP_M_CONNECT) { // When CONNECT is used, the request URI contains an authority string. if (htp_parse_uri_hostport(tx->connp, tx->request_uri, tx->parsed_uri_raw) != HTP_OK) { return HTP_ERROR; } } else { // Parse the request URI into htp_tx_t::parsed_uri_raw. if (htp_parse_uri(tx->request_uri, &(tx->parsed_uri_raw)) != HTP_OK) { return HTP_ERROR; } } // Build htp_tx_t::parsed_uri, but only if it was not explicitly set already. if (tx->parsed_uri == NULL) { tx->parsed_uri = htp_uri_alloc(); if (tx->parsed_uri == NULL) return HTP_ERROR; // Keep the original URI components, but create a copy which we can normalize and use internally. if (htp_normalize_parsed_uri(tx->connp, tx->parsed_uri_raw, tx->parsed_uri) != HTP_OK) { return HTP_ERROR; } } // Check parsed_uri hostname. if (tx->parsed_uri->hostname != NULL) { if (htp_validate_hostname(tx->parsed_uri->hostname) == 0) { tx->flags |= HTP_HOSTU_INVALID; } } // Run hook REQUEST_URI_NORMALIZE. htp_status_t rc = htp_hook_run_all(tx->connp->cfg->hook_request_uri_normalize, tx); if (rc != HTP_OK) return rc; // Run hook REQUEST_LINE. rc = htp_hook_run_all(tx->connp->cfg->hook_request_line, tx); if (rc != HTP_OK) return rc; // Move on to the next phase. tx->connp->in_state = htp_connp_REQ_PROTOCOL; return HTP_OK; }
htp_status_t htp_tx_state_response_line(htp_tx_t *tx) { if (tx == NULL) return HTP_ERROR; #if 0 // Commented-out until we determine which fields can be // unavailable in real-life. // Unless we're dealing with HTTP/0.9, check that // the minimum amount of data has been provided. if (tx->is_protocol_0_9 != 0) { if ((tx->response_protocol == NULL) || (tx->response_status_number == -1) || (tx->response_message == NULL)) { return HTP_ERROR; } } #endif // Is the response line valid? if ((tx->response_protocol_number == HTP_PROTOCOL_INVALID) || (tx->response_status_number == HTP_STATUS_INVALID) || (tx->response_status_number < HTP_VALID_STATUS_MIN) || (tx->response_status_number > HTP_VALID_STATUS_MAX)) { htp_log(tx->connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Invalid response line."); tx->flags |= HTP_STATUS_LINE_INVALID; } // Run hook HTP_RESPONSE_LINE htp_status_t rc = htp_hook_run_all(tx->connp->cfg->hook_response_line, tx); if (rc != HTP_OK) return rc; return HTP_OK; }
htp_status_t htp_tx_state_request_headers(htp_tx_t *tx) { // Did this request arrive in multiple chunks? if (tx->connp->in_chunk_count != tx->connp->in_chunk_request_index) { tx->flags |= HTP_MULTI_PACKET_HEAD; } // If we're in TX_PROGRESS_REQ_HEADERS that means that this is the // first time we're processing headers in a request. Otherwise, // we're dealing with trailing headers. if (tx->progress > HTP_REQUEST_HEADERS) { // Run hook HTP_REQUEST_TRAILER int rc = htp_hook_run_all(tx->connp->cfg->hook_request_trailer, tx->connp); if (rc != HTP_OK) return rc; // Completed parsing this request; finalize it now. tx->connp->in_state = htp_connp_REQ_FINALIZE; } else if (tx->progress >= HTP_REQUEST_LINE) { // Process request headers int rc = htp_tx_process_request_headers(tx); if (rc != HTP_OK) return rc; tx->connp->in_state = htp_connp_REQ_CONNECT_CHECK; } else { htp_log(tx->connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "[Internal Error] Invalid tx progress: %d", tx->progress); return HTP_ERROR; } return HTP_OK; }
htp_status_t htp_tx_state_request_complete(htp_tx_t *tx) { // Finalize request body. if (htp_tx_req_has_body(tx)) { int rc = htp_tx_req_process_body_data(tx, NULL, 0); if (rc != HTP_OK) return rc; } // Run hook REQUEST_COMPLETE. int rc = htp_hook_run_all(tx->connp->cfg->hook_request_complete, tx->connp); if (rc != HTP_OK) return rc; // Clean-up. if (tx->connp->put_file != NULL) { bstr_free(tx->connp->put_file->filename); free(tx->connp->put_file); tx->connp->put_file = NULL; } // Update the transaction status, but only if it did already // move on. This may happen when we're processing a CONNECT // request and need to wait for the response to determine how // to continue to treat the rest of the TCP stream. if (tx->progress < HTP_REQUEST_COMPLETE) { tx->progress = HTP_REQUEST_COMPLETE; } return HTP_OK; }
htp_status_t htp_tx_state_response_complete_ex(htp_tx_t *tx, int hybrid_mode) { if (tx == NULL) return HTP_ERROR; if (tx->response_progress != HTP_RESPONSE_COMPLETE) { tx->response_progress = HTP_RESPONSE_COMPLETE; // Run the last RESPONSE_BODY_DATA HOOK, but only if there was a response body present. if (tx->response_transfer_coding != HTP_CODING_NO_BODY) { htp_tx_res_process_body_data_ex(tx, NULL, 0); } // Run hook RESPONSE_COMPLETE. htp_status_t rc = htp_hook_run_all(tx->connp->cfg->hook_response_complete, tx); if (rc != HTP_OK) return rc; } if (!hybrid_mode) { // Check if the inbound parser is waiting on us. If it is, that means that // there might be request data that the inbound parser hasn't consumed yet. // If we don't stop parsing we might encounter a response without a request, // which is why we want to return straight away before processing any data. // // This situation will occur any time the parser needs to see the server // respond to a particular situation before it can decide how to proceed. For // example, when a CONNECT is sent, different paths are used when it is accepted // and when it is not accepted. // // It is not enough to check only in_status here. Because of pipelining, it's possible // that many inbound transactions have been processed, and that the parser is // waiting on a response that we have not seen yet. if ((tx->connp->in_status == HTP_STREAM_DATA_OTHER) && (tx->connp->in_tx == tx->connp->out_tx)) { return HTP_DATA_OTHER; } // Do we have a signal to yield to inbound processing at // the end of the next transaction? if (tx->connp->out_data_other_at_tx_end) { // We do. Let's yield then. tx->connp->out_data_other_at_tx_end = 0; return HTP_DATA_OTHER; } } // Make a copy of the connection parser pointer, so that // we don't have to reference it via tx, which may be destroyed later. htp_connp_t *connp = tx->connp; // Finalize the transaction. This may call may destroy the transaction, if auto-destroy is enabled. htp_status_t rc = htp_tx_finalize(tx); if (rc != HTP_OK) return rc; // Disconnect transaction from the parser. connp->out_tx = NULL; connp->out_state = htp_connp_RES_IDLE; return HTP_OK; }
htp_status_t htp_tx_state_response_headers(htp_tx_t *tx) { if (tx == NULL) return HTP_ERROR; // Check for compression. // Determine content encoding. tx->response_content_encoding = HTP_COMPRESSION_NONE; htp_header_t *ce = htp_table_get_c(tx->response_headers, "content-encoding"); if (ce != NULL) { if ((bstr_cmp_c(ce->value, "gzip") == 0) || (bstr_cmp_c(ce->value, "x-gzip") == 0)) { tx->response_content_encoding = HTP_COMPRESSION_GZIP; } else if ((bstr_cmp_c(ce->value, "deflate") == 0) || (bstr_cmp_c(ce->value, "x-deflate") == 0)) { tx->response_content_encoding = HTP_COMPRESSION_DEFLATE; } } // Configure decompression, if enabled in the configuration. if (tx->connp->cfg->response_decompression_enabled) { tx->response_content_encoding_processing = tx->response_content_encoding; } else { tx->response_content_encoding_processing = HTP_COMPRESSION_NONE; } // Finalize sending raw header data. htp_status_t rc = htp_connp_res_receiver_finalize_clear(tx->connp); if (rc != HTP_OK) return rc; // Run hook RESPONSE_HEADERS. rc = htp_hook_run_all(tx->connp->cfg->hook_response_headers, tx); if (rc != HTP_OK) return rc; // Initialize the decompression engine as necessary. We can deal with three // scenarios: // // 1. Decompression is enabled, compression indicated in headers, and we decompress. // // 2. As above, but the user disables decompression by setting response_content_encoding // to COMPRESSION_NONE. // // 3. Decompression is disabled and we do not attempt to enable it, but the user // forces decompression by setting response_content_encoding to one of the // supported algorithms. if ((tx->response_content_encoding_processing == HTP_COMPRESSION_GZIP) || (tx->response_content_encoding_processing == HTP_COMPRESSION_DEFLATE)) { if (tx->connp->out_decompressor != NULL) { tx->connp->out_decompressor->destroy(tx->connp->out_decompressor); tx->connp->out_decompressor = NULL; } tx->connp->out_decompressor = (htp_decompressor_t *) htp_gzip_decompressor_create(tx->connp, tx->response_content_encoding_processing); if (tx->connp->out_decompressor == NULL) return HTP_ERROR; tx->connp->out_decompressor->callback = htp_tx_res_process_body_data_decompressor_callback; } else if (tx->response_content_encoding_processing != HTP_COMPRESSION_NONE) { return HTP_ERROR; } return HTP_OK; }
htp_status_t htp_tx_state_request_start(htp_tx_t *tx) { // Run hook REQUEST_START. int rc = htp_hook_run_all(tx->connp->cfg->hook_request_start, tx->connp); if (rc != HTP_OK) return rc; // Change state into request line parsing. tx->connp->in_state = htp_connp_REQ_LINE; tx->connp->in_tx->progress = HTP_REQUEST_LINE; return HTP_OK; }
htp_status_t htp_tx_state_response_complete(htp_tx_t *tx) { if (tx->connp->out_tx->progress != HTP_RESPONSE_COMPLETE) { tx->progress = HTP_RESPONSE_COMPLETE; // Run the last RESPONSE_BODY_DATA HOOK, but only if there was a response body present. if (tx->response_transfer_coding != HTP_CODING_NO_BODY) { htp_tx_res_process_body_data(tx, NULL, 0); } // Run hook RESPONSE_COMPLETE. return htp_hook_run_all(tx->connp->cfg->hook_response_complete, tx->connp); } return HTP_OK; }
/** * 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_req_receiver_send_data(htp_connp_t *connp, int is_last) { if (connp->in_data_receiver_hook == NULL) return HTP_OK; htp_tx_data_t d; d.tx = connp->in_tx; d.data = connp->in_current_data + connp->in_current_receiver_offset; d.len = connp->in_current_read_offset - connp->in_current_receiver_offset; d.is_last = is_last; htp_status_t rc = htp_hook_run_all(connp->in_data_receiver_hook, &d); if (rc != HTP_OK) return rc; connp->in_current_receiver_offset = connp->in_current_read_offset; return HTP_OK; }
htp_status_t htp_tx_finalize(htp_tx_t *tx) { if (tx == NULL) return HTP_ERROR; if (!htp_tx_is_complete(tx)) return HTP_OK; // Run hook TRANSACTION_COMPLETE. htp_status_t rc = htp_hook_run_all(tx->connp->cfg->hook_transaction_complete, tx); if (rc != HTP_OK) return rc; // In streaming processing, we destroy the transaction because it will not be needed any more. if (tx->connp->cfg->tx_auto_destroy) { htp_tx_destroy(tx); } return HTP_OK; }
htp_status_t htp_tx_state_response_start(htp_tx_t *tx) { tx->connp->out_tx = tx; // Run hook RESPONSE_START. int rc = htp_hook_run_all(tx->connp->cfg->hook_response_start, tx->connp); if (rc != HTP_OK) return rc; // Change state into response line parsing, except if we're following // a HTTP/0.9 request (no status line or response headers). if (tx->is_protocol_0_9) { tx->response_transfer_coding = HTP_CODING_IDENTITY; tx->response_content_encoding = HTP_COMPRESSION_NONE; tx->progress = HTP_RESPONSE_BODY; tx->connp->out_state = htp_connp_RES_BODY_IDENTITY; } else { tx->connp->out_state = htp_connp_RES_LINE; tx->progress = HTP_RESPONSE_LINE; } return HTP_OK; }
htp_status_t htp_tx_state_request_complete_partial(htp_tx_t *tx) { if (tx == NULL) return HTP_ERROR; // Finalize request body. if (htp_tx_req_has_body(tx)) { htp_status_t rc = htp_tx_req_process_body_data_ex(tx, NULL, 0); if (rc != HTP_OK) return rc; } tx->request_progress = HTP_REQUEST_COMPLETE; // Run hook REQUEST_COMPLETE. htp_status_t rc = htp_hook_run_all(tx->connp->cfg->hook_request_complete, tx); if (rc != HTP_OK) return rc; // Clean-up. if (tx->connp->put_file != NULL) { bstr_free(tx->connp->put_file->filename); free(tx->connp->put_file); tx->connp->put_file = NULL; } 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) { for (;;) { OUT_COPY_BYTE_OR_RETURN(connp); // Have we reached the end of the line? if (connp->out_next_byte == LF) { unsigned char *data; size_t len; htp_connp_res_consolidate_data(connp, &data, &len); #ifdef HTP_DEBUG fprint_raw_data(stderr, __FUNCTION__, data, len); #endif // Should we terminate headers? if (htp_connp_is_line_terminator(connp, data, len)) { // 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->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); 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. connp->out_header = bstr_dup_mem(data, len); if (connp->out_header == NULL) return HTP_ERROR; } else { // Add to the existing header. 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; } } htp_connp_res_clear_buffer(connp); } } return HTP_ERROR; }
static htp_status_t htp_tx_process_request_headers(htp_tx_t *tx) { if (tx == NULL) return HTP_ERROR; // Determine if we have a request body, and how it is packaged. htp_status_t rc = HTP_OK; htp_header_t *cl = htp_table_get_c(tx->request_headers, "content-length"); htp_header_t *te = htp_table_get_c(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. // TODO The HTTP/1.1 RFC also allows the T-E header to contain "identity", which // presumably should have the same effect as T-E header absence. However, Apache // (2.2.22 on Ubuntu 12.04 LTS) instead errors out with "Unknown Transfer-Encoding: identity". // And it behaves strangely, too, sending a 501 and proceeding to process the request // (e.g., PHP is run), but without the body. It then closes the connection. if (bstr_cmp_c(te->value, "chunked") != 0) { // Invalid T-E header value. tx->request_transfer_coding = HTP_CODING_INVALID; tx->flags |= HTP_REQUEST_INVALID_T_E; tx->flags |= HTP_REQUEST_INVALID; } else { // Chunked encoding is a HTTP/1.1 feature, so check that an earlier protocol // version 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. This should be a // personality trait. if (tx->request_protocol_number < HTP_PROTOCOL_1_1) { tx->flags |= HTP_REQUEST_INVALID_T_E; tx->flags |= HTP_REQUEST_SMUGGLING; } // If the T-E header is present we are going to use it. tx->request_transfer_coding = HTP_CODING_CHUNKED; // We are still going to check for the presence of C-L. if (cl != NULL) { // According to the HTTP/1.1 RFC (section 4.4): // // "The Content-Length header field MUST NOT be sent // if these two lengths are different (i.e., if a Transfer-Encoding // header field is present). If a message is received with both a // Transfer-Encoding header field and a Content-Length header field, // the latter MUST be ignored." // tx->flags |= HTP_REQUEST_SMUGGLING; } } } else if (cl != NULL) { // Check for a folded C-L header. if (cl->flags & HTP_FIELD_FOLDED) { tx->flags |= HTP_REQUEST_SMUGGLING; } // Check for multiple C-L headers. if (cl->flags & HTP_FIELD_REPEATED) { tx->flags |= HTP_REQUEST_SMUGGLING; // TODO Personality trait to determine which C-L header to parse. // At the moment we're parsing the combination of all instances, // which is bound to fail (because it will contain commas). } // Get the body length. tx->request_content_length = htp_parse_content_length(cl->value); if (tx->request_content_length < 0) { tx->request_transfer_coding = HTP_CODING_INVALID; tx->flags |= HTP_REQUEST_INVALID_C_L; tx->flags |= HTP_REQUEST_INVALID; } else { // We have a request body of known length. tx->request_transfer_coding = HTP_CODING_IDENTITY; } } else { // No body. tx->request_transfer_coding = HTP_CODING_NO_BODY; } // If we could not determine the correct body handling, // consider the request invalid. if (tx->request_transfer_coding == HTP_CODING_UNKNOWN) { tx->request_transfer_coding = HTP_CODING_INVALID; tx->flags |= HTP_REQUEST_INVALID; } // Check for PUT requests, which we need to treat as file uploads. if (tx->request_method_number == HTP_M_PUT) { if (htp_tx_req_has_body(tx)) { // Prepare to treat PUT request body as a file. tx->connp->put_file = calloc(1, sizeof (htp_file_t)); if (tx->connp->put_file == NULL) return HTP_ERROR; tx->connp->put_file->source = HTP_FILE_PUT; } else { // TODO Warn about PUT request without a body. } return HTP_OK; } // Determine hostname. // Use the hostname from the URI, when available. if (tx->parsed_uri->hostname != NULL) { tx->request_hostname = bstr_dup(tx->parsed_uri->hostname); if (tx->request_hostname == NULL) return HTP_ERROR; } tx->request_port_number = tx->parsed_uri->port_number; // Examine the Host header. htp_header_t *h = htp_table_get_c(tx->request_headers, "host"); if (h == NULL) { // No host information in the headers. // HTTP/1.1 requires host information in the headers. if (tx->request_protocol_number >= HTP_PROTOCOL_1_1) { tx->flags |= HTP_HOST_MISSING; } } else { // Host information available in the headers. bstr *hostname; int port; rc = htp_parse_header_hostport(h->value, &hostname, &port, &(tx->flags)); if (rc != HTP_OK) return rc; // Is there host information in the URI? if (tx->request_hostname == NULL) { // There is no host information in the URI. Place the // hostname from the headers into the parsed_uri structure. tx->request_hostname = hostname; tx->request_port_number = port; } else { // The host information appears in the URI and in the headers. It's // OK if both have the same thing, but we want to check for differences. if ((bstr_cmp_nocase(hostname, tx->request_hostname) != 0) || (port != tx->request_port_number)) { // The host information is different in the headers and the URI. The // HTTP RFC states that we should ignore the header copy. tx->flags |= HTP_HOST_AMBIGUOUS; } bstr_free(hostname); } } // Determine Content-Type. htp_header_t *ct = htp_table_get_c(tx->request_headers, "content-type"); if (ct != NULL) { rc = htp_parse_ct_header(ct->value, &tx->request_content_type); if (rc != HTP_OK) return rc; } // Parse cookies. if (tx->connp->cfg->parse_request_cookies) { rc = htp_parse_cookies_v0(tx->connp); if (rc != HTP_OK) return rc; } // Parse authentication information. if (tx->connp->cfg->parse_request_auth) { rc = htp_parse_authorization(tx->connp); if (rc == HTP_DECLINED) { // Don't fail the stream if an authorization header is invalid, just set a flag. tx->flags |= HTP_AUTH_INVALID; } else { if (rc != HTP_OK) return rc; } } // Finalize sending raw header data. rc = htp_connp_req_receiver_finalize_clear(tx->connp); if (rc != HTP_OK) return rc; // Run hook REQUEST_HEADERS. rc = htp_hook_run_all(tx->connp->cfg->hook_request_headers, tx); if (rc != HTP_OK) return rc; // We cannot proceed if the request is invalid. if (tx->flags & HTP_REQUEST_INVALID) { return HTP_ERROR; } return HTP_OK; }
htp_status_t htp_tx_state_request_line(htp_tx_t *tx) { htp_connp_t *connp = tx->connp; if (connp->in_tx->request_method_number == HTP_M_CONNECT) { // Parse authority if (htp_parse_uri_hostport(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) != HTP_OK) { // Note: downstream responsible for error logging. return HTP_ERROR; } // Run hook REQUEST_URI_NORMALIZE. int rc = htp_hook_run_all(connp->cfg->hook_request_uri_normalize, connp); if (rc != HTP_OK) return rc; // 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) 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_cmp_c(connp->in_tx->parsed_uri->scheme, "http") != 0) { // TODO Invalid scheme. } } else { connp->in_tx->parsed_uri->scheme = bstr_dup_c("http"); if (connp->in_tx->parsed_uri->scheme == NULL) { return HTP_ERROR; } } // Path. if (connp->in_tx->parsed_uri->path == NULL) { connp->in_tx->parsed_uri->path = bstr_dup_c("/"); if (connp->in_tx->parsed_uri->path == NULL) { return HTP_ERROR; } } } // Run hook REQUEST_LINE. int rc = htp_hook_run_all(connp->cfg->hook_request_line, connp); if (rc != HTP_OK) return rc; // Move on to the next phase. connp->in_state = htp_connp_REQ_PROTOCOL; return HTP_OK; }
static htp_status_t htp_tx_process_request_headers(htp_tx_t *tx) { // Remember how many header lines there were before trailers. tx->request_header_lines_no_trailers = htp_list_size(tx->request_header_lines); // Determine if we have a request body, and how it is packaged. htp_header_t *cl = htp_table_get_c(tx->request_headers, "content-length"); htp_header_t *te = htp_table_get_c(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_cmp_c(te->value, "chunked") != 0) { // Invalid T-E header value. tx->flags |= HTP_INVALID_CHUNKING; htp_log(tx->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 (tx->request_protocol_number < HTP_PROTOCOL_1_1) { tx->flags |= HTP_INVALID_CHUNKING; } // If the T-E header is present we are going to use it. tx->request_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. tx->flags |= HTP_REQUEST_SMUGGLING; } } else if (cl != NULL) { // We have a request body of known length. tx->request_transfer_coding = HTP_CODING_IDENTITY; // Check for a folded C-L header. if (cl->flags & HTP_FIELD_FOLDED) { tx->flags |= HTP_REQUEST_SMUGGLING; } // Check for multiple C-L headers. if (cl->flags & HTP_FIELD_REPEATED) { tx->flags |= HTP_REQUEST_SMUGGLING; } // Get body length. tx->request_content_length = htp_parse_content_length(cl->value); if (tx->request_content_length < 0) { htp_log(tx->connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "Invalid C-L field in request"); return HTP_ERROR; } } else { // No body. tx->request_transfer_coding = HTP_CODING_NO_BODY; } // Check for PUT requests, which we need to treat as file uploads. if (tx->request_method_number == HTP_M_PUT) { if (htp_tx_req_has_body(tx)) { // Prepare to treat PUT request body as a file. tx->connp->put_file = calloc(1, sizeof (htp_file_t)); if (tx->connp->put_file == NULL) return HTP_ERROR; tx->connp->put_file->source = HTP_FILE_PUT; } else { // TODO Warn about PUT request without a body. } return HTP_OK; } // Host resolution htp_header_t *h = htp_table_get_c(tx->request_headers, "host"); if (h == NULL) { // No host information in the headers. // HTTP/1.1 requires host information in the headers. if (tx->request_protocol_number >= HTP_PROTOCOL_1_1) { tx->flags |= HTP_HOST_MISSING; htp_log(tx->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. bstr *hostname; int port; if (htp_parse_hostport(h->value, &hostname, &port, &(tx->flags)) != HTP_OK) return HTP_ERROR; // Is there host information in the URI? if (tx->parsed_uri->hostname == NULL) { // There is no host information in the URI. Place the // hostname from the headers into the parsed_uri structure. tx->parsed_uri->hostname = hostname; tx->parsed_uri->port_number = port; } else { if ((bstr_cmp_nocase(hostname, tx->parsed_uri->hostname) != 0) || (port != tx->parsed_uri->port_number)) { // The host information is different in the // headers and the URI. The HTTP RFC states that // we should ignore the header copy. tx->flags |= HTP_HOST_AMBIGUOUS; htp_log(tx->connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Host information ambiguous"); } bstr_free(hostname); } } // Parse the Content-Type header. htp_header_t *ct = htp_table_get_c(tx->request_headers, "content-type"); if (ct != NULL) { if (htp_parse_ct_header(ct->value, &tx->request_content_type) != HTP_OK) return HTP_ERROR; } // Parse cookies. if (tx->connp->cfg->parse_request_cookies) { htp_parse_cookies_v0(tx->connp); } // Parse authentication information. if (tx->connp->cfg->parse_request_http_authentication) { htp_parse_authorization(tx->connp); } // Run hook REQUEST_HEADERS. int rc = htp_hook_run_all(tx->connp->cfg->hook_request_headers, tx->connp); if (rc != HTP_OK) return rc; return HTP_OK; }