/** * Parses a single v0 request cookie and places the results into tx->request_cookies. * * @param[in] connp * @param[in] data * @param[in] len * @return HTP_OK on success, HTP_ERROR on error. */ int htp_parse_single_cookie_v0(htp_connp_t *connp, unsigned char *data, size_t len) { if (len == 0) return HTP_OK; size_t pos = 0; // Look for '=' while ((pos < len) && (data[pos] != '=')) pos++; if (pos == 0) return HTP_OK; // Ignore nameless cookies bstr *name = bstr_dup_mem(data, pos); if (name == NULL) return HTP_ERROR; bstr *value = NULL; if (pos == len) { // Cookie is empty value = bstr_dup_c(""); } else { // Cookie is not empty value = bstr_dup_mem(data + pos + 1, len - pos - 1); } if (value == NULL) { bstr_free(name); return HTP_ERROR; } // Add cookie directly htp_table_addn(connp->in_tx->request_cookies, name, value); return HTP_OK; }
TEST(BstrTest, DupMem) { bstr *dst; dst = bstr_dup_mem("ABCDEFGHIJKL\000NOPQRSTUVWXYZ", 18); EXPECT_EQ(0, memcmp("ABCDEFGHIJKL\000NOPQRSTUVWXYZ", bstr_ptr(dst), 18)); bstr_free(dst); }
TEST(BstrTest, IndexOf) { bstr *haystack = bstr_dup_mem("ABCDEFGHIJKL\000NOPQRSTUVWXYZ", 20); bstr *p1 = bstr_dup_c("NOPQ"); bstr *p2 = bstr_dup_c("siej"); bstr *p3 = bstr_dup_c("TUVWXYZ"); bstr *p4 = bstr_dup_c("nopq"); EXPECT_EQ(13, bstr_index_of(haystack, p1)); EXPECT_EQ(-1, bstr_index_of(haystack, p2)); EXPECT_EQ(-1, bstr_index_of(haystack, p3)); EXPECT_EQ(-1, bstr_index_of(haystack, p4)); EXPECT_EQ(13, bstr_index_of_nocase(haystack, p4)); EXPECT_EQ(16, bstr_index_of_c(haystack, "QRS")); EXPECT_EQ(-1, bstr_index_of_c(haystack, "qrs")); EXPECT_EQ(16, bstr_index_of_c_nocase(haystack, "qrs")); EXPECT_EQ(16, bstr_index_of_mem(haystack, "QRSSDF",3)); EXPECT_EQ(-1, bstr_index_of_mem(haystack, "qrssdf",3)); EXPECT_EQ(16, bstr_index_of_mem_nocase(haystack, "qrssdf",3)); bstr_free(p1); bstr_free(p2); bstr_free(p3); bstr_free(p4); bstr_free(haystack); }
TEST(BstrTest, CharAt) { bstr *str = bstr_dup_mem("ABCDEFGHIJKL\000NOPQRSTUVWXYZ", 20); EXPECT_EQ('\000', bstr_char_at(str, 12)); EXPECT_EQ(-1, bstr_char_at(str, 45)); bstr_free(str); }
static bstr *copy_or_wrap_mem(const void *data, size_t len, enum htp_alloc_strategy_t alloc) { if (alloc == HTP_ALLOC_REUSE) { return bstr_wrap_mem(data, len); } else { return bstr_dup_mem(data, len); } }
TEST(BstrTest, CharAtEnd) { bstr *str = bstr_dup_mem("ABCDEFGHIJKL\000NOPQRSTUVWXYZ", 20); EXPECT_EQ('T', bstr_char_at_end(str, 0)); EXPECT_EQ('\000', bstr_char_at_end(str, 7)); EXPECT_EQ(-1, bstr_char_at_end(str, bstr_len(str))); bstr_free(str); }
/** * Determines whether inbound parsing needs to continue or stop. In * case the data appears to be plain text HTTP, we try to continue. * * @param[in] connp * @return HTP_OK if the parser can resume parsing, HTP_DATA_BUFFER if * we need more data. */ htp_status_t htp_connp_REQ_CONNECT_PROBE_DATA(htp_connp_t *connp) { for (;;) {//;i < max_read; i++) { IN_PEEK_NEXT(connp); // Have we reached the end of the line? For some reason // we can't test after IN_COPY_BYTE_OR_RETURN */ if (connp->in_next_byte == LF || connp->in_next_byte == 0x00) break; IN_COPY_BYTE_OR_RETURN(connp); } unsigned char *data; size_t len; if (htp_connp_req_consolidate_data(connp, &data, &len) != HTP_OK) { fprintf(stderr, "htp_connp_req_consolidate_data fail"); return HTP_ERROR; } #ifdef HTP_DEBUG fprint_raw_data(stderr, "PROBING", data, len); #endif size_t pos = 0; size_t mstart = 0; // skip past leading whitespace. IIS allows this while ((pos < len) && htp_is_space(data[pos])) pos++; if (pos) mstart = pos; // The request method starts at the beginning of the // line and ends with the first whitespace character. while ((pos < len) && (!htp_is_space(data[pos]))) pos++; int methodi = HTP_M_UNKNOWN; bstr *method = bstr_dup_mem(data + mstart, pos - mstart); if (method) { methodi = htp_convert_method_to_number(method); bstr_free(method); } if (methodi != HTP_M_UNKNOWN) { #ifdef HTP_DEBUG fprint_raw_data(stderr, "htp_connp_REQ_CONNECT_PROBE_DATA: tunnel contains plain text HTTP", data, len); #endif connp->in_state = htp_connp_REQ_IDLE; } else { #ifdef HTP_DEBUG fprint_raw_data(stderr, "htp_connp_REQ_CONNECT_PROBE_DATA: tunnel is not HTTP", data, len); #endif connp->in_status = HTP_STREAM_TUNNEL; connp->out_status = HTP_STREAM_TUNNEL; } // not calling htp_connp_req_clear_buffer, we're not consuming the data return HTP_OK; }
TEST(BstrTest, DupBin) { bstr *src = bstr_dup_mem("ABCDEFGHIJKL\000NOPQRSTUVWXYZ", 20); bstr *dst; dst = bstr_dup(src); EXPECT_EQ(bstr_len(src), bstr_len(dst)); EXPECT_EQ(0, memcmp(bstr_ptr(src), bstr_ptr(dst), bstr_len(src))); bstr_free(src); bstr_free(dst); }
TEST(BstrTest, DupToC) { char *c; bstr *str = bstr_dup_mem("ABCDEFGHIJKL\000NOPQRSTUVWXYZ", 20); c = bstr_util_memdup_to_c("1234\0006789", 9); EXPECT_STREQ("1234\\06789", c); free(c); c = bstr_util_strdup_to_c(str); EXPECT_STREQ("ABCDEFGHIJKL\\0NOPQRST", c); free(c); bstr_free(str); }
/** * 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; }
htp_header_t *htp_connp_header_parse(htp_connp_t *reqp, unsigned char *data, size_t len) { htp_header_t *h = calloc(1, sizeof (htp_header_t)); if (h == NULL) return NULL; // Parse the header line if (reqp->impl_header_parse(data, len, h) < 0) { // Invalid header line h->is_parsed = 0; h->name = bstr_dup_mem(data, len); return h; } // Now extract the name and the value h->name = bstr_dup_mem(data + h->name_offset, h->name_len); h->value = bstr_dup_mem(data + h->value_offset, h->value_len); h->is_parsed = 1; // Because header names are case-insensitive, we will convert // the name to lowercase to use it as a lookup key. h->name_lowercase = bstr_to_lowercase(h->name); return h; }
/** * Base64-decode input, given as memory range. * * @param[in] data * @param[in] len * @return new base64-decoded bstring */ bstr *htp_base64_decode_mem(const void *data, size_t len) { htp_base64_decoder decoder; bstr *r = NULL; htp_base64_decoder_init(&decoder); unsigned char *tmpstr = malloc(len); if (tmpstr == NULL) return NULL; int resulting_len = htp_base64_decode(&decoder, data, len, tmpstr, len); if (resulting_len > 0) { r = bstr_dup_mem(tmpstr, resulting_len); } free(tmpstr); return r; }
/** * 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; }
// We return a HTP::URI and throw an exception on error. VALUE rbhtp_parse_uri( VALUE self, VALUE input ) { Check_Type( input, T_STRING ); bstr* input_b = bstr_dup_mem( RSTRING_PTR( input ), RSTRING_LEN( input ) ); htp_uri_t* uri = NULL; // htp_parse_uri will alloc. int result = htp_parse_uri( input_b, &uri ); if ( result != HTP_OK ) { bstr_free( &input_b ); free( uri ); rb_raise( rb_eRuntimeError, "HTP error in htp_parse_uri: %d", result ); return Qnil; // Ignored? } bstr_free( &input_b ); // Okay, as htp_parse_uri dups the data it needs. return rb_funcall( cURI, rb_intern( "new" ), 1, Data_Wrap_Struct( rb_cObject, 0, rbhtp_free_uri, uri ) ); }
TEST(BstrTest, BeginsWith) { bstr *haystack = bstr_dup_mem("ABCDEFGHIJKL\000NOPQRSTUVWXYZ", 20); bstr *p1 = bstr_dup_c("ABCD"); bstr *p2 = bstr_dup_c("aBcD"); EXPECT_EQ(1, bstr_begins_with(haystack,p1)); EXPECT_NE(1, bstr_begins_with(haystack,p2)); EXPECT_EQ(1, bstr_begins_with_nocase(haystack,p2)); EXPECT_EQ(1, bstr_begins_with_c(haystack, "AB")); EXPECT_NE(1, bstr_begins_with_c(haystack, "ab")); EXPECT_EQ(1, bstr_begins_with_c_nocase(haystack, "ab")); EXPECT_EQ(1, bstr_begins_with_mem(haystack, "ABq",2)); EXPECT_NE(1, bstr_begins_with_mem(haystack, "abq",2)); EXPECT_EQ(1, bstr_begins_with_mem_nocase(haystack, "abq",2)); bstr_free(p1); bstr_free(p2); bstr_free(haystack); }
/** * 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[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; }
/** * 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; }
/** * This method is invoked whenever a piece of data, belonging to a single field (name or value) * becomes available. It will either create a new parameter or store the transient information * until a parameter can be created. * * @param urlenp * @param data * @param startpos * @param endpos * @param c Should contain -1 if the reason this function is called is because the end of * the current data chunk is reached. */ static void htp_urlenp_add_field_piece(htp_urlenp_t *urlenp, unsigned char *data, size_t startpos, size_t endpos, int c) { // Add field if we know it ended or if we know that // we've used all of the input data if ((c != -1) || (urlenp->_complete)) { // Add field bstr *field = NULL; // Did we use the string builder for this field? if (bstr_builder_size(urlenp->_bb) > 0) { // The current field consists of more than once piece, // we have to use the string builder // Add current piece to string builder if (endpos - startpos > 0) { bstr_builder_append_mem(urlenp->_bb, (char *) data + startpos, endpos - startpos); } // Generate the field and clear the string builder field = bstr_builder_to_str(urlenp->_bb); if (field == NULL) return; bstr_builder_clear(urlenp->_bb); } else { // We only have the current piece to work with, so // no need to involve the string builder field = bstr_dup_mem((char *) data + startpos, endpos - startpos); if (field == NULL) return; } // Process the field differently, depending on the current state if (urlenp->_state == HTP_URLENP_STATE_KEY) { // Store the name for later urlenp->_name = field; if (urlenp->_complete) { // Param with key but no value bstr *name = urlenp->_name; bstr *value = bstr_dup_c(""); if (urlenp->decode_url_encoding) { // htp_uriencoding_normalize_inplace(name); htp_decode_urlencoded_inplace(urlenp->tx->connp->cfg, urlenp->tx, name); } table_addn(urlenp->params, name, value); urlenp->_name = NULL; #ifdef HTP_DEBUG fprint_raw_data(stderr, "NAME", (unsigned char *) bstr_ptr(name), bstr_len(name)); fprint_raw_data(stderr, "VALUE", (unsigned char *) bstr_ptr(value), bstr_len(value)); #endif } } else { // Param with key and value bstr *name = urlenp->_name; bstr *value = field; if (urlenp->decode_url_encoding) { htp_decode_urlencoded_inplace(urlenp->tx->connp->cfg, urlenp->tx, name); htp_decode_urlencoded_inplace(urlenp->tx->connp->cfg, urlenp->tx, value); } table_addn(urlenp->params, name, value); urlenp->_name = NULL; #ifdef HTP_DEBUG fprint_raw_data(stderr, "NAME", (unsigned char *) bstr_ptr(name), bstr_len(name)); fprint_raw_data(stderr, "VALUE", (unsigned char *) bstr_ptr(value), bstr_len(value)); #endif } } else { // Make a copy of the data and store it in an array for later if (endpos - startpos > 0) { bstr_builder_append_mem(urlenp->_bb, (char *) data + startpos, endpos - startpos); } } }
/** * Generic response line parser. * * @param[in] connp * @return HTP status */ htp_status_t htp_parse_response_line_generic(htp_connp_t *connp) { htp_tx_t *tx = connp->out_tx; unsigned char *data = bstr_ptr(tx->response_line); size_t len = bstr_len(tx->response_line); size_t pos = 0; tx->response_protocol = NULL; tx->response_protocol_number = HTP_PROTOCOL_INVALID; tx->response_status = NULL; tx->response_status_number = HTP_STATUS_INVALID; tx->response_message = NULL; // Ignore whitespace at the beginning of the line. while ((pos < len) && (htp_is_space(data[pos]))) pos++; size_t start = pos; // Find the end of the protocol string. while ((pos < len) && (!htp_is_space(data[pos]))) pos++; if (pos - start == 0) return HTP_OK; tx->response_protocol = bstr_dup_mem(data + start, pos - start); if (tx->response_protocol == NULL) return HTP_ERROR; tx->response_protocol_number = htp_parse_protocol(tx->response_protocol); #ifdef HTP_DEBUG fprint_raw_data(stderr, "Response protocol", bstr_ptr(tx->response_protocol), bstr_len(tx->response_protocol)); fprintf(stderr, "Response protocol number: %d\n", tx->response_protocol_number); #endif // Ignore whitespace after the response protocol. while ((pos < len) && (htp_is_space(data[pos]))) pos++; if (pos == len) return HTP_OK; start = pos; // Find the next whitespace character. while ((pos < len) && (!htp_is_space(data[pos]))) pos++; if (pos - start == 0) return HTP_OK; tx->response_status = bstr_dup_mem(data + start, pos - start); if (tx->response_status == NULL) return HTP_ERROR; tx->response_status_number = htp_parse_status(tx->response_status); #ifdef HTP_DEBUG fprint_raw_data(stderr, "Response status (as text)", bstr_ptr(tx->response_status), bstr_len(tx->response_status)); fprintf(stderr, "Response status number: %d\n", tx->response_status_number); #endif // Ignore whitespace that follows the status code. while ((pos < len) && (isspace(data[pos]))) pos++; if (pos == len) return HTP_OK; // Assume the message stretches until the end of the line. tx->response_message = bstr_dup_mem(data + pos, len - pos); if (tx->response_message == NULL) return HTP_ERROR; #ifdef HTP_DEBUG fprint_raw_data(stderr, "Response status message", bstr_ptr(tx->response_message), bstr_len(tx->response_message)); #endif return HTP_OK; }
/** * Transcode one bstr. * * @param[in] cd * @param[in] input * @param[in] output */ int htp_transcode_bstr(iconv_t cd, bstr *input, bstr **output) { // Reset conversion state for every new string iconv(cd, NULL, 0, NULL, 0); bstr_builder_t *bb = NULL; const size_t buflen = 10; unsigned char *buf = malloc(buflen); if (buf == NULL) { return HTP_ERROR; } const char *inbuf = (const char *)bstr_ptr(input); size_t inleft = bstr_len(input); char *outbuf = (char *)buf; size_t outleft = buflen; int loop = 1; while (loop) { loop = 0; if (iconv(cd, (ICONV_CONST char **)&inbuf, &inleft, (char **)&outbuf, &outleft) == (size_t) - 1) { if (errno == E2BIG) { // Create bstr builder on-demand if (bb == NULL) { bb = bstr_builder_create(); if (bb == NULL) { free(buf); return HTP_ERROR; } } // The output buffer is full bstr_builder_append_mem(bb, buf, buflen - outleft); outbuf = (char *)buf; outleft = buflen; // Continue in the loop, as there's more work to do loop = 1; } else { // Error if (bb != NULL) bstr_builder_destroy(bb); free(buf); return HTP_ERROR; } } } if (bb != NULL) { bstr_builder_append_mem(bb, buf, buflen - outleft); *output = bstr_builder_to_str(bb); bstr_builder_destroy(bb); if (*output == NULL) { free(buf); return HTP_ERROR; } } else { *output = bstr_dup_mem(buf, buflen - outleft); if (*output == NULL) { free(buf); return HTP_ERROR; } } free(buf); return HTP_OK; }
/** * This method is invoked whenever a piece of data, belonging to a single field (name or value) * becomes available. It will either create a new parameter or store the transient information * until a parameter can be created. * * @param[in] urlenp * @param[in] data * @param[in] startpos * @param[in] endpos * @param[in] c Should contain -1 if the reason this function is called is because the end of * the current data chunk is reached. */ static void htp_urlenp_add_field_piece(htp_urlenp_t *urlenp, const unsigned char *data, size_t startpos, size_t endpos, int last_char) { // Add field if we know it ended (last_char is something other than -1) // or if we know that there won't be any more input data (urlenp->_complete is true). if ((last_char != -1) || (urlenp->_complete)) { // Prepare the field value, assembling from multiple pieces as necessary. bstr *field = NULL; // Did we use the string builder for this field? if (bstr_builder_size(urlenp->_bb) > 0) { // The current field consists of more than once piece, we have to use the string builder. // Add current piece to string builder. if ((data != NULL) && (endpos - startpos > 0)) { bstr_builder_append_mem(urlenp->_bb, data + startpos, endpos - startpos); } // Generate the field and clear the string builder. field = bstr_builder_to_str(urlenp->_bb); if (field == NULL) return; bstr_builder_clear(urlenp->_bb); } else { // We only have the current piece to work with, so no need to involve the string builder. if ((data != NULL) && (endpos - startpos > 0)) { field = bstr_dup_mem(data + startpos, endpos - startpos); if (field == NULL) return; } } // Process field as key or value, as appropriate. if (urlenp->_state == HTP_URLENP_STATE_KEY) { // Key. // If there is no more work left to do, then we have a single key. Add it. if ((urlenp->_complete)||(last_char == urlenp->argument_separator)) { // Handling empty pairs is tricky. We don't want to create a pair for // an entirely empty input, but in some cases it may be appropriate // (e.g., /index.php?&q=2). if ((field != NULL)||(last_char == urlenp->argument_separator)) { // Add one pair, with an empty value and possibly empty key too. bstr *name = field; if (name == NULL) { name = bstr_dup_c(""); if (name == NULL) return; } bstr *value = bstr_dup_c(""); if (value == NULL) { bstr_free(name); return; } if (urlenp->decode_url_encoding) { htp_tx_urldecode_params_inplace(urlenp->tx, name); } htp_table_addn(urlenp->params, name, value); urlenp->_name = NULL; #ifdef HTP_DEBUG fprint_raw_data(stderr, "NAME", bstr_ptr(name), bstr_len(name)); fprint_raw_data(stderr, "VALUE", bstr_ptr(value), bstr_len(value)); #endif } } else { // This key will possibly be followed by a value, so keep it for later. urlenp->_name = field; } } else { // Value (with a key remembered from before). bstr *name = urlenp->_name; urlenp->_name = NULL; if (name == NULL) { name = bstr_dup_c(""); if (name == NULL) { bstr_free(field); return; } } bstr *value = field; if (value == NULL) { value = bstr_dup_c(""); if (value == NULL) { bstr_free(name); return; } } if (urlenp->decode_url_encoding) { htp_tx_urldecode_params_inplace(urlenp->tx, name); htp_tx_urldecode_params_inplace(urlenp->tx, value); } htp_table_addn(urlenp->params, name, value); #ifdef HTP_DEBUG fprint_raw_data(stderr, "NAME", bstr_ptr(name), bstr_len(name)); fprint_raw_data(stderr, "VALUE", bstr_ptr(value), bstr_len(value)); #endif } } else { // The field has not ended. We'll make a copy of of the available data for later. if ((data != NULL) && (endpos - startpos > 0)) { bstr_builder_append_mem(urlenp->_bb, data + startpos, endpos - startpos); } } }
/** * This is a proof-of-concept processor that processes parameter names in * a way _similar_ to PHP. Whitespace at the beginning is removed, and the * remaining whitespace characters are converted to underscores. Proper * research of PHP's behavior is needed before we can claim to be emulating it. * * @param[in,out] p * @return HTP_OK on success, HTP_ERROR on failure. */ htp_status_t htp_php_parameter_processor(htp_param_t *p) { if (p == NULL) return HTP_ERROR; // Name transformation bstr *new_name = NULL; // Ignore whitespace characters at the beginning of parameter name. unsigned char *data = bstr_ptr(p->name); size_t len = bstr_len(p->name); size_t pos = 0; // Advance over any whitespace characters at the beginning of the name. while ((pos < len) && (isspace(data[pos]))) pos++; // Have we seen any whitespace? if (pos > 0) { // Make a copy of the name, starting with // the first non-whitespace character. new_name = bstr_dup_mem(data + pos, len - pos); if (new_name == NULL) return HTP_ERROR; } // Replace remaining whitespace characters with underscores. size_t offset = pos; pos = 0; // Advance to the end of name or to the first whitespace character. while ((offset + pos < len)&&(!isspace(data[pos]))) pos++; // Are we at the end of the name? if (offset + pos < len) { // Seen whitespace within the string. // Make a copy of the name if needed (which would be the case // with a parameter that does not have any whitespace in front). if (new_name == NULL) { new_name = bstr_dup(p->name); if (new_name == NULL) return HTP_ERROR; } // Change the pointers to the new name and ditch the offset. data = bstr_ptr(new_name); len = bstr_len(new_name); // Replace any whitespace characters in the copy with underscores. while (pos < len) { if (isspace(data[pos])) { data[pos] = '_'; } pos++; } } // If we made any changes, free the old parameter name and put the new one in. if (new_name != NULL) { bstr_free(p->name); p->name = new_name; } 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 one part header. * * @param data * @param len * @param Success indication */ int htp_mpartp_parse_header(htp_mpart_part_t *part, unsigned char *data, size_t len) { size_t name_start, name_end; size_t value_start, value_end; name_start = 0; // Look for the colon size_t colon_pos = 0; while ((colon_pos < len) && (data[colon_pos] != ':')) colon_pos++; if (colon_pos == len) { // Missing colon // TODO Error message return -1; } if (colon_pos == 0) { // Empty header name // TODO Error message } name_end = colon_pos; // Ignore LWS after field-name size_t prev = name_end; while ((prev > name_start) && (htp_is_lws(data[prev - 1]))) { prev--; name_end--; // LWS after field name // TODO Error message } // 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) 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])) { // Request field is not a token // TODO Error message break; } i++; } // Now extract the name and the value htp_header_t *h = calloc(1, sizeof (htp_header_t)); if (h == NULL) return -1; h->name = bstr_dup_mem((char *) data + name_start, name_end - name_start); h->value = bstr_dup_mem((char *) data + value_start, value_end - value_start); // Check if the header already exists htp_header_t * h_existing = table_get(part->headers, h->name); if (h_existing != NULL) { // 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); return -1; } h_existing->value = new_value; bstr_add_mem_noex(h_existing->value, ", ", 2); bstr_add_noex(h_existing->value, h->value); // The header is 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 table_add(part->headers, h->name, h); } return 1; }
/** * Process part headers. In the current implementation, we only parse the * Content-Disposition header if it is present. * * @param part * @return Success indication */ int htp_mpart_part_process_headers(htp_mpart_part_t *part) { // Find C-D header htp_header_t *h = (htp_header_t *) table_get_c(part->headers, "content-disposition"); if (h == NULL) { // TODO Error message return 0; } if (bstr_index_of_c(h->value, "form-data") != 0) { return -1; } // The parsing starts here unsigned char *data = (unsigned char *) bstr_ptr(h->value); size_t len = bstr_len(h->value); size_t pos = 9; // Start after "form-data" // Main parameter parsing loop (once per parameter) while (pos < len) { // Find semicolon and go over it while ((pos < len) && ((data[pos] == '\t') || (data[pos] == ' '))) pos++; if (pos == len) return -2; // Semicolon if (data[pos] != ';') return -3; pos++; // Go over the whitespace before parameter name while ((pos < len) && ((data[pos] == '\t') || (data[pos] == ' '))) pos++; if (pos == len) return -4; // Found starting position (name) size_t start = pos; // Look for ending position while ((pos < len) && (data[pos] != '\t') && (data[pos] != ' ') && (data[pos] != '=')) pos++; if (pos == len) return -5; // Ending position is in "pos" now // Is it a parameter we are interested in? int param_type = htp_mpartp_cd_param_type(data, start, pos); // Ignore whitespace while ((pos < len) && ((data[pos] == '\t') || (data[pos] == ' '))) pos++; if (pos == len) return -6; // Equals if (data[pos] != '=') return -7; pos++; // Go over the whitespace before value while ((pos < len) && ((data[pos] == '\t') || (data[pos] == ' '))) pos++; if (pos == len) return -8; // Found starting point (value) start = pos; // Quoting char indicator int qchar = -1; // Different handling for quoted and bare strings if (data[start] == '"') { // Quoted string qchar = data[start]; start = ++pos; // Find the end of the value while ((pos < len) && (data[pos] != qchar)) { if (data[pos] == '\\') { // Ignore invalid quoting pairs if (pos + 1 < len) return -9; // Go over the quoted character pos++; } pos++; } } else { // Bare string while ((pos < len) && (!htp_is_token(data[pos]))) pos++; } switch (param_type) { case PARAM_NAME: // TODO Unquote quoted characters part->name = bstr_dup_mem((char *) data + start, pos - start); if (part->name == NULL) return -1; break; case PARAM_FILENAME: // TODO Unquote quoted characters part->file = calloc(1, sizeof (htp_file_t)); if (part->file == NULL) return -1; part->file->filename = bstr_dup_mem((char *) data + start, pos - start); if (part->file->filename == NULL) return -1; part->file->source = HTP_FILE_MULTIPART; break; default: // Ignore unknown parameter // TODO Warn/log? break; } // Skip over the quoting character if (qchar != -1) { pos++; } // Continue to parse the next parameter, if any } return 1; }
/** * 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; }
htp_status_t htp_parse_request_line_generic_ex(htp_connp_t *connp, int nul_terminates) { htp_tx_t *tx = connp->in_tx; unsigned char *data = bstr_ptr(tx->request_line); size_t len = bstr_len(tx->request_line); size_t pos = 0; size_t mstart = 0; size_t start; size_t bad_delim; if (nul_terminates) { // The line ends with the first NUL byte. size_t newlen = 0; while ((pos < len) && (data[pos] != '\0')) { pos++; newlen++; } // Start again, with the new length. len = newlen; pos = 0; } // skip past leading whitespace. IIS allows this while ((pos < len) && htp_is_space(data[pos])) pos++; if (pos) { htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Request line: leading whitespace"); mstart = pos; if (connp->cfg->requestline_leading_whitespace_unwanted != HTP_UNWANTED_IGNORE) { // reset mstart so that we copy the whitespace into the method mstart = 0; // set expected response code to this anomaly tx->response_status_expected_number = connp->cfg->requestline_leading_whitespace_unwanted; } } // The request method starts at the beginning of the // line and ends with the first whitespace character. while ((pos < len) && (!htp_is_space(data[pos]))) pos++; // No, we don't care if the method is empty. tx->request_method = bstr_dup_mem(data + mstart, pos - mstart); if (tx->request_method == NULL) return HTP_ERROR; #ifdef HTP_DEBUG fprint_raw_data(stderr, __FUNCTION__, bstr_ptr(tx->request_method), bstr_len(tx->request_method)); #endif tx->request_method_number = htp_convert_method_to_number(tx->request_method); bad_delim = 0; // Ignore whitespace after request method. The RFC allows // for only one SP, but then suggests any number of SP and HT // should be permitted. Apache uses isspace(), which is even // more permitting, so that's what we use here. while ((pos < len) && (isspace(data[pos]))) { if (!bad_delim && data[pos] != 0x20) { bad_delim++; } pos++; } if (bad_delim) { htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Request line: non-compliant delimiter between Method and URI"); } // Is there anything after the request method? if (pos == len) { // No, this looks like a HTTP/0.9 request. tx->is_protocol_0_9 = 1; tx->request_protocol_number = HTP_PROTOCOL_0_9; return HTP_OK; } start = pos; bad_delim = 0; // The URI ends with the first whitespace. while ((pos < len) && (data[pos] != 0x20)) { if (!bad_delim && htp_is_space(data[pos])) { bad_delim++; } pos++; } /* if we've seen some 'bad' delimiters, we retry with those */ if (bad_delim && pos == len) { // special case: even though RFC's allow only SP (0x20), many // implementations allow other delimiters, like tab or other // characters that isspace() accepts. pos = start; while ((pos < len) && (!htp_is_space(data[pos]))) pos++; } if (bad_delim) { // warn regardless if we've seen non-compliant chars htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Request line: URI contains non-compliant delimiter"); } tx->request_uri = bstr_dup_mem(data + start, pos - start); if (tx->request_uri == NULL) return HTP_ERROR; #ifdef HTP_DEBUG fprint_raw_data(stderr, __FUNCTION__, bstr_ptr(tx->request_uri), bstr_len(tx->request_uri)); #endif // Ignore whitespace after URI. while ((pos < len) && (htp_is_space(data[pos]))) pos++; // Is there protocol information available? if (pos == len) { // No, this looks like a HTTP/0.9 request. tx->is_protocol_0_9 = 1; tx->request_protocol_number = HTP_PROTOCOL_0_9; return HTP_OK; } // The protocol information continues until the end of the line. tx->request_protocol = bstr_dup_mem(data + pos, len - pos); if (tx->request_protocol == NULL) return HTP_ERROR; tx->request_protocol_number = htp_parse_protocol(tx->request_protocol); #ifdef HTP_DEBUG fprint_raw_data(stderr, __FUNCTION__, bstr_ptr(tx->request_protocol), bstr_len(tx->request_protocol)); #endif 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; } } }
htp_status_t bstr_builder_append_mem(bstr_builder_t *bb, const void *data, size_t len) { bstr *b = bstr_dup_mem(data, len); if (b == NULL) return HTP_ERROR; return htp_list_push(bb->pieces, b); }