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; }
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; }
/** * 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_get_c(connp->in_tx->request_headers, "content-length"); htp_header_t *te = table_get_c(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_cmp_c(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; } // Check for PUT requests, which we need to treat as file uploads if (connp->in_tx->request_method_number == M_PUT) { if (connp->in_tx->connp->in_tx->request_transfer_coding != 0) { // Prepare to treat PUT request body as a file connp->put_file = calloc(1, sizeof (htp_file_t)); if (connp->put_file == NULL) return HTP_ERROR; 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 = table_get_c(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 if (bstr_cmp_nocase(h->value, connp->in_tx->parsed_uri->hostname) != 0) { // The host information is different 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"); } } // Parse Content-Type htp_header_t *ct = table_get_c(connp->in_tx->request_headers, "content-type"); if (ct != NULL) { connp->in_tx->request_content_type = bstr_dup_lower(ct->value); if (connp->in_tx->request_content_type == NULL) { return HTP_ERROR; } // Ignore parameters char *data = bstr_ptr(connp->in_tx->request_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_util_adjust_len(connp->in_tx->request_content_type, newlen); break; } newlen++; } } // Parse cookies if (connp->cfg->parse_request_cookies) { htp_parse_cookies_v0(connp); } // Parse authentication information if (connp->cfg->parse_request_http_authentication) { htp_parse_authorization(connp); } // Run hook REQUEST_HEADERS int rc = hook_run_all(connp->cfg->hook_request_headers, connp); if (rc != HOOK_OK) { switch (rc) { case HOOK_STOP: return HTP_STOP; case HOOK_ERROR: case HOOK_DECLINED: default: htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "Request headers callback returned error (%d)", rc); return HTP_ERROR; } } return HTP_OK; }