/** * 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 connp * @returns HTP_OK on state change, HTTP_ERROR on error, or HTP_DATA when more data is needed. */ int htp_connp_RES_BODY_CHUNKED_LENGTH(htp_connp_t *connp) { for (;;) { OUT_COPY_BYTE_OR_RETURN(connp); connp->out_tx->response_message_len++; // Have we reached the end of the line? if (connp->out_next_byte == LF) { htp_chomp(connp->out_line, &connp->out_line_len); // Extract chunk length connp->out_chunked_length = htp_parse_chunked_length(connp->out_line, connp->out_line_len); // Cleanup for the next line connp->out_line_len = 0; // Handle chunk length if (connp->out_chunked_length > 0) { // More data available // TODO Add a check for chunk length 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->progress = TX_PROGRESS_RES_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; } } }
/** * 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 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; } } }
/** * 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++; } } }