/** * 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; }
/** * Parses a message header line as Apache 2.2 does. * * @param connp * @param h * @param data * @param len * @return HTP_OK or HTP_ERROR */ int htp_parse_request_header_apache_2_2(htp_connp_t *connp, htp_header_t *h, unsigned char *data, size_t len) { size_t name_start, name_end; size_t value_start, value_end; htp_chomp(data, &len); name_start = 0; // Look for the colon size_t colon_pos = 0; while ((colon_pos < len) && (data[colon_pos] != '\0') && (data[colon_pos] != ':')) colon_pos++; if ((colon_pos == len) || (data[colon_pos] == '\0')) { // Missing colon h->flags |= HTP_FIELD_UNPARSEABLE; if (!(connp->in_tx->flags & HTP_FIELD_UNPARSEABLE)) { connp->in_tx->flags |= HTP_FIELD_UNPARSEABLE; // Only log once per transaction htp_log(connp, HTP_LOG_MARK, HTP_LOG_ERROR, 0, "Request field invalid: colon missing"); } return HTP_ERROR; } if (colon_pos == 0) { // Empty header name h->flags |= HTP_FIELD_INVALID; if (!(connp->in_tx->flags & HTP_FIELD_INVALID)) { connp->in_tx->flags |= HTP_FIELD_INVALID; // Only log once per transaction htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Request field invalid: empty name"); } } name_end = colon_pos; // Ignore LWS after field-name size_t prev = name_end - 1; while ((prev > name_start) && (htp_is_lws(data[prev]))) { prev--; name_end--; h->flags |= HTP_FIELD_INVALID; if (!(connp->in_tx->flags & HTP_FIELD_INVALID)) { connp->in_tx->flags |= HTP_FIELD_INVALID; htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Request field invalid: LWS after name"); } } // Value value_start = colon_pos; // Go over the colon if (value_start < len) { value_start++; } // Ignore LWS before field-content while ((value_start < len) && (htp_is_lws(data[value_start]))) { value_start++; } // Look for the end of field-content value_end = value_start; while ((value_end < len) && (data[value_end] != '\0')) value_end++; // Ignore LWS after field-content prev = value_end - 1; while ((prev > value_start) && (htp_is_lws(data[prev]))) { prev--; value_end--; } // Check that the header name is a token size_t i = name_start; while (i < name_end) { if (!htp_is_token(data[i])) { h->flags |= HTP_FIELD_INVALID; if (!(connp->in_tx->flags & HTP_FIELD_INVALID)) { connp->in_tx->flags |= HTP_FIELD_INVALID; htp_log(connp, HTP_LOG_MARK, HTP_LOG_WARNING, 0, "Request header name is not a token"); } break; } i++; } // Now extract the name and the value h->name = bstr_memdup((char *) data + name_start, name_end - name_start); h->value = bstr_memdup((char *) data + value_start, value_end - value_start); return HTP_OK; }
/** * Parses 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; }
/** * 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; }
int htp_header_parse_internal_strict(unsigned char *data, size_t len, htp_header_t *h) { size_t name_start, name_end; size_t value_start, value_end; // Deal with the name first name_start = name_end = 0; // Find where the header name ends while (name_end < len) { if (htp_is_lws(data[name_end]) || data[name_end] == ':') break; name_end++; } if (name_end == 0) { // Empty header name return -1; } if (name_end == len) { // TODO return -1; } // Is there any LWS before colon? size_t pos = name_end; while (pos < len) { if (!htp_is_lws(data[pos])) break; pos++; // TODO // return -1; } if (pos == len) { // TODO return -1; } // The next character must be a colon if (data[pos] != ':') { // TODO return -1; } // Move over the colon pos++; // Again, ignore any LWS while (pos < len) { if (!htp_is_lws(data[pos])) break; pos++; } if (pos == len) { // TODO return -1; } value_start = value_end = pos; while (value_end < len) { if (htp_is_lws(data[value_end])) break; value_end++; } h->name_offset = name_start; h->name_len = name_end - name_start; h->value_offset = value_start; h->value_len = value_end - value_start; return 1; }