TEST(UtilTest, Chomp) { char data[100]; size_t len; int result; strcpy(data, "test\r\n"); len = strlen(data); result = htp_chomp((unsigned char*)data, &len); EXPECT_EQ(2, result); EXPECT_EQ(4, len); strcpy(data, "foo\n"); len = strlen(data); result = htp_chomp((unsigned char*)data, &len); EXPECT_EQ(1, result); EXPECT_EQ(3, len); strcpy(data, "arfarf"); len = strlen(data); result = htp_chomp((unsigned char*)data, &len); EXPECT_EQ(0, result); EXPECT_EQ(6, len); }
/** * Parses request line. * * @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_LINE(htp_connp_t *connp) { for (;;) { // Get one byte 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 // Is this a line that should be ignored? if (htp_connp_is_line_ignorable(connp, data, 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? htp_connp_req_clear_buffer(connp); return HTP_OK; } // Process request line. htp_chomp(data, &len); connp->in_tx->request_line = bstr_dup_mem(data, len); if (connp->in_tx->request_line == NULL) return HTP_ERROR; if (connp->cfg->parse_request_line(connp) != HTP_OK) return HTP_ERROR; // Finalize request line parsing. if (htp_tx_state_request_line(connp->in_tx) != HTP_OK) return HTP_ERROR; htp_connp_req_clear_buffer(connp); return HTP_OK; } } return HTP_ERROR; }
/** * Extracts chunk length. * * @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_CHUNKED_LENGTH(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; if (htp_connp_res_consolidate_data(connp, &data, &len) != HTP_OK) { return HTP_ERROR; } connp->out_tx->response_message_len += len; #ifdef HTP_DEBUG fprint_raw_data(stderr, "Chunk length line", data, len); #endif htp_chomp(data, &len); connp->out_chunked_length = htp_parse_chunked_length(data, len); htp_connp_res_clear_buffer(connp); // Handle chunk length if (connp->out_chunked_length > 0) { // More data available connp->out_state = htp_connp_RES_BODY_CHUNKED_DATA; } else if (connp->out_chunked_length == 0) { // End of data connp->out_state = htp_connp_RES_HEADERS; connp->out_tx->response_progress = HTP_RESPONSE_TRAILER; } else { // Invalid chunk length htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "Response chunk encoding: Invalid chunk length: %d", connp->out_chunked_length); return HTP_ERROR; } return HTP_OK; } } return HTP_ERROR; }
/** * Extracts chunk length. * * @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_BODY_CHUNKED_LENGTH(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); connp->in_tx->request_message_len += len; #ifdef HTP_DEBUG fprint_raw_data(stderr, "Chunk length line", data, len); #endif htp_chomp(data, &len); connp->in_chunked_length = htp_parse_chunked_length(data, len); htp_connp_req_clear_buffer(connp); // Handle chunk length. if (connp->in_chunked_length > 0) { // More data available. // TODO Add a check (flag) for excessive 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->request_progress = HTP_REQUEST_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; } } return HTP_ERROR; }
/** * Parse the request line. * * @param[in] connp * @returns HTP_OK on succesful parse, HTP_ERROR on error. */ htp_status_t htp_connp_REQ_LINE_complete(htp_connp_t *connp) { unsigned char *data; size_t len; if (htp_connp_req_consolidate_data(connp, &data, &len) != HTP_OK) { return HTP_ERROR; } #ifdef HTP_DEBUG fprint_raw_data(stderr, __FUNCTION__, data, len); #endif // Is this a line that should be ignored? if (htp_connp_is_line_ignorable(connp, data, len)) { // We have an empty/whitespace line, which we'll note, ignore and move on. connp->in_tx->request_ignored_lines++; htp_connp_req_clear_buffer(connp); return HTP_OK; } // Process request line. htp_chomp(data, &len); connp->in_tx->request_line = bstr_dup_mem(data, len); if (connp->in_tx->request_line == NULL) return HTP_ERROR; if (connp->cfg->parse_request_line(connp) != HTP_OK) return HTP_ERROR; // Finalize request line parsing. if (htp_tx_state_request_line(connp->in_tx) != HTP_OK) return HTP_ERROR; htp_connp_req_clear_buffer(connp); 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[0] = 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; } } return HTP_ERROR; }
/** * Generic response header parser. * * @param[in] connp * @param[in] h * @param[in] data * @param[in] len * @return HTP status */ htp_status_t htp_parse_response_header_generic(htp_connp_t *connp, htp_header_t *h, unsigned char *data, size_t len) { size_t name_start, name_end; size_t value_start, value_end; size_t prev; htp_chomp(data, &len); name_start = 0; // Look for the first colon. size_t colon_pos = 0; while ((colon_pos < len) && (data[colon_pos] != ':')) colon_pos++; if (colon_pos == len) { // Header line with a missing colon. h->flags |= HTP_FIELD_UNPARSEABLE; h->flags |= HTP_FIELD_INVALID; if (!(connp->out_tx->flags & HTP_FIELD_UNPARSEABLE)) { // Only once per transaction. connp->out_tx->flags |= HTP_FIELD_UNPARSEABLE; connp->out_tx->flags |= HTP_FIELD_INVALID; htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Response field invalid: missing colon."); } // Reset the position. We're going to treat this invalid header // as a header with an empty name. That will increase the probability // that the content will be inspected. colon_pos = 0; name_end = 0; value_start = 0; } else { // Header line with a colon. if (colon_pos == 0) { // Empty header name. h->flags |= HTP_FIELD_INVALID; if (!(connp->out_tx->flags & HTP_FIELD_INVALID)) { // Only once per transaction. connp->out_tx->flags |= HTP_FIELD_INVALID; htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Response field invalid: empty name."); } } name_end = colon_pos; // Ignore LWS after field-name. prev = name_end; while ((prev > name_start) && (htp_is_lws(data[prev - 1]))) { prev--; name_end--; h->flags |= HTP_FIELD_INVALID; if (!(connp->out_tx->flags & HTP_FIELD_INVALID)) { // Only once per transaction. connp->out_tx->flags |= HTP_FIELD_INVALID; htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Response field invalid: LWS after name."); } } value_start = colon_pos + 1; } // Header value. // Ignore LWS before field-content. while ((value_start < len) && (htp_is_lws(data[value_start]))) { value_start++; } // Look for the end of field-content. value_end = len; // Check that the header name is a token. size_t i = name_start; while (i < name_end) { if (!htp_is_token(data[i])) { h->flags |= HTP_FIELD_INVALID; if (!(connp->out_tx->flags & HTP_FIELD_INVALID)) { connp->out_tx->flags |= HTP_FIELD_INVALID; htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Response header name is not a token."); } break; } i++; } // Now extract the name and the value. h->name = bstr_dup_mem(data + name_start, name_end - name_start); h->value = bstr_dup_mem(data + value_start, value_end - value_start); if ((h->name == NULL) || (h->value == NULL)) { bstr_free(h->name); bstr_free(h->value); return HTP_ERROR; } return HTP_OK; }
/** * 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 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; }
/** * Parses response line. * * @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_LINE(htp_connp_t *connp) { for (;;) { // Get one byte 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 // Is this a line that should be ignored? if (htp_connp_is_line_ignorable(connp, data, len)) { // We have an empty/whitespace line, which we'll note, ignore and move on connp->out_tx->response_ignored_lines++; // TODO How many lines are we willing to accept? // Start again htp_connp_res_clear_buffer(connp); return HTP_OK; } // Deallocate previous response line allocations, which we would have on a 100 response. if (connp->out_tx->response_line != NULL) { bstr_free(connp->out_tx->response_line); connp->out_tx->response_line = NULL; } if (connp->out_tx->response_protocol != NULL) { bstr_free(connp->out_tx->response_protocol); connp->out_tx->response_protocol = NULL; } if (connp->out_tx->response_status != NULL) { bstr_free(connp->out_tx->response_status); connp->out_tx->response_status = NULL; } if (connp->out_tx->response_message != NULL) { bstr_free(connp->out_tx->response_message); connp->out_tx->response_message = NULL; } // Process response line. int chomp_result = htp_chomp(data, &len); connp->out_tx->response_line = bstr_dup_mem(data, len); if (connp->out_tx->response_line == NULL) return HTP_ERROR; if (connp->cfg->parse_response_line(connp) != HTP_OK) return HTP_ERROR; // If the response line is invalid, determine if it _looks_ like // a response line. If it does not look like a line, process the // data as a response body because that is what browsers do. if (htp_treat_response_line_as_body(connp->out_tx)) { connp->out_tx->response_content_encoding_processing = HTP_COMPRESSION_NONE; int rc = htp_tx_res_process_body_data(connp->out_tx, data, len + chomp_result); if (rc != HTP_OK) return rc; // Continue to process response body. Because we don't have // any headers to parse, we assume the body continues until // the end of the stream. connp->out_tx->response_transfer_coding = HTP_CODING_IDENTITY; connp->out_tx->progress = HTP_RESPONSE_BODY; connp->out_state = htp_connp_RES_BODY_IDENTITY_STREAM_CLOSE; connp->out_body_data_left = -1; return HTP_OK; } int rc = htp_tx_state_response_line(connp->out_tx); if (rc != HTP_OK) return rc; htp_connp_res_clear_buffer(connp); // Move on to the next phase. connp->out_state = htp_connp_RES_HEADERS; connp->out_tx->progress = HTP_RESPONSE_HEADERS; return HTP_OK; } } 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 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++; } } }
/** * Generic request header parser. * * @param[in] connp * @param[in] h * @param[in] data * @param[in] len * @return HTP_OK or HTP_ERROR */ htp_status_t htp_parse_request_header_generic(htp_connp_t *connp, htp_header_t *h, unsigned char *data, size_t len) { size_t name_start, name_end; size_t value_start, value_end; htp_chomp(data, &len); name_start = 0; // Look for the colon. size_t colon_pos = 0; while ((colon_pos < len) && (data[colon_pos] != '\0') && (data[colon_pos] != ':')) colon_pos++; if ((colon_pos == len) || (data[colon_pos] == '\0')) { // Missing colon. h->flags |= HTP_FIELD_UNPARSEABLE; // Log only once per transaction. if (!(connp->in_tx->flags & HTP_FIELD_UNPARSEABLE)) { connp->in_tx->flags |= HTP_FIELD_UNPARSEABLE; htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Request field invalid: colon missing"); } // We handle this case as a header with an empty name, with the value equal // to the entire input string. // TODO Apache will respond to this problem with a 400. // Now extract the name and the value h->name = bstr_dup_c(""); if (h->name == NULL) return HTP_ERROR; h->value = bstr_dup_mem(data, len); if (h->value == NULL) { bstr_free(h->name); return HTP_ERROR; } return HTP_OK; } if (colon_pos == 0) { // Empty header name. h->flags |= HTP_FIELD_INVALID; // Log only once per transaction. if (!(connp->in_tx->flags & HTP_FIELD_INVALID)) { connp->in_tx->flags |= HTP_FIELD_INVALID; htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Request field invalid: empty name"); } } name_end = colon_pos; // Ignore LWS after field-name. size_t prev = name_end; while ((prev > name_start) && (htp_is_lws(data[prev - 1]))) { // LWS after header name. prev--; name_end--; h->flags |= HTP_FIELD_INVALID; // Log only once per transaction. if (!(connp->in_tx->flags & HTP_FIELD_INVALID)) { connp->in_tx->flags |= HTP_FIELD_INVALID; htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Request field invalid: LWS after name"); } } // Header value. value_start = colon_pos; // Go over the colon. if (value_start < len) { value_start++; } // Ignore LWS before field-content. while ((value_start < len) && (htp_is_lws(data[value_start]))) { value_start++; } // Look for the end of field-content. value_end = value_start; while ((value_end < len) && (data[value_end] != '\0')) value_end++; // Ignore LWS after field-content. prev = value_end - 1; while ((prev > value_start) && (htp_is_lws(data[prev]))) { prev--; value_end--; } // Check that the header name is a token. size_t i = name_start; while (i < name_end) { if (!htp_is_token(data[i])) { // Incorrectly formed header name. h->flags |= HTP_FIELD_INVALID; // Log only once per transaction. if (!(connp->in_tx->flags & HTP_FIELD_INVALID)) { connp->in_tx->flags |= HTP_FIELD_INVALID; htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Request header name is not a token"); } break; } i++; } // Now extract the name and the value h->name = bstr_dup_mem(data + name_start, name_end - name_start); if (h->name == NULL) return HTP_ERROR; h->value = bstr_dup_mem(data + value_start, value_end - value_start); if (h->value == NULL) { bstr_free(h->name); return HTP_ERROR; } return HTP_OK; }
/** * Extract one request header. A header can span multiple lines, in * which case they will be folded into one before parsing is attempted. * * @param connp * @return HTP_OK or HTP_ERROR */ int htp_process_request_header_apache_2_2(htp_connp_t *connp) { bstr *tempstr = NULL; unsigned char *data = NULL; size_t len = 0; // Create new header structure htp_header_t *h = calloc(1, sizeof (htp_header_t)); if (h == NULL) return HTP_ERROR; // Ensure we have the necessary header data in a single buffer if (connp->in_header_line_index + 1 == connp->in_header_line_counter) { // Single line htp_header_line_t *hl = list_get(connp->in_tx->request_header_lines, connp->in_header_line_index); if (hl == NULL) { // Internal error htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "Process request header (Apache 2.2): Internal error"); free(h); return HTP_ERROR; } data = (unsigned char *) bstr_ptr(hl->line); len = bstr_len(hl->line); hl->header = h; } else { // Multiple lines (folded) int i = 0; for (i = connp->in_header_line_index; i < connp->in_header_line_counter; i++) { htp_header_line_t *hl = list_get(connp->in_tx->request_header_lines, i); if (hl == NULL) { // Internal error htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "Process request header (Apache 2.2): Internal error"); free(h); return HTP_ERROR; } len += bstr_len(hl->line); } tempstr = bstr_alloc(len); if (tempstr == NULL) { htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "Process request header (Apache 2.2): Failed to allocate bstring of %d bytes", len); free(h); return HTP_ERROR; } for (i = connp->in_header_line_index; i < connp->in_header_line_counter; i++) { htp_header_line_t *hl = list_get(connp->in_tx->request_header_lines, i); if (hl == NULL) { // Internal error htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "Process request header (Apache 2.2): Internal error"); bstr_free(tempstr); free(h); return HTP_ERROR; } char *data = bstr_ptr(hl->line); size_t len = bstr_len(hl->line); htp_chomp((unsigned char *)data, &len); bstr_add_mem_noex(tempstr, data, len); hl->header = h; } data = (unsigned char *) bstr_ptr(tempstr); } // Now try to parse the header if (htp_parse_request_header_apache_2_2(connp, h, data, len) != HTP_OK) { // Note: downstream responsible for error logging if (tempstr != NULL) { free(tempstr); } free(h); return HTP_ERROR; } // Do we already have a header with the same name? htp_header_t *h_existing = table_get(connp->in_tx->request_headers, h->name); if (h_existing != NULL) { // TODO Do we want to keep a list of the headers that are // allowed to be combined in this way? // Add to existing header h_existing->value = bstr_expand(h_existing->value, bstr_len(h_existing->value) + 2 + bstr_len(h->value)); bstr_add_mem_noex(h_existing->value, ", ", 2); bstr_add_str_noex(h_existing->value, h->value); // The header is no longer needed if (h->name != NULL) free(h->name); if (h->value != NULL) free(h->value); free(h); // Keep track of same-name headers h_existing->flags |= HTP_FIELD_REPEATED; } else { // Add as a new header table_add(connp->in_tx->request_headers, h->name, h); } if (tempstr != NULL) { free(tempstr); } return HTP_OK; }
/** * Parses a message header line as Apache 2.2 does. * * @param connp * @param h * @param data * @param len * @return HTP_OK or HTP_ERROR */ int htp_parse_request_header_apache_2_2(htp_connp_t *connp, htp_header_t *h, unsigned char *data, size_t len) { size_t name_start, name_end; size_t value_start, value_end; htp_chomp(data, &len); name_start = 0; // Look for the colon size_t colon_pos = 0; while ((colon_pos < len) && (data[colon_pos] != '\0') && (data[colon_pos] != ':')) colon_pos++; if ((colon_pos == len) || (data[colon_pos] == '\0')) { // Missing colon h->flags |= HTP_FIELD_UNPARSEABLE; if (!(connp->in_tx->flags & HTP_FIELD_UNPARSEABLE)) { connp->in_tx->flags |= HTP_FIELD_UNPARSEABLE; // Only log once per transaction htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "Request field invalid: colon missing"); } return HTP_ERROR; } if (colon_pos == 0) { // Empty header name h->flags |= HTP_FIELD_INVALID; if (!(connp->in_tx->flags & HTP_FIELD_INVALID)) { connp->in_tx->flags |= HTP_FIELD_INVALID; // Only log once per transaction htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Request field invalid: empty name"); } } name_end = colon_pos; // Ignore LWS after field-name size_t prev = name_end - 1; while ((prev > name_start) && (htp_is_lws(data[prev]))) { prev--; name_end--; h->flags |= HTP_FIELD_INVALID; if (!(connp->in_tx->flags & HTP_FIELD_INVALID)) { connp->in_tx->flags |= HTP_FIELD_INVALID; htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Request field invalid: LWS after name"); } } // Value value_start = colon_pos; // Go over the colon if (value_start < len) { value_start++; } // Ignore LWS before field-content while ((value_start < len) && (htp_is_lws(data[value_start]))) { value_start++; } // Look for the end of field-content value_end = value_start; while ((value_end < len) && (data[value_end] != '\0')) value_end++; // Ignore LWS after field-content prev = value_end - 1; while ((prev > value_start) && (htp_is_lws(data[prev]))) { prev--; value_end--; } // Check that the header name is a token size_t i = name_start; while (i < name_end) { if (!htp_is_token(data[i])) { h->flags |= HTP_FIELD_INVALID; if (!(connp->in_tx->flags & HTP_FIELD_INVALID)) { connp->in_tx->flags |= HTP_FIELD_INVALID; htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Request header name is not a token"); } break; } i++; } // Now extract the name and the value h->name = bstr_memdup((char *) data + name_start, name_end - name_start); h->value = bstr_memdup((char *) data + value_start, value_end - value_start); 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 connp->in_tx->request_line_raw = bstr_dup_mem((char *) connp->in_line, connp->in_line_len); if (connp->in_tx->request_line_raw == NULL) { return HTP_ERROR; } /// @todo Would be nice to reference request_line_raw data htp_chomp(connp->in_line, &connp->in_line_len); connp->in_tx->request_line = bstr_dup_ex(connp->in_tx->request_line_raw, 0, connp->in_line_len); if (connp->in_tx->request_line == NULL) { return HTP_ERROR; } // 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; } // Run hook REQUEST_URI_NORMALIZE int rc = hook_run_all(connp->cfg->hook_request_uri_normalize, 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; } } // 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_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; } } // 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->conn->use_local_port) { 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 { connp->in_tx->parsed_uri->port_number = connp->conn->remote_port; } } else { // Invalid port; use the real port instead if (connp->conn->use_local_port) { connp->in_tx->parsed_uri->port_number = connp->conn->local_port; } else { connp->in_tx->parsed_uri->port_number = connp->conn->remote_port; } // TODO Log } } else { if (connp->conn->use_local_port) { connp->in_tx->parsed_uri->port_number = connp->conn->local_port; } else { connp->in_tx->parsed_uri->port_number = connp->conn->remote_port; } } // 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 = hook_run_all(connp->cfg->hook_request_line, 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; } } // Clean up. connp->in_line_len = 0; // Move on to the next phase. connp->in_state = htp_connp_REQ_PROTOCOL; 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); // 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++; } } }
/** * Parses response line. * * @param connp * @returns HTP_OK on state change, HTTP_ERROR on error, or HTP_DATA when more data is needed. */ int htp_connp_RES_LINE(htp_connp_t * connp) { for (;;) { // Get one byte OUT_COPY_BYTE_OR_RETURN(connp); // 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 // Is this a line that should be ignored? if (htp_connp_is_line_ignorable(connp, connp->out_line, connp->out_line_len)) { // We have an empty/whitespace line, which we'll note, ignore and move on connp->out_tx->response_ignored_lines++; // TODO How many lines are we willing to accept? // Start again connp->out_line_len = 0; return HTP_OK; } // Process response line // Deallocate previous response line allocations, which we would have on a 100 response // TODO Consider moving elsewhere; no need to make these checks on every response if (connp->out_tx->response_line != NULL) { bstr_free(&connp->out_tx->response_line); } if (connp->out_tx->response_protocol != NULL) { bstr_free(&connp->out_tx->response_protocol); } if (connp->out_tx->response_status != NULL) { bstr_free(&connp->out_tx->response_status); } if (connp->out_tx->response_message != NULL) { bstr_free(&connp->out_tx->response_message); } connp->out_tx->response_line_raw = bstr_dup_mem((char *) connp->out_line, connp->out_line_len); if (connp->out_tx->response_line_raw == NULL) { return HTP_ERROR; } /// @todo Would be nice to reference response_line_raw data int chomp_result = htp_chomp(connp->out_line, &connp->out_line_len); connp->out_tx->response_line = bstr_dup_ex(connp->out_tx->response_line_raw, 0, connp->out_line_len); if (connp->out_tx->response_line == NULL) { return HTP_ERROR; } // Parse response line if (connp->cfg->parse_response_line(connp) != HTP_OK) { // Note: downstream responsible for error logging return HTP_ERROR; } // Is the response line valid? if ((connp->out_tx->response_protocol_number < 0) || (connp->out_tx->response_status_number < 0) || (connp->out_tx->response_status_number < HTP_VALID_STATUS_MIN) || (connp->out_tx->response_status_number > HTP_VALID_STATUS_MAX)) { // Response line is invalid htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Invalid response line"); connp->out_tx->flags |= HTP_STATUS_LINE_INVALID; } // Even when the response line is invalid, determine if it looks like // a response line (which is what browsers do). if (htp_resembles_response_line(connp->out_tx) == 0) { // Process this line as response body data htp_tx_data_t d; d.tx = connp->out_tx; d.data = connp->out_line; d.len = connp->out_line_len + chomp_result; connp->out_tx->response_message_len += d.len; connp->out_tx->response_entity_len += d.len; int rc = htp_res_run_hook_body_data(connp, &d); if (rc != HOOK_OK) { htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "Response body data callback returned error (%d)", rc); return HTP_ERROR; } // Continue to process response body connp->out_tx->response_transfer_coding = IDENTITY; connp->out_state = htp_connp_RES_BODY_IDENTITY; connp->out_tx->progress = TX_PROGRESS_RES_BODY; return HTP_OK; } // Run hook RESPONSE_LINE int rc = hook_run_all(connp->cfg->hook_response_line, connp); if (rc != HOOK_OK) { htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "Response line callback returned error (%d)", rc); return HTP_ERROR; } // Clean up. connp->out_line_len = 0; // Move on to the next phase. connp->out_state = htp_connp_RES_HEADERS; connp->out_tx->progress = TX_PROGRESS_RES_HEADERS; return HTP_OK; } } }
/** * Extract one request header. A header can span multiple lines, in * which case they will be folded into one before parsing is attempted. * * @param[in] connp * @return HTP_OK or HTP_ERROR */ int htp_process_request_header_generic(htp_connp_t *connp) { bstr *tempstr = NULL; unsigned char *data = NULL; size_t len = 0; // Create new header structure htp_header_t *h = calloc(1, sizeof (htp_header_t)); if (h == NULL) { return HTP_ERROR; } // Ensure we have the necessary header data in a single buffer if (connp->in_header_line_index + 1 == connp->in_header_line_counter) { // Single line htp_header_line_t *hl = htp_list_get(connp->in_tx->request_header_lines, connp->in_header_line_index); if (hl == NULL) { // Internal error free(h); return HTP_ERROR; } data = (unsigned char *)bstr_ptr(hl->line); len = bstr_len(hl->line); hl->header = h; } else { // Multiple lines (folded) int i = 0; for (i = connp->in_header_line_index; i < connp->in_header_line_counter; i++) { htp_header_line_t *hl = htp_list_get(connp->in_tx->request_header_lines, i); len += bstr_len(hl->line); } tempstr = bstr_alloc(len); if (tempstr == NULL) { free(h); return HTP_ERROR; } for (i = connp->in_header_line_index; i < connp->in_header_line_counter; i++) { htp_header_line_t *hl = htp_list_get(connp->in_tx->request_header_lines, i); unsigned char *line = bstr_ptr(hl->line); size_t llen = bstr_len(hl->line); htp_chomp((unsigned char *)line, &llen); bstr_add_mem_noex(tempstr, line, llen); hl->header = h; if (i != connp->in_header_line_index) { hl->flags |= HTP_FIELD_FOLDED; } } h->flags |= HTP_FIELD_FOLDED; data = (unsigned char *)bstr_ptr(tempstr); len = bstr_len(tempstr); } // Now try to parse the header if (htp_parse_request_header_generic(connp, h, data, len) != HTP_OK) { bstr_free(tempstr); free(h); return HTP_ERROR; } // Do we already have a header with the same name? htp_header_t *h_existing = htp_table_get(connp->in_tx->request_headers, h->name); if (h_existing != NULL) { // repeated header int i = 0; // TODO Do we want to keep a list of the headers that are // allowed to be combined in this way? // Add to existing header bstr *new_value = bstr_expand(h_existing->value, bstr_len(h_existing->value) + 2 + bstr_len(h->value)); if (new_value == NULL) { bstr_free(h->name); bstr_free(h->value); free(h); bstr_free(tempstr); return HTP_ERROR; } h_existing->value = new_value; bstr_add_mem_noex(h_existing->value, (unsigned char *)", ", 2); bstr_add_noex(h_existing->value, h->value); // replace the header references in all lines for (i = connp->in_header_line_index; i < connp->in_header_line_counter; i++) { htp_header_line_t *hl = htp_list_get(connp->in_tx->request_header_lines, i); hl->header = h_existing; } // The header fields are no longer needed bstr_free(h->name); bstr_free(h->value); free(h); // Keep track of same-name headers h_existing->flags |= HTP_FIELD_REPEATED; } else { // Add as a new header htp_table_add(connp->in_tx->request_headers, h->name, h); } bstr_free(tempstr); return HTP_OK; }