/** * Parses request 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_REQ_HEADERS(htp_connp_t *connp) { for (;;) { IN_COPY_BYTE_OR_RETURN(connp); // Have we reached the end of the line? if (connp->in_next_byte == LF) { unsigned char *data; size_t len; htp_connp_req_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->in_header != NULL) { if (connp->cfg->process_request_header(connp, bstr_ptr(connp->in_header), bstr_len(connp->in_header)) != HTP_OK) return HTP_ERROR; bstr_free(connp->in_header); connp->in_header = NULL; } htp_connp_req_clear_buffer(connp); // We've seen all the request headers. return htp_tx_state_request_headers(connp->in_tx); } 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->in_header != NULL) { if (connp->cfg->process_request_header(connp, bstr_ptr(connp->in_header), bstr_len(connp->in_header)) != HTP_OK) return HTP_ERROR; bstr_free(connp->in_header); connp->in_header = NULL; } IN_PEEK_NEXT(connp); if (htp_is_folding_char(connp->in_next_byte) == 0) { // Because we know this header is not folded, we can process the buffer straight away. if (connp->cfg->process_request_header(connp, data, len) != HTP_OK) return HTP_ERROR; } else { // Keep the partial header data for parsing later. connp->in_header = bstr_dup_mem(data, len); if (connp->in_header == NULL) return HTP_ERROR; } } else { // Folding; check that there's a previous header line to add to. if (connp->in_header == NULL) { // Invalid folding. // Warn only once per transaction. 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"); } // Keep the header data for parsing later. connp->in_header = bstr_dup_mem(data, len); if (connp->in_header == NULL) return HTP_ERROR; } else { // Add to the existing header. bstr *new_in_header = bstr_add_mem(connp->in_header, data, len); if (new_in_header == NULL) return HTP_ERROR; connp->in_header = new_in_header; } } htp_connp_req_clear_buffer(connp); } } return HTP_ERROR; }
/** * 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; }
/** * 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); // Allocate structure to hold one header line 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 connp->in_tx->request_headers_sep = bstr_dup_mem((char *)connp->in_line, connp->in_line_len); if (connp->in_tx->request_headers_sep == NULL) { return HTP_ERROR; } // 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; // We've seen all request headers // Did this request arrive in multiple chunks? 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) { // Remember how many header lines there were before trailers connp->in_tx->request_header_lines_no_trailers = list_size(connp->in_tx->request_header_lines); // Run hook REQUEST_HEADERS_RAW //if (connp->cfg->hook_request_headers_raw != NULL) { // htp_req_run_hook_request_headers_raw(connp, 0, // connp->in_tx->request_header_lines_no_trailers); //} // Determine if this request has a body connp->in_state = htp_connp_REQ_CONNECT_CHECK; } else { // Run hook REQUEST_HEADERS_RAW //if ((connp->cfg->hook_request_headers_raw != NULL) // && (list_size(connp->in_tx->request_header_lines) > connp->in_tx->request_header_lines_no_trailers)) { // htp_req_run_hook_request_headers_raw(connp, // connp->in_tx->request_header_lines_no_trailers, // list_size(connp->in_tx->request_header_lines)); //} // Run hook REQUEST_TRAILER int rc = hook_run_all(connp->cfg->hook_request_trailer, 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; } } // 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 int chomp_result = 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_dup_mem((char *) connp->in_line, connp->in_line_len + chomp_result); if (connp->in_header_line->line == NULL) { return HTP_ERROR; } 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++; } } }
TEST(UtilTest, IsLineFolded) { EXPECT_EQ(-1, htp_connp_is_line_folded((unsigned char*)"", 0)); EXPECT_EQ(1, htp_connp_is_line_folded((unsigned char*)"\tline", 5)); EXPECT_EQ(1, htp_connp_is_line_folded((unsigned char*)" line", 5)); EXPECT_EQ(0, htp_connp_is_line_folded((unsigned char*)"line ", 5)); }
/** * Parses response headers. * * @param connp * @returns HTP_OK on state change, HTTP_ERROR on error, or HTP_DATA when more data is needed. */ int htp_connp_RES_HEADERS(htp_connp_t * connp) { for (;;) { OUT_COPY_BYTE_OR_RETURN(connp); if (connp->out_header_line == NULL) { connp->out_header_line = calloc(1, sizeof (htp_header_line_t)); if (connp->out_header_line == NULL) return HTP_ERROR; connp->out_header_line->first_nul_offset = -1; } // Keep track of NUL bytes if (connp->out_next_byte == 0) { // Store the offset of the first NUL if (connp->out_header_line->has_nulls == 0) { connp->out_header_line->first_nul_offset = connp->out_line_len; } // Remember how many NULs there were connp->out_header_line->flags |= HTP_FIELD_NUL_BYTE; connp->out_header_line->has_nulls++; } // Have we reached the end of the line? if (connp->out_next_byte == LF) { #ifdef HTP_DEBUG fprint_raw_data(stderr, __FUNCTION__, connp->out_line, connp->out_line_len); #endif // Should we terminate headers? if (htp_connp_is_line_terminator(connp, connp->out_line, connp->out_line_len)) { // Terminator line connp->out_tx->response_headers_sep = bstr_dup_mem((char *)connp->out_line, connp->out_line_len); if (connp->out_tx->response_headers_sep == NULL) { return HTP_ERROR; } // Parse previous header, if any if (connp->out_header_line_index != -1) { // Only try to parse a header, but ignore // any problems. That's what browsers do. connp->cfg->process_response_header(connp); // Reset index connp->out_header_line_index = -1; } // Cleanup free(connp->out_header_line); connp->out_line_len = 0; connp->out_header_line = NULL; // We've seen all response headers if (connp->out_tx->progress == TX_PROGRESS_RES_HEADERS) { // Determine if this response has a body connp->out_state = htp_connp_RES_BODY_DETERMINE; } else { // Run hook response_TRAILER int rc = hook_run_all(connp->cfg->hook_response_trailer, connp); if (rc != HOOK_OK) { htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "Response trailer callback returned error (%d)", rc); return HTP_ERROR; } // We've completed parsing this response connp->out_state = htp_connp_RES_IDLE; } return HTP_OK; } // Prepare line for consumption int chomp_result = htp_chomp(connp->out_line, &connp->out_line_len); // Check for header folding if (htp_connp_is_line_folded(connp->out_line, connp->out_line_len) == 0) { // New header line // Parse previous header, if any if (connp->out_header_line_index != -1) { // Only try to parse a header, but ignore // any problems. That's what browsers do. connp->cfg->process_response_header(connp); // Reset index connp->out_header_line_index = -1; } // Remember the index of the fist header line connp->out_header_line_index = connp->out_header_line_counter; } else { // Folding; check that there's a previous header line to add to if (connp->out_header_line_index == -1) { 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"); } } } // Add the raw header line to the list connp->out_header_line->line = bstr_dup_mem((char *) connp->out_line, connp->out_line_len + chomp_result); if (connp->out_header_line->line == NULL) { return HTP_ERROR; } list_add(connp->out_tx->response_header_lines, connp->out_header_line); connp->out_header_line = NULL; // Cleanup for the next line connp->out_line_len = 0; if (connp->out_header_line_index == -1) { connp->out_header_line_index = connp->out_header_line_counter; } connp->out_header_line_counter++; } } }
/** * 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; // 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[0] == 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[0] = TX_PROGRESS_WAIT; } return HTP_OK; } // Prepare line for consumption size_t raw_in_line_len = connp->in_line_len; 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 if (raw_in_line_len > connp->in_line_len) { if (raw_in_line_len - connp->in_line_len == 2 && connp->in_line[connp->in_line_len] == 0x0d && connp->in_line[connp->in_line_len + 1] == 0x0a) { connp->in_header_line->terminators = NULL; } else { connp->in_header_line->terminators = bstr_memdup((char *) connp->in_line + connp->in_line_len, raw_in_line_len - connp->in_line_len); if (connp->in_header_line->terminators == NULL) { return HTP_ERROR; } } } else { connp->in_header_line->terminators = NULL; } connp->in_header_line->line = bstr_memdup((char *) connp->in_line, connp->in_line_len); if (connp->in_header_line->line == NULL) { return HTP_ERROR; } 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++; } } return HTP_ERROR; }