htp_status_t htp_tx_state_response_headers(htp_tx_t *tx) { if (tx == NULL) return HTP_ERROR; // Check for compression. // Determine content encoding. tx->response_content_encoding = HTP_COMPRESSION_NONE; htp_header_t *ce = htp_table_get_c(tx->response_headers, "content-encoding"); if (ce != NULL) { if ((bstr_cmp_c(ce->value, "gzip") == 0) || (bstr_cmp_c(ce->value, "x-gzip") == 0)) { tx->response_content_encoding = HTP_COMPRESSION_GZIP; } else if ((bstr_cmp_c(ce->value, "deflate") == 0) || (bstr_cmp_c(ce->value, "x-deflate") == 0)) { tx->response_content_encoding = HTP_COMPRESSION_DEFLATE; } } // Configure decompression, if enabled in the configuration. if (tx->connp->cfg->response_decompression_enabled) { tx->response_content_encoding_processing = tx->response_content_encoding; } else { tx->response_content_encoding_processing = HTP_COMPRESSION_NONE; } // Finalize sending raw header data. htp_status_t rc = htp_connp_res_receiver_finalize_clear(tx->connp); if (rc != HTP_OK) return rc; // Run hook RESPONSE_HEADERS. rc = htp_hook_run_all(tx->connp->cfg->hook_response_headers, tx); if (rc != HTP_OK) return rc; // Initialize the decompression engine as necessary. We can deal with three // scenarios: // // 1. Decompression is enabled, compression indicated in headers, and we decompress. // // 2. As above, but the user disables decompression by setting response_content_encoding // to COMPRESSION_NONE. // // 3. Decompression is disabled and we do not attempt to enable it, but the user // forces decompression by setting response_content_encoding to one of the // supported algorithms. if ((tx->response_content_encoding_processing == HTP_COMPRESSION_GZIP) || (tx->response_content_encoding_processing == HTP_COMPRESSION_DEFLATE)) { if (tx->connp->out_decompressor != NULL) { tx->connp->out_decompressor->destroy(tx->connp->out_decompressor); tx->connp->out_decompressor = NULL; } tx->connp->out_decompressor = (htp_decompressor_t *) htp_gzip_decompressor_create(tx->connp, tx->response_content_encoding_processing); if (tx->connp->out_decompressor == NULL) return HTP_ERROR; tx->connp->out_decompressor->callback = htp_tx_res_process_body_data_decompressor_callback; } else if (tx->response_content_encoding_processing != HTP_COMPRESSION_NONE) { return HTP_ERROR; } return HTP_OK; }
virtual void SetUp() { home = getenv("srcdir"); if (home == NULL) { fprintf(stderr, "This program needs environment variable 'srcdir' set."); exit(EXIT_FAILURE); } cfg = htp_config_create(); htp_config_set_server_personality(cfg, HTP_SERVER_APACHE_2); connp = htp_connp_create(cfg); tx = htp_connp_tx_create(connp); htp_tx_set_user_data(tx, &output); decompressor = htp_gzip_decompressor_create(connp, HTP_COMPRESSION_GZIP); decompressor->callback = GUnzip_decompressor_callback; o_boxing_wizards = bstr_dup_c("The five boxing wizards jump quickly."); output = NULL; }
/** * Determines presence (and encoding) of a response body. * * @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_DETERMINE(htp_connp_t *connp) { // If the request uses the CONNECT method, then not only are we // to assume there's no body, but we need to ignore all // subsequent data in the stream. if (connp->out_tx->request_method_number == M_CONNECT) { if ((connp->out_tx->response_status_number >= 200) && (connp->out_tx->response_status_number <= 299)) { // This is a successful CONNECT stream, which means // we need to switch into tunnelling mode. connp->in_status = STREAM_STATE_TUNNEL; connp->out_status = STREAM_STATE_TUNNEL; connp->out_state = htp_connp_RES_IDLE; connp->out_tx->progress = TX_PROGRESS_DONE; return HTP_OK; } else { // This is a failed CONNECT stream, which means that // we can unblock request parsing connp->in_status = STREAM_STATE_DATA; // We are going to continue processing this transaction, // adding a note for ourselves to stop at the end (because // we don't want to see the beginning of a new transaction). connp->out_data_other_at_tx_end = 1; } } // Check for an interim "100 Continue" // response. Ignore it if found, and revert back to RES_FIRST_LINE. if (connp->out_tx->response_status_number == 100) { if (connp->out_tx->seen_100continue != 0) { htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "Already seen 100-Continue"); return HTP_ERROR; } // Ignore any response headers set table_clear(connp->out_tx->response_headers); connp->out_state = htp_connp_RES_LINE; connp->out_tx->progress = TX_PROGRESS_RES_LINE; connp->out_tx->seen_100continue++; return HTP_OK; } // Check for compression if (connp->cfg->response_decompression_enabled) { htp_header_t *ce = table_get_c(connp->out_tx->response_headers, "content-encoding"); if (ce != NULL) { if ((bstr_cmp_c(ce->value, "gzip") == 0) || (bstr_cmp_c(ce->value, "x-gzip") == 0)) { connp->out_tx->response_content_encoding = COMPRESSION_GZIP; } else if ((bstr_cmp_c(ce->value, "deflate") == 0) || (bstr_cmp_c(ce->value, "x-deflate") == 0)) { connp->out_tx->response_content_encoding = COMPRESSION_DEFLATE; } if (connp->out_tx->response_content_encoding != COMPRESSION_NONE) { connp->out_decompressor = (htp_decompressor_t *) htp_gzip_decompressor_create(connp, connp->out_tx->response_content_encoding); if (connp->out_decompressor != NULL) { connp->out_decompressor->callback = htp_connp_RES_BODY_DECOMPRESSOR_CALLBACK; } else { // No need to do anything; the error will have already // been reported by the failed decompressor. } } } } // 1. Any response message which MUST NOT include a message-body // (such as the 1xx, 204, and 304 responses and any response to a HEAD // request) is always terminated by the first empty line after the // header fields, regardless of the entity-header fields present in the // message. if (((connp->out_tx->response_status_number >= 100) && (connp->out_tx->response_status_number <= 199)) || (connp->out_tx->response_status_number == 204) || (connp->out_tx->response_status_number == 304) || (connp->out_tx->request_method_number == M_HEAD)) { // There's no response body connp->out_state = htp_connp_RES_IDLE; } else { // We have a response body htp_header_t *cl = table_get_c(connp->out_tx->response_headers, "content-length"); htp_header_t *te = table_get_c(connp->out_tx->response_headers, "transfer-encoding"); // 2. If a Transfer-Encoding header field (section 14.40) is present and // indicates that the "chunked" transfer coding has been applied, then // the length is defined by the chunked encoding (section 3.6). if ((te != NULL) && (bstr_cmp_c(te->value, "chunked") == 0)) { // If the T-E header is present we are going to use it. connp->out_tx->response_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->out_tx->flags |= HTP_REQUEST_SMUGGLING; // TODO } connp->out_state = htp_connp_RES_BODY_CHUNKED_LENGTH; connp->out_tx->progress = TX_PROGRESS_RES_BODY; }// 3. If a Content-Length header field (section 14.14) is present, its // value in bytes represents the length of the message-body. else if (cl != NULL) { // We know the exact length connp->out_tx->response_transfer_coding = IDENTITY; // Check for multiple C-L headers if (cl->flags & HTP_FIELD_REPEATED) { connp->out_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 response: %d", i); return HTP_ERROR; } else { connp->out_content_length = i; connp->out_body_data_left = connp->out_content_length; if (connp->out_content_length != 0) { connp->out_state = htp_connp_RES_BODY_IDENTITY; connp->out_tx->progress = TX_PROGRESS_RES_BODY; } else { connp->out_state = htp_connp_RES_IDLE; connp->out_tx->progress = TX_PROGRESS_DONE; } } } else { // 4. If the message uses the media type "multipart/byteranges", which is // self-delimiting, then that defines the length. This media type MUST // NOT be used unless the sender knows that the recipient can parse it; // the presence in a request of a Range header with multiple byte-range // specifiers implies that the client can parse multipart/byteranges // responses. htp_header_t *ct = table_get_c(connp->out_tx->response_headers, "content-type"); if (ct != NULL) { // TODO Handle multipart/byteranges if (bstr_index_of_c_nocase(ct->value, "multipart/byteranges") != -1) { htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "C-T multipart/byteranges in responses not supported"); return HTP_ERROR; } } // 5. By the server closing the connection. (Closing the connection // cannot be used to indicate the end of a request body, since that // would leave no possibility for the server to send back a response.) connp->out_state = htp_connp_RES_BODY_IDENTITY; connp->out_tx->progress = TX_PROGRESS_RES_BODY; } } // NOTE We do not need to check for short-style HTTP/0.9 requests here because // that is done earlier, before response line parsing begins // Run hook RESPONSE_HEADERS_COMPLETE int rc = hook_run_all(connp->cfg->hook_response_headers, connp); if (rc != HOOK_OK) { htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "Response headers callback returned error (%d)", rc); return HTP_ERROR; } return HTP_OK; }