static int HttpGetHeaders(lua_State *luastate, int dir) { if (!(LuaStateNeedProto(luastate, ALPROTO_HTTP))) return LuaCallbackError(luastate, "error: protocol not http"); htp_tx_t *tx = LuaStateGetTX(luastate); if (tx == NULL) return LuaCallbackError(luastate, "internal error: no tx"); htp_table_t *table = tx->request_headers; if (dir == 1) table = tx->response_headers; if (tx->request_headers == NULL) return LuaCallbackError(luastate, "no headers"); lua_newtable(luastate); htp_header_t *h = NULL; size_t i = 0; size_t no_of_headers = htp_table_size(table); for (; i < no_of_headers; i++) { h = htp_table_get_index(table, i, NULL); LuaPushStringBuffer(luastate, bstr_ptr(h->name), bstr_len(h->name)); LuaPushStringBuffer(luastate, bstr_ptr(h->value), bstr_len(h->value)); lua_settable(luastate, -3); } return 1; }
htp_param_t *htp_tx_req_get_param_ex(htp_tx_t *tx, enum htp_data_source_t source, const char *name, size_t name_len) { htp_param_t *p = NULL; for (int i = 0, n = htp_table_size(tx->request_params); i < n; i++) { p = htp_table_get_index(tx->request_params, i, NULL); if (p->source != source) continue; if (bstr_cmp_mem_nocase(p->name, name, name_len) == 0) return p; } return NULL; }
/** * Parses request query string, if present. * * @param[in] connp * @param[in] raw_data * @param[in] raw_len * @return HTP_OK if query string was parsed, HTP_DECLINED if there was no query * string, and HTP_ERROR on failure. */ htp_status_t htp_ch_urlencoded_callback_request_line(htp_tx_t *tx) { // Proceed only if there's something for us to parse. if ((tx->parsed_uri->query == NULL) || (bstr_len(tx->parsed_uri->query) == 0)) { return HTP_DECLINED; } // We have a non-zero length query string. tx->request_urlenp_query = htp_urlenp_create(tx); if (tx->request_urlenp_query == NULL) return HTP_ERROR; if (htp_urlenp_parse_complete(tx->request_urlenp_query, bstr_ptr(tx->parsed_uri->query), bstr_len(tx->parsed_uri->query)) != HTP_OK) { htp_urlenp_destroy(tx->request_urlenp_query); return HTP_ERROR; } // Add all parameters to the transaction. bstr *name = NULL; bstr *value = NULL; for (size_t i = 0, n = htp_table_size(tx->request_urlenp_query->params); i < n; i++) { value = htp_table_get_index(tx->request_urlenp_query->params, i, &name); htp_param_t *param = calloc(1, sizeof (htp_param_t)); if (param == NULL) return HTP_ERROR; param->name = name; param->value = value; param->source = HTP_SOURCE_QUERY_STRING; param->parser_id = HTP_PARSER_URLENCODED; param->parser_data = NULL; if (htp_tx_req_add_param(tx, param) != HTP_OK) { free(param); return HTP_ERROR; } } // All the parameter data is now owned by the transaction, and // the parser table used to store it is no longer needed. The // line below will destroy just the table, leaving keys intact. htp_table_destroy_ex(tx->request_urlenp_query->params); tx->request_urlenp_query->params = NULL; htp_urlenp_destroy(tx->request_urlenp_query); tx->request_urlenp_query = NULL; return HTP_OK; }
htp_status_t htp_tx_res_set_headers_clear(htp_tx_t *tx) { if ((tx == NULL) || (tx->response_headers == NULL)) return HTP_ERROR; htp_header_t *h = NULL; for (size_t i = 0, n = htp_table_size(tx->response_headers); i < n; i++) { h = htp_table_get_index(tx->response_headers, i, NULL); bstr_free(h->name); bstr_free(h->value); free(h); } htp_table_destroy(tx->response_headers); tx->response_headers = htp_table_create(32); if (tx->response_headers == NULL) return HTP_ERROR; return HTP_OK; }
/** * This callback function feeds request body data to a Urlencoded parser * and, later, feeds the parsed parameters to the correct structures. * * @param[in] d * @return HTP_OK on success, HTP_ERROR on failure. */ htp_status_t htp_ch_urlencoded_callback_request_body_data(htp_tx_data_t *d) { htp_tx_t *tx = d->tx; // Check that we were not invoked again after the finalization. if (tx->request_urlenp_body->params == NULL) return HTP_ERROR; if (d->data != NULL) { // Process one chunk of data. htp_urlenp_parse_partial(tx->request_urlenp_body, d->data, d->len); } else { // Finalize parsing. htp_urlenp_finalize(tx->request_urlenp_body); // Add all parameters to the transaction. bstr *name = NULL; bstr *value = NULL; for (size_t i = 0, n = htp_table_size(tx->request_urlenp_body->params); i < n; i++) { value = htp_table_get_index(tx->request_urlenp_body->params, i, &name); htp_param_t *param = calloc(1, sizeof (htp_param_t)); if (param == NULL) return HTP_ERROR; param->name = name; param->value = value; param->source = HTP_SOURCE_BODY; param->parser_id = HTP_PARSER_URLENCODED; param->parser_data = NULL; if (htp_tx_req_add_param(tx, param) != HTP_OK) { free(param); return HTP_ERROR; } } // All the parameter data is now owned by the transaction, and // the parser table used to store it is no longer needed. The // line below will destroy just the table, leaving keys intact. htp_table_destroy_ex(tx->request_urlenp_body->params); tx->request_urlenp_body->params = NULL; } return HTP_OK; }
/** * Destroys an existing URLENCODED parser. * * @param[in] urlenp */ void htp_urlenp_destroy(htp_urlenp_t *urlenp) { if (urlenp == NULL) return; if (urlenp->_name != NULL) { bstr_free(urlenp->_name); } bstr_builder_destroy(urlenp->_bb); if (urlenp->params != NULL) { // Destroy parameters. for (size_t i = 0, n = htp_table_size(urlenp->params); i < n; i++) { bstr *b = htp_table_get_index(urlenp->params, i, NULL); // Parameter name will be freed by the table code. bstr_free(b); } htp_table_destroy(urlenp->params); } free(urlenp); }
/** * Determines presence (and encoding) of a response body. * * @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_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 == HTP_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 = HTP_STREAM_TUNNEL; connp->out_status = HTP_STREAM_TUNNEL; connp->out_state = htp_connp_RES_FINALIZE; return HTP_OK; } else { // This is a failed CONNECT stream, which means that // we can unblock request parsing connp->in_status = HTP_STREAM_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 seen so far. htp_header_t *h = NULL; for (int i = 0, n = htp_table_size(connp->out_tx->response_headers); i < n; i++) { h = htp_table_get_index(connp->out_tx->response_headers, i, NULL); bstr_free(h->name); bstr_free(h->value); free(h); } htp_table_clear(connp->out_tx->response_headers); // Expecting to see another response line next. connp->out_state = htp_connp_RES_LINE; connp->out_tx->progress = HTP_RESPONSE_LINE; connp->out_tx->seen_100continue++; return HTP_OK; } // 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 == HTP_M_HEAD)) { // There's no response body connp->out_tx->response_transfer_coding = HTP_CODING_NO_BODY; connp->out_state = htp_connp_RES_FINALIZE; } else { // We have a response body htp_header_t *ct = htp_table_get_c(connp->out_tx->response_headers, "content-type"); htp_header_t *cl = htp_table_get_c(connp->out_tx->response_headers, "content-length"); htp_header_t *te = htp_table_get_c(connp->out_tx->response_headers, "transfer-encoding"); if (ct != NULL) { connp->out_tx->response_content_type = bstr_dup_lower(ct->value); if (connp->out_tx->response_content_type == NULL) return HTP_ERROR; // Ignore parameters unsigned char *data = bstr_ptr(connp->out_tx->response_content_type); size_t len = bstr_len(ct->value); size_t newlen = 0; while (newlen < len) { // TODO Some platforms may do things differently here. if (htp_is_space(data[newlen]) || (data[newlen] == ';')) { bstr_adjust_len(connp->out_tx->response_content_type, newlen); break; } newlen++; } } // 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 = HTP_CODING_CHUNKED; // We are still going to check for the presence of C-L if (cl != NULL) { // This is a violation of the RFC connp->out_tx->flags |= HTP_REQUEST_SMUGGLING; } connp->out_state = htp_connp_RES_BODY_CHUNKED_LENGTH; connp->out_tx->progress = HTP_RESPONSE_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 = HTP_CODING_IDENTITY; // Check for multiple C-L headers if (cl->flags & HTP_FIELD_REPEATED) { connp->out_tx->flags |= HTP_REQUEST_SMUGGLING; } // Get body length connp->out_tx->response_content_length = htp_parse_content_length(cl->value); if (connp->out_tx->response_content_length < 0) { htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "Invalid C-L field in response: %d", connp->out_tx->response_content_length); return HTP_ERROR; } else { connp->out_content_length = connp->out_tx->response_content_length; connp->out_body_data_left = connp->out_content_length; if (connp->out_content_length != 0) { connp->out_state = htp_connp_RES_BODY_IDENTITY_CL_KNOWN; connp->out_tx->progress = HTP_RESPONSE_BODY; } else { connp->out_state = htp_connp_RES_FINALIZE; } } } 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. 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_STREAM_CLOSE; connp->out_tx->response_transfer_coding = HTP_CODING_IDENTITY; connp->out_tx->progress = HTP_RESPONSE_BODY; connp->out_body_data_left = -1; } } // 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 int rc = htp_tx_state_response_headers(connp->out_tx); if (rc != HTP_OK) return rc; return HTP_OK; }
static uint8_t *GetBufferForTX(htp_tx_t *tx, uint64_t tx_id, DetectEngineThreadCtx *det_ctx, Flow *f, uint8_t flags, uint32_t *buffer_len) { *buffer_len = 0; HttpHeaderThreadData *hdr_td = NULL; HttpHeaderBuffer *buf = HttpHeaderGetBufferSpaceForTXID(det_ctx, f, flags, tx_id, g_keyword_thread_id, &hdr_td); if (unlikely(buf == NULL)) { return NULL; } else if (buf->len > 0) { /* already filled buf, reuse */ *buffer_len = buf->len; return buf->buffer; } bstr *line = NULL; htp_table_t *headers; if (flags & STREAM_TOSERVER) { if (AppLayerParserGetStateProgress(IPPROTO_TCP, ALPROTO_HTTP, tx, flags) <= HTP_REQUEST_HEADERS) return NULL; line = tx->request_line; headers = tx->request_headers; } else { if (AppLayerParserGetStateProgress(IPPROTO_TCP, ALPROTO_HTTP, tx, flags) <= HTP_RESPONSE_HEADERS) return NULL; headers = tx->response_headers; line = tx->response_line; } if (line == NULL || headers == NULL) return NULL; size_t line_size = bstr_len(line) + 2; if (line_size + buf->len > buf->size) { if (HttpHeaderExpandBuffer(hdr_td, buf, line_size) != 0) { return NULL; } } memcpy(buf->buffer + buf->len, bstr_ptr(line), bstr_size(line)); buf->len += bstr_size(line); buf->buffer[buf->len++] = '\r'; buf->buffer[buf->len++] = '\n'; size_t i = 0; size_t no_of_headers = htp_table_size(headers); for (; i < no_of_headers; i++) { htp_header_t *h = htp_table_get_index(headers, i, NULL); size_t size1 = bstr_size(h->name); size_t size2 = bstr_size(h->value); size_t size = size1 + size2 + 4; if (i + 1 == no_of_headers) size += 2; if (size + buf->len > buf->size) { if (HttpHeaderExpandBuffer(hdr_td, buf, size) != 0) { return NULL; } } memcpy(buf->buffer + buf->len, bstr_ptr(h->name), bstr_size(h->name)); buf->len += bstr_size(h->name); buf->buffer[buf->len++] = ':'; buf->buffer[buf->len++] = ' '; memcpy(buf->buffer + buf->len, bstr_ptr(h->value), bstr_size(h->value)); buf->len += bstr_size(h->value); buf->buffer[buf->len++] = '\r'; buf->buffer[buf->len++] = '\n'; if (i + 1 == no_of_headers) { buf->buffer[buf->len++] = '\r'; buf->buffer[buf->len++] = '\n'; } } *buffer_len = buf->len; return buf->buffer; }
/** * Transcode all parameters supplied in the table. * * @param[in] connp * @param[in] params * @param[in] destroy_old */ int htp_transcode_params(htp_connp_t *connp, htp_table_t **params, int destroy_old) { htp_table_t *input_params = *params; // No transcoding unless necessary if ((connp->cfg->internal_encoding == NULL)||(connp->cfg->request_encoding == NULL)) return HTP_OK; // Create a new table that will hold transcoded parameters htp_table_t *output_params = htp_table_create(htp_table_size(input_params)); if (output_params == NULL) return HTP_ERROR; // Initialize iconv iconv_t cd = iconv_open(connp->cfg->internal_encoding, connp->cfg->request_encoding); if (cd == (iconv_t) -1) { htp_table_destroy(output_params); return HTP_ERROR; } #if (_LIBICONV_VERSION >= 0x0108) int iconv_param = 0; iconvctl(cd, ICONV_SET_TRANSLITERATE, &iconv_param); iconv_param = 1; iconvctl(cd, ICONV_SET_DISCARD_ILSEQ, &iconv_param); #endif // Convert the parameters, one by one bstr *name = NULL; bstr *value = NULL; for (int i = 0, n = htp_table_size(input_params); i < n; i++) { value = htp_table_get_index(input_params, i, &name); bstr *new_name = NULL, *new_value = NULL; // Convert name htp_transcode_bstr(cd, name, &new_name); if (new_name == NULL) { iconv_close(cd); bstr *b = NULL; for (int j = 0, k = htp_table_size(output_params); j < k; j++) { b = htp_table_get_index(output_params, j, NULL); bstr_free(b); } htp_table_destroy(output_params); return HTP_ERROR; } // Convert value htp_transcode_bstr(cd, value, &new_value); if (new_value == NULL) { bstr_free(new_name); iconv_close(cd); bstr *b = NULL; for (int j = 0, k = htp_table_size(output_params); j < k; j++) { b = htp_table_get_index(output_params, j, NULL); bstr_free(b); } htp_table_destroy(output_params); return HTP_ERROR; } // Add to new table htp_table_addn(output_params, new_name, new_value); } // Replace the old parameter table *params = output_params; // Destroy the old parameter table if necessary if (destroy_old) { bstr *b = NULL; for (int i = 0, n = htp_table_size(input_params); i < n; i++) { b = htp_table_get_index(input_params, i, NULL); bstr_free(b); } htp_table_destroy(input_params); } iconv_close(cd); return HTP_OK; }
void htp_tx_destroy_incomplete(htp_tx_t *tx) { if (tx == NULL) return; // Disconnect transaction from other structures. htp_conn_remove_tx(tx->conn, tx); htp_connp_tx_remove(tx->connp, tx); // Request fields. bstr_free(tx->request_line); bstr_free(tx->request_method); bstr_free(tx->request_uri); bstr_free(tx->request_protocol); bstr_free(tx->request_content_type); bstr_free(tx->request_hostname); htp_uri_free(tx->parsed_uri_raw); htp_uri_free(tx->parsed_uri); // Request_headers. if (tx->request_headers != NULL) { htp_header_t *h = NULL; for (size_t i = 0, n = htp_table_size(tx->request_headers); i < n; i++) { h = htp_table_get_index(tx->request_headers, i, NULL); bstr_free(h->name); bstr_free(h->value); free(h); } htp_table_destroy(tx->request_headers); } // Request parsers. htp_urlenp_destroy(tx->request_urlenp_query); htp_urlenp_destroy(tx->request_urlenp_body); htp_mpartp_destroy(tx->request_mpartp); // Request parameters. htp_param_t *param = NULL; for (size_t i = 0, n = htp_table_size(tx->request_params); i < n; i++) { param = htp_table_get_index(tx->request_params, i, NULL); free(param->name); free(param->value); free(param); } htp_table_destroy(tx->request_params); // Request cookies. if (tx->request_cookies != NULL) { bstr *b = NULL; for (size_t i = 0, n = htp_table_size(tx->request_cookies); i < n; i++) { b = htp_table_get_index(tx->request_cookies, i, NULL); bstr_free(b); } htp_table_destroy(tx->request_cookies); } htp_hook_destroy(tx->hook_request_body_data); // Response fields. bstr_free(tx->response_line); bstr_free(tx->response_protocol); bstr_free(tx->response_status); bstr_free(tx->response_message); bstr_free(tx->response_content_type); // Destroy response headers. if (tx->response_headers != NULL) { htp_header_t *h = NULL; for (size_t i = 0, n = htp_table_size(tx->response_headers); i < n; i++) { h = htp_table_get_index(tx->response_headers, i, NULL); bstr_free(h->name); bstr_free(h->value); free(h); } htp_table_destroy(tx->response_headers); } // If we're using a private configuration structure, destroy it. if (tx->is_config_shared == HTP_CONFIG_PRIVATE) { htp_config_destroy(tx->cfg); } free(tx); }
void htp_tx_destroy(htp_tx_t *tx) { bstr_free(tx->request_line); bstr_free(tx->request_line_raw); bstr_free(tx->request_method); bstr_free(tx->request_uri); bstr_free(tx->request_uri_normalized); bstr_free(tx->request_protocol); bstr_free(tx->request_headers_sep); if (tx->parsed_uri != NULL) { bstr_free(tx->parsed_uri->scheme); bstr_free(tx->parsed_uri->username); bstr_free(tx->parsed_uri->password); bstr_free(tx->parsed_uri->hostname); bstr_free(tx->parsed_uri->port); bstr_free(tx->parsed_uri->path); bstr_free(tx->parsed_uri->query); bstr_free(tx->parsed_uri->fragment); free(tx->parsed_uri); } if (tx->parsed_uri_incomplete != NULL) { bstr_free(tx->parsed_uri_incomplete->scheme); bstr_free(tx->parsed_uri_incomplete->username); bstr_free(tx->parsed_uri_incomplete->password); bstr_free(tx->parsed_uri_incomplete->hostname); bstr_free(tx->parsed_uri_incomplete->port); bstr_free(tx->parsed_uri_incomplete->path); bstr_free(tx->parsed_uri_incomplete->query); bstr_free(tx->parsed_uri_incomplete->fragment); free(tx->parsed_uri_incomplete); } // Destroy request_header_lines. if (tx->request_header_lines != NULL) { for (int i = 0, n = htp_list_size(tx->request_header_lines); i < n; i++) { htp_header_line_t *hl = htp_list_get(tx->request_header_lines, i); bstr_free(hl->line); // No need to destroy hl->header because // htp_header_line_t does not own it. free(hl); } htp_list_destroy(tx->request_header_lines); tx->request_header_lines = NULL; } // Destroy request_headers. if (tx->request_headers != NULL) { htp_header_t *h = NULL; for (int i = 0, n = htp_table_size(tx->request_headers); i < n; i++) { h = htp_table_get_index(tx->request_headers, i, NULL); bstr_free(h->name); bstr_free(h->value); free(h); } htp_table_destroy(tx->request_headers); } if (tx->request_headers_raw != NULL) { bstr_free(tx->request_headers_raw); } if (tx->response_headers_raw != NULL) { bstr_free(tx->response_headers_raw); } bstr_free(tx->response_line); bstr_free(tx->response_line_raw); bstr_free(tx->response_protocol); bstr_free(tx->response_status); bstr_free(tx->response_message); bstr_free(tx->response_headers_sep); // Destroy response_header_lines. if (tx->response_header_lines != NULL) { for (int i = 0, n = htp_list_size(tx->response_header_lines); i < n; i++) { htp_header_line_t *hl = htp_list_get(tx->response_header_lines, i); bstr_free(hl->line); // No need to destroy hl->header because // htp_header_line_t does not own it. free(hl); } htp_list_destroy(tx->response_header_lines); tx->response_header_lines = NULL; } // Destroy response headers. if (tx->response_headers != NULL) { htp_header_t *h = NULL; for (int i = 0, n = htp_table_size(tx->response_headers); i < n; i++) { h = htp_table_get_index(tx->response_headers, i, NULL); bstr_free(h->name); bstr_free(h->value); free(h); } htp_table_destroy(tx->response_headers); } // Tell the connection to remove this transaction from the list. htp_conn_remove_tx(tx->conn, tx); // Invalidate the pointer to this transactions held // by the connection parser. This is to allow a transaction // to be destroyed from within the final response callback. if (tx->connp != NULL) { if (tx->connp->out_tx == tx) { tx->connp->out_tx = NULL; } } bstr_free(tx->request_content_type); bstr_free(tx->response_content_type); // Parsers htp_urlenp_destroy(tx->request_urlenp_query); htp_urlenp_destroy(tx->request_urlenp_body); htp_mpartp_destroy(tx->request_mpartp); // Request parameters htp_param_t *param = NULL; for (int i = 0, n = htp_table_size(tx->request_params); i < n; i++) { param = htp_table_get_index(tx->request_params, i, NULL); free(param->name); free(param->value); free(param); } htp_table_destroy(tx->request_params); // Request cookies if (tx->request_cookies != NULL) { bstr *b = NULL; for (int i = 0, n = htp_table_size(tx->request_cookies); i < n; i++) { b = htp_table_get_index(tx->request_cookies, i, NULL); bstr_free(b); } htp_table_destroy(tx->request_cookies); } htp_hook_destroy(tx->hook_request_body_data); // If we're using a private configuration, destroy it. if (tx->is_config_shared == HTP_CONFIG_PRIVATE) { htp_config_destroy(tx->cfg); } free(tx); }