nhttp_msg_header.h
nhttp_msg_body.cc
nhttp_msg_body.h
- nhttp_msg_chunk_head.cc
- nhttp_msg_chunk_head.h
- nhttp_msg_chunk_body.cc
- nhttp_msg_chunk_body.h
+ nhttp_msg_chunk.cc
+ nhttp_msg_chunk.h
nhttp_msg_trailer.cc
nhttp_msg_trailer.h
nhttp_head_norm.cc
nhttp_msg_head_shared.cc nhttp_msg_head_shared.h \
nhttp_msg_header.cc nhttp_msg_header.h \
nhttp_msg_body.cc nhttp_msg_body.h \
-nhttp_msg_chunk_head.cc nhttp_msg_chunk_head.h \
-nhttp_msg_chunk_body.cc nhttp_msg_chunk_body.h \
+nhttp_msg_chunk.cc nhttp_msg_chunk.h \
nhttp_msg_trailer.cc nhttp_msg_trailer.h \
nhttp_head_norm.cc nhttp_head_norm.h \
nhttp_uri.cc nhttp_uri.h \
// Type of message section
typedef enum { SEC_DISCARD = -10, SEC_CLOSED = -9, SEC_ABORT = -8, SEC__NOTCOMPUTE=-4, SEC__NOTPRESENT=-1, SEC_REQUEST = 2,
- SEC_STATUS, SEC_HEADER, SEC_BODY, SEC_CHUNKHEAD, SEC_CHUNKBODY, SEC_TRAILER } SectionType;
+ SEC_STATUS, SEC_HEADER, SEC_BODY, SEC_CHUNK, SEC_TRAILER } SectionType;
// Result of scanning by splitter
-typedef enum { SCAN_NOTFOUND, SCAN_FOUND, SCAN_DISCARD } ScanResult;
+typedef enum { SCAN_NOTFOUND, SCAN_FOUND, SCAN_DISCARD, SCAN_ABORT } ScanResult;
// Result of processing a message section--what needs to happen next
-typedef enum { RES_INSPECT, RES_IGNORE, RES_AGGREGATE, RES_FLUSHCHUNKS } ProcessResult;
+typedef enum { RES_INSPECT, RES_IGNORE, RES_AGGREGATE } ProcessResult;
// List of possible HTTP versions. Version 0.9 omitted because 0.9 predates creation of the HTTP/X.Y token. There would
// never be a message with "HTTP/0.9"
data_length[source_id] = STAT_NOTPRESENT;
body_octets[source_id] = STAT_NOTPRESENT;
- num_chunks[source_id] = STAT_NOTPRESENT;
}
void NHttpFlowData::show(FILE* out_file) const {
fprintf(out_file, "Type expected: %d/%d\n", type_expected[0], type_expected[1]);
fprintf(out_file, "Data length: %" PRIi64 "/%" PRIi64 "\n", data_length[0], data_length[1]);
fprintf(out_file, "Body octets: %" PRIi64 "/%" PRIi64 "\n", body_octets[0], body_octets[1]);
- fprintf(out_file, "Number of chunks: %" PRIi64 "/%" PRIi64 "\n", num_chunks[0], num_chunks[1]);
fprintf(out_file, "Unused octets visible: %u/%u\n", unused_octets_visible[0], unused_octets_visible[1]);
fprintf(out_file, "Header octets visible: %u/%u\n", header_octets_visible[0], header_octets_visible[1]);
fprintf(out_file, "Section buffer length: %d/%d\n", section_buffer_length[0], section_buffer_length[1]);
friend class NHttpMsgRequest;
friend class NHttpMsgStatus;
friend class NHttpMsgBody;
- friend class NHttpMsgChunkHead;
- friend class NHttpMsgChunkBody;
+ friend class NHttpMsgChunk;
friend class NHttpMsgTrailer;
friend class NHttpStreamSplitter;
friend class NHttpTransaction;
NHttpRequestSplitter request_splitter[2];
NHttpStatusSplitter status_splitter[2];
NHttpHeaderSplitter header_splitter[2];
- NHttpChunkHeaderSplitter chunkhead_splitter[2];
+ NHttpChunkSplitter chunk_splitter[2];
NHttpTrailerSplitter trailer_splitter[2];
uint32_t unused_octets_visible[2] = { 0, 0 };
uint32_t header_octets_visible[2] = { 0, 0 };
// Inspector => StreamSplitter (facts about the message section that is coming next)
NHttpEnums::SectionType type_expected[2] = { NHttpEnums::SEC_REQUEST, NHttpEnums::SEC_STATUS };
- int64_t data_length[2] = { NHttpEnums::STAT_NOTPRESENT, NHttpEnums::STAT_NOTPRESENT }; // length of the data from Content-Length field or chunk header.
+ int64_t data_length[2] = { NHttpEnums::STAT_NOTPRESENT, NHttpEnums::STAT_NOTPRESENT }; // length of the data from Content-Length field
// Inspector's internal data about the current message
// Some items don't apply in both directions. Have two copies anyway just to simplify code and minimize
int32_t status_code_num[2] = { NHttpEnums::STAT_NOTPRESENT, NHttpEnums::STAT_NOTPRESENT };
int64_t body_octets[2] = { NHttpEnums::STAT_NOTPRESENT, NHttpEnums::STAT_NOTPRESENT }; // number of user data octets seen so far (regular body or chunks)
- int64_t num_chunks[2] = { NHttpEnums::STAT_NOTPRESENT, NHttpEnums::STAT_NOTPRESENT }; // number of chunks seen so far
// Transaction management including pipelining
NHttpTransaction* transaction[2] = { nullptr, nullptr };
case SEC_STATUS: msg_section = new NHttpMsgStatus(data, dsize, session_data, source_id, buf_owner); break;
case SEC_HEADER: msg_section = new NHttpMsgHeader(data, dsize, session_data, source_id, buf_owner); break;
case SEC_BODY: msg_section = new NHttpMsgBody(data, dsize, session_data, source_id, buf_owner); break;
- case SEC_CHUNKHEAD: msg_section = new NHttpMsgChunkHead(data, dsize, session_data, source_id, buf_owner); break;
- case SEC_CHUNKBODY: msg_section = new NHttpMsgChunkBody(data, dsize, session_data, source_id, buf_owner); break;
+ case SEC_CHUNK: msg_section = new NHttpMsgChunk(data, dsize, session_data, source_id, buf_owner); break;
case SEC_TRAILER: msg_section = new NHttpMsgTrailer(data, dsize, session_data, source_id, buf_owner); break;
case SEC_DISCARD: if (buf_owner) delete[] data; return RES_IGNORE;
default: assert(0); if (buf_owner) delete[] data; return RES_IGNORE;
#include "nhttp_msg_status.h"
#include "nhttp_msg_header.h"
#include "nhttp_msg_body.h"
-#include "nhttp_msg_chunk_head.h"
-#include "nhttp_msg_chunk_body.h"
+#include "nhttp_msg_chunk.h"
#include "nhttp_msg_trailer.h"
#include "nhttp_test_manager.h"
#include "nhttp_stream_splitter.h"
//
// @author Tom Peters <thopeter@cisco.com>
//
-// @brief NHttpMsgChunkBody class analyzes data portion (not start line) of an HTTP chunk.
+// @brief NHttpMsgChunk class analyzes HTTP chunked body.
//
#include "snort.h"
#include "nhttp_enum.h"
-#include "nhttp_msg_chunk_body.h"
+#include "nhttp_msg_chunk.h"
using namespace NHttpEnums;
-NHttpMsgChunkBody::NHttpMsgChunkBody(const uint8_t *buffer, const uint16_t buf_size, NHttpFlowData *session_data_,
+NHttpMsgChunk::NHttpMsgChunk(const uint8_t *buffer, const uint16_t buf_size, NHttpFlowData *session_data_,
SourceId source_id_, bool buf_owner) : NHttpMsgBody(buffer, buf_size, session_data_, source_id_, buf_owner)
{
transaction->set_body(this);
}
-void NHttpMsgChunkBody::analyze() {
- body_octets += msg_text.length;
+void NHttpMsgChunk::gen_events() {}
- data.start = msg_text.start;
- data.length = msg_text.length;
-
- if (tcp_close) infractions |= INF_TRUNCATED;
-}
-
-
-void NHttpMsgChunkBody::gen_events() {}
-
-void NHttpMsgChunkBody::print_section(FILE *output) {
- NHttpMsgSection::print_message_title(output, "chunk body");
+void NHttpMsgChunk::print_section(FILE *output) {
+ NHttpMsgSection::print_message_title(output, "chunk");
fprintf(output, "Cumulative octets %" PRIi64 "\n", body_octets);
data.print(output, "Data");
NHttpMsgSection::print_message_wrapup(output);
}
-void NHttpMsgChunkBody::update_flow() {
+void NHttpMsgChunk::update_flow() {
if (tcp_close) {
session_data->type_expected[source_id] = SEC_CLOSED;
session_data->half_reset(source_id);
}
else {
+ // Zero-length chunk is not visible here. StreamSplitter updates expected_type to SEC_TRAILER when necessary.
session_data->body_octets[source_id] = body_octets;
}
}
//
// @author Tom Peters <thopeter@cisco.com>
//
-// @brief NHttpMsgChunkBody class declaration
+// @brief NHttpMsgChunk class declaration
//
-#ifndef NHTTP_MSG_CHUNK_BODY_H
-#define NHTTP_MSG_CHUNK_BODY_H
+#ifndef NHTTP_MSG_CHUNK_H
+#define NHTTP_MSG_CHUNK_H
#include "nhttp_msg_body.h"
//-------------------------------------------------------------------------
-// NHttpMsgChunkBody class
+// NHttpMsgChunk class
//-------------------------------------------------------------------------
-class NHttpMsgChunkBody : public NHttpMsgBody {
+class NHttpMsgChunk : public NHttpMsgBody {
public:
- NHttpMsgChunkBody(const uint8_t *buffer, const uint16_t buf_size, NHttpFlowData *session_data_,
+ NHttpMsgChunk(const uint8_t *buffer, const uint16_t buf_size, NHttpFlowData *session_data_,
NHttpEnums::SourceId source_id_, bool buf_owner);
- void analyze();
void print_section(FILE *output);
void gen_events();
void update_flow();
+++ /dev/null
-/****************************************************************************
- *
-** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved.
- * Copyright (C) 2003-2013 Sourcefire, Inc.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License Version 2 as
- * published by the Free Software Foundation. You may not use, modify or
- * distribute this program under any other version of the GNU General
- * Public License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- *
- ****************************************************************************/
-
-//
-// @author Tom Peters <thopeter@cisco.com>
-//
-// @brief NHttpMsgChunkHead class analyzes header line for a chunk.
-//
-
-
-#include <assert.h>
-#include <string.h>
-#include <sys/types.h>
-#include <stdio.h>
-
-#include "snort.h"
-#include "nhttp_enum.h"
-#include "nhttp_msg_chunk_head.h"
-
-using namespace NHttpEnums;
-
-NHttpMsgChunkHead::NHttpMsgChunkHead(const uint8_t *buffer, const uint16_t buf_size, NHttpFlowData *session_data_,
- SourceId source_id_, bool buf_owner) :
- NHttpMsgSection(buffer, buf_size, session_data_, source_id_, buf_owner),
- num_chunks(session_data->num_chunks[source_id])
-{
- transaction->set_body(this);
-}
-
-
-// Convert the hexadecimal chunk length.
-// RFC says that zero may be written with multiple digits "000000".
-// Arbitrary limit of 15 hex digits not including leading zeros ensures in a simple way against 64-bit overflow and should be
-// vastly bigger than any legitimate chunk.
-void NHttpMsgChunkHead::derive_chunk_length() {
- if (chunk_size.length <= 0) {
- data_length = STAT_NOSOURCE;
- infractions |= INF_BADCHUNKSIZE;
- return;
- }
- data_length = 0;
- int non_leading_zeros = 0;
- for (int k=0; k < chunk_size.length; k++) {
- if (non_leading_zeros || (chunk_size.start[k] != '0')) non_leading_zeros++;
- if (non_leading_zeros > 15) {
- data_length = STAT_PROBLEMATIC;
- infractions |= INF_BADCHUNKSIZE;
- return;
- }
-
- data_length *= 16;
- if ((chunk_size.start[k] >= '0') && (chunk_size.start[k] <= '9')) data_length += chunk_size.start[k] - '0';
- else if ((chunk_size.start[k] >= 'A') && (chunk_size.start[k] <= 'F')) data_length += chunk_size.start[k] - 'A' + 10;
- else if ((chunk_size.start[k] >= 'a') && (chunk_size.start[k] <= 'f')) data_length += chunk_size.start[k] - 'a' + 10;
- else {
- data_length = STAT_PROBLEMATIC;
- infractions |= INF_BADCHUNKSIZE;
- return;
- }
- }
-}
-
-void NHttpMsgChunkHead::analyze() {
- // First section in a new chunk is just the start line.
- num_chunks++;
- start_line.start = msg_text.start;
- if (!tcp_close) {
- start_line.length = msg_text.length - 2;
- }
- else {
- start_line.length = find_crlf(start_line.start, msg_text.length, false);
- }
- chunk_size.start = msg_text.start;
- // Start line format is chunk size in hex followed by optional semicolon and extensions field
- for (chunk_size.length = 0; (chunk_size.length < start_line.length) && (start_line.start[chunk_size.length] != ';'); chunk_size.length++);
- if (chunk_size.length == start_line.length) {
- chunk_extensions.length = STAT_NOTPRESENT;
- }
- else if (chunk_size.length == start_line.length - 1) {
- chunk_extensions.length = STAT_EMPTYSTRING;
- }
- else {
- chunk_extensions.start = msg_text.start + chunk_size.length + 1;
- chunk_extensions.length = start_line.length - chunk_size.length - 1;
- }
- derive_chunk_length();
- if (tcp_close) infractions |= INF_TRUNCATED;
-}
-
-void NHttpMsgChunkHead::gen_events() {}
-
-void NHttpMsgChunkHead::print_section(FILE *output) {
- NHttpMsgSection::print_message_title(output, "chunk header");
- fprintf(output, "Chunk size: %" PRIi64 "\n", data_length);
- chunk_extensions.print(output, "Chunk extensions");
- NHttpMsgSection::print_message_wrapup(output);
-}
-
-void NHttpMsgChunkHead::update_flow() {
- if (tcp_close) {
- session_data->type_expected[source_id] = SEC_CLOSED;
- session_data->half_reset(source_id);
- }
- else if (data_length > 0) {
- session_data->type_expected[source_id] = SEC_CHUNKBODY;
- session_data->num_chunks[source_id] = num_chunks;
- session_data->data_length[source_id] = data_length;
- }
- else { // FIXIT-H what if data_length is bad (probable loss of sync)?
- // This was zero-length last chunk, trailer comes next
- session_data->type_expected[source_id] = SEC_TRAILER;
- }
-}
-
-ProcessResult NHttpMsgChunkHead::worth_detection() {
- return ((data_length > 0) && !tcp_close) ? RES_IGNORE : RES_FLUSHCHUNKS;
-}
-
-// Legacy support function. Puts message fields into the buffers used by old Snort.
-void NHttpMsgChunkHead::legacy_clients() {
- ClearHttpBuffers();
- legacy_request();
- legacy_status();
- legacy_header(false);
- if (start_line.length > 0) SetHttpBuffer(HTTP_BUFFER_CLIENT_BODY, start_line.start, (unsigned)start_line.length);
-}
-
-
-
-
-
-
-
-
-
-
+++ /dev/null
-/****************************************************************************
- *
-** Copyright (C) 2014 Cisco and/or its affiliates. All rights reserved.
- * Copyright (C) 2003-2013 Sourcefire, Inc.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License Version 2 as
- * published by the Free Software Foundation. You may not use, modify or
- * distribute this program under any other version of the GNU General
- * Public License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- *
- ****************************************************************************/
-
-//
-// @author Tom Peters <thopeter@cisco.com>
-//
-// @brief NHttpMsgChunkHead class declaration
-//
-
-#ifndef NHTTP_MSG_CHUNK_HEAD_H
-#define NHTTP_MSG_CHUNK_HEAD_H
-
-#include "nhttp_msg_section.h"
-#include "nhttp_field.h"
-
-//-------------------------------------------------------------------------
-// NHttpMsgChunkHead class
-//-------------------------------------------------------------------------
-
-class NHttpMsgChunkHead : public NHttpMsgSection {
-public:
- NHttpMsgChunkHead(const uint8_t *buffer, const uint16_t buf_size, NHttpFlowData *session_data_,
- NHttpEnums::SourceId source_id_, bool buf_owner);
- void analyze();
- void print_section(FILE *output);
- void gen_events();
- void update_flow();
- void legacy_clients();
- NHttpEnums::ProcessResult worth_detection();
-
-private:
- void derive_chunk_length();
-
- Field start_line;
- Field chunk_size;
- Field chunk_extensions;
-
- int64_t data_length = NHttpEnums::STAT_NOTCOMPUTE;
- int64_t num_chunks;
-};
-
-#endif
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
(get_header_value_norm(HEAD_TRANSFER_ENCODING).length - 8))) == TRANSCODE_CHUNKED) ) {
// FIXIT-M inspect for Content-Length header which should not be present
// Chunked body
- session_data->type_expected[source_id] = SEC_CHUNKHEAD;
+ session_data->type_expected[source_id] = SEC_CHUNK;
session_data->body_octets[source_id] = 0;
- session_data->num_chunks[source_id] = 0;
}
else if ((get_header_value_norm(HEAD_CONTENT_LENGTH).length > 0) &&
(*(int64_t*)header_value_norm[HEAD_CONTENT_LENGTH].start > 0)) {
// @brief Section-specific splitters
//
+//#include "nhttp_uri_norm.h"
#include "nhttp_splitter.h"
using namespace NHttpEnums;
+void NHttpSplitter::conditional_reset() {
+ if (complete) {
+ octets_seen = 0;
+ num_crlf = 0;
+ num_flush = 0;
+ complete = false;
+ }
+}
+
ScanResult NHttpRequestSplitter::split(const uint8_t* buffer, uint32_t length) {
+ conditional_reset();
for (uint32_t k = 0; k < length; k++) {
// Count the alternating <CR> and <LF> characters we have seen in a row
if (((buffer[k] == '\r') && (num_crlf == 0)) ||
}
num_flush = k+1;
// If the first two octets are CRLF then they must be discarded.
+ complete = true;
return ((octets_seen + k + 1) == 2) ? SCAN_DISCARD : SCAN_FOUND;
}
octets_seen += length;
}
ScanResult NHttpStatusSplitter::split(const uint8_t* buffer, uint32_t length) {
+ conditional_reset();
for (uint32_t k = 0; k < length; k++) {
// Count the alternating <CR> and <LF> characters we have seen in a row
if (((buffer[k] == '\r') && (num_crlf == 0)) ||
return SCAN_NOTFOUND;
}
-ScanResult NHttpChunkHeaderSplitter::split(const uint8_t* buffer, uint32_t length) {
+ScanResult NHttpChunkSplitter::split(const uint8_t* buffer, uint32_t length) {
+ conditional_reset();
+ if (header_complete) {
+ // Previously read the chunk header. Now just flush the length.
+ num_flush = expected_length;
+ complete = true;
+ return SCAN_FOUND;
+ }
for (uint32_t k = 0; k < length; k++) {
- // Count the alternating <CR> and <LF> characters we have seen in a row
- if (((buffer[k] == '\r') && (num_crlf == 0)) ||
- ((buffer[k] == '\n') && (num_crlf == 1))) {
- num_crlf++;
- if (num_crlf < 2) {
- continue;
+ if (buffer[k] == '\r') {
+ num_crlf = 1;
+ continue;
+ }
+ if ((buffer[k] == '\n') && (num_crlf == 1)) {
+ if ((octets_seen + k + 1) == 2) {
+ // \r\n leftover from previous chunk
+ complete = true;
+ num_flush = k+1;
+ return SCAN_DISCARD;
+ }
+ if (!length_started) {
+ // chunk header specifies no length
+ complete = true;
+ return SCAN_ABORT;
}
+ // flush completed chunk header
+ header_complete = true;
+ num_flush = k+1;
+ return SCAN_DISCARD;
}
- else {
- num_crlf = 0;
+ num_crlf = 0;
+ if (buffer[k] == ';') {
+ semicolon = true;
+ }
+ if (semicolon) {
+ // we don't look at chunk header extensions
continue;
}
- num_flush = k+1;
- // If the first two octets are CRLF then they must be discarded.
- return ((octets_seen + k + 1) == 2) ? SCAN_DISCARD : SCAN_FOUND;
+ if (as_hex[buffer[k]] == -1) {
+ // illegal character present in chunk length
+ complete = true;
+ return SCAN_ABORT;
+ }
+ length_started = true;
+ if (digits_seen >= 8) {
+ // overflow protection: must fit into 32 bits
+ complete = true;
+ return SCAN_ABORT;
+ }
+ expected_length = expected_length * 16 + as_hex[buffer[k]];
+ if (expected_length > 0) {
+ // leading zeroes don't count
+ digits_seen++;
+ }
}
octets_seen += length;
return SCAN_NOTFOUND;
}
+void NHttpChunkSplitter::conditional_reset() {
+ if (complete) {
+ expected_length = 0;
+ length_started = false;
+ digits_seen = 0;
+ semicolon = false;
+ header_complete = false;
+ }
+ NHttpSplitter::conditional_reset();
+}
+
ScanResult NHttpHeaderSplitter::split(const uint8_t* buffer, uint32_t length) {
+ conditional_reset();
if (peek_status == SCAN_FOUND) {
return SCAN_FOUND;
}
num_crlf++;
if ((num_crlf == 2) && (octets_seen + k + 1) == 2) {
num_flush = k+1;
+ complete = true;
return SCAN_FOUND;
}
if (num_crlf < 4) {
continue;
}
num_flush = k + 1 + peek_octets;
+ complete = true;
return SCAN_FOUND;
}
octets_seen += length;
return peek_status;
}
+void NHttpHeaderSplitter::conditional_reset() {
+ if (complete) {
+ peek_octets = 0;
+ peek_status = SCAN_NOTFOUND;
+ }
+ NHttpSplitter::conditional_reset();
+}
ScanResult NHttpTrailerSplitter::split(const uint8_t* buffer, uint32_t length) {
+ conditional_reset();
for (uint32_t k = 0; k < length; k++) {
// Count the alternating <CR> and <LF> characters we have seen in a row
if (((buffer[k] == '\r') && (num_crlf%2 == 0)) ||
num_crlf++;
if ((num_crlf == 2) && (octets_seen + k + 1) == 2) {
num_flush = k+1;
+ complete = true;
return SCAN_FOUND;
}
if (num_crlf < 4) {
continue;
}
num_flush = k+1;
+ complete = true;
return SCAN_FOUND;
}
octets_seen += length;
class NHttpSplitter {
public:
virtual ~NHttpSplitter() = default;
- virtual void reset() { octets_seen = 0; num_crlf = 0; num_flush = 0; };
virtual NHttpEnums::ScanResult split(const uint8_t* buffer, uint32_t length) = 0;
virtual NHttpEnums::ScanResult peek(const uint8_t*, uint32_t) { assert(0); return NHttpEnums::SCAN_NOTFOUND; };
uint32_t get_num_flush() { return num_flush; };
virtual uint32_t get_octets_seen() { return octets_seen; };
protected:
+ static const int8_t as_hex[256];
+
uint32_t octets_seen = 0;
uint32_t num_crlf = 0;
uint32_t num_flush = 0;
+ bool complete = false;
+
+ virtual void conditional_reset();
};
class NHttpRequestSplitter : public NHttpSplitter {
public:
NHttpEnums::ScanResult split(const uint8_t* buffer, uint32_t length);
NHttpEnums::ScanResult peek(const uint8_t* buffer, uint32_t length);
- void reset() { NHttpSplitter::reset(); peek_octets = 0; peek_status = NHttpEnums::SCAN_NOTFOUND; };
+ void conditional_reset();
uint32_t get_octets_seen() { return octets_seen - peek_octets; };
private:
uint32_t peek_octets = 0;
NHttpEnums::ScanResult peek_status = NHttpEnums::SCAN_NOTFOUND;
};
-class NHttpChunkHeaderSplitter : public NHttpSplitter {
+class NHttpChunkSplitter : public NHttpSplitter {
public:
NHttpEnums::ScanResult split(const uint8_t* buffer, uint32_t length);
+ void conditional_reset();
+private:
+ uint32_t expected_length = 0;
+ bool length_started = false;
+ uint32_t digits_seen = 0;
+ bool semicolon = false;
+ bool header_complete = false;
};
class NHttpTrailerSplitter : public NHttpSplitter {
// Convenience function. All the housekeeping that must be done before we can return FLUSH to stream.
void NHttpStreamSplitter::prepare_flush(NHttpFlowData* session_data, uint32_t* flush_offset, SourceId source_id,
SectionType section_type, bool tcp_close, uint64_t infractions, uint32_t num_octets, uint32_t length) {
- assert(num_octets > 0);
session_data->section_type[source_id] = section_type;
session_data->tcp_close[source_id] = tcp_close;
session_data->infractions[source_id] = infractions;
+ switch (section_type) {
+ case SEC_BODY:
+ paf_max = 16384;
+ break;
+ case SEC_CHUNK:
+ paf_max = 16384 - session_data->chunk_buffer_length[source_id];
+ if (num_octets == 0) {
+ session_data->type_expected[source_id] = SEC_TRAILER;
+ }
+ break;
+ default:
+ paf_max = 63780;
+ break;
+ }
if (tcp_close) {
session_data->type_expected[source_id] = SEC_CLOSED;
}
if (!NHttpTestManager::use_test_input()) {
- *flush_offset = num_octets;
+ // when TCP connection closes do not flush octets that have not arrived yet
+ *flush_offset = (!tcp_close || (num_octets <= length)) ? num_octets : length;
}
else {
- NHttpTestManager::get_test_input_source()->flush(num_octets);
+ NHttpTestManager::get_test_input_source()->flush((!tcp_close || (num_octets <= length)) ? num_octets : length);
}
- session_data->unused_octets_visible[source_id] = length - num_octets;
+ session_data->unused_octets_visible[source_id] = (length >= num_octets) ? length - num_octets : 0;
session_data->header_octets_visible[source_id] = 0;
}
-// Convenience function. Size buffer required to accommodate the current section plus possible aggregation.
+// Convenience function. Size buffer required to reassemble the current section plus possible aggregation.
uint32_t NHttpStreamSplitter::size_buffer_needed(unsigned total, NHttpEnums::SectionType type,
uint32_t possible_additional) {
switch (type) {
- case SEC_CHUNKBODY:
+ case SEC_CHUNK:
return 16384;
case SEC_REQUEST:
case SEC_STATUS:
switch (type) {
case SEC_REQUEST:
case SEC_STATUS:
- case SEC_CHUNKHEAD:
+ case SEC_CHUNK:
case SEC_HEADER:
case SEC_TRAILER:
{
- paf_max = 63780;
NHttpSplitter* splitter;
switch (type) {
case SEC_REQUEST: splitter = (NHttpSplitter*)&session_data->request_splitter[source_id]; break;
case SEC_STATUS: splitter = (NHttpSplitter*)&session_data->status_splitter[source_id]; break;
- case SEC_CHUNKHEAD: splitter = (NHttpSplitter*)&session_data->chunkhead_splitter[source_id]; break;
+ case SEC_CHUNK: splitter = (NHttpSplitter*)&session_data->chunk_splitter[source_id]; break;
case SEC_HEADER: splitter = (NHttpSplitter*)&session_data->header_splitter[source_id]; break;
case SEC_TRAILER: splitter = (NHttpSplitter*)&session_data->trailer_splitter[source_id]; break;
default: assert(0); break;
const uint32_t max_length = (length <= (63780 - splitter->get_octets_seen())) ? length :
(63780 - splitter->get_octets_seen());
const ScanResult split_result = splitter->split(data, max_length);
- if (split_result == SCAN_NOTFOUND) {
+ switch (split_result) {
+ case SCAN_NOTFOUND:
if (splitter->get_octets_seen() == 63780) {
- // FIXIT-M need to implement processing and detection instead of just discarding this data
+ prepare_flush(session_data, flush_offset, source_id, SEC_DISCARD, false, 0, 0, length);
session_data->type_expected[source_id] = SEC_ABORT;
return StreamSplitter::ABORT;
}
- if (!tcp_close) {
- // Incomplete headers wait patiently for more data
- return StreamSplitter::SEARCH;
+ if (tcp_close) {
+ prepare_flush(session_data, flush_offset, source_id, type, true, INF_TRUNCATED, length, length);
+ return StreamSplitter::FLUSH;
}
- prepare_flush(session_data, flush_offset, source_id, type, true, INF_TRUNCATED, length, length);
- splitter->reset();
+ // Incomplete headers wait patiently for more data
+ return StreamSplitter::SEARCH;
+ case SCAN_ABORT:
+ prepare_flush(session_data, flush_offset, source_id, SEC_DISCARD, false, 0, 0, length);
+ session_data->type_expected[source_id] = SEC_ABORT;
return StreamSplitter::FLUSH;
- }
- const uint32_t flush_octets = splitter->get_num_flush();
- if (split_result == SCAN_DISCARD) {
- prepare_flush(session_data, flush_offset, source_id, SEC_DISCARD, tcp_close && (flush_octets == length), 0,
+ case SCAN_DISCARD: {
+ const uint32_t flush_octets = splitter->get_num_flush();
+ prepare_flush(session_data, flush_offset, source_id, SEC_DISCARD, tcp_close && (flush_octets >= length), 0,
flush_octets, length);
- splitter->reset();
return StreamSplitter::FLUSH;
- }
- prepare_flush(session_data, flush_offset, source_id, type, tcp_close && (flush_octets == length), 0,
- flush_octets, length);
- splitter->reset();
- if ((type == SEC_REQUEST) || (type == SEC_STATUS)) {
- // Look ahead to see if entire header section is already here so we can aggregate it for detection.
- const uint32_t peek_max_length = ((length - flush_octets <= 63780)) ? (length - flush_octets) : 63780;
- if (session_data->header_splitter[source_id].peek(data + flush_octets, peek_max_length) == SCAN_FOUND) {
- session_data->header_octets_visible[source_id] = session_data->header_splitter[source_id].get_num_flush();
+ }
+ case SCAN_FOUND: {
+ const uint32_t flush_octets = splitter->get_num_flush();
+ prepare_flush(session_data, flush_offset, source_id, type, tcp_close && (flush_octets == length), 0,
+ flush_octets, length);
+ if ((type == SEC_REQUEST) || (type == SEC_STATUS)) {
+ // Look ahead to see if entire header section is already here so we can aggregate it for detection.
+ const uint32_t peek_max_length = ((length - flush_octets <= 63780)) ? (length - flush_octets) : 63780;
+ if (session_data->header_splitter[source_id].peek(data + flush_octets, peek_max_length) == SCAN_FOUND) {
+ session_data->header_octets_visible[source_id] = session_data->header_splitter[source_id].get_num_flush();
+ }
}
+ return StreamSplitter::FLUSH;
+ }
}
- return StreamSplitter::FLUSH;
}
case SEC_BODY:
- case SEC_CHUNKBODY:
- paf_max = 16384 - session_data->chunk_buffer_length[source_id];
if ((!tcp_close) || (length > session_data->data_length[source_id])) {
prepare_flush(session_data, flush_offset, source_id, type, false, 0, session_data->data_length[source_id],
length);
}
uint8_t* test_buffer;
NHttpTestManager::get_test_input_source()->reassemble(&test_buffer, len, source_id, session_data);
- if (len == 0) {
+ if (test_buffer == nullptr) {
// There is no more test data
return nullptr;
}
assert(total <= 63780);
assert(offset+len <= total);
- bool is_chunk_body = session_data->section_type[source_id] == SEC_CHUNKBODY;
+ bool is_chunk = session_data->section_type[source_id] == SEC_CHUNK;
uint8_t*& chunk_buffer = session_data->chunk_buffer[source_id];
int32_t& chunk_buffer_length = session_data->chunk_buffer_length[source_id];
- uint8_t*& buffer = !is_chunk_body ? session_data->section_buffer[source_id] : chunk_buffer;
- int32_t& buffer_length = !is_chunk_body ? session_data->section_buffer_length[source_id] : chunk_buffer_length;
+ uint8_t*& buffer = !is_chunk ? session_data->section_buffer[source_id] : chunk_buffer;
+ int32_t& buffer_length = !is_chunk ? session_data->section_buffer_length[source_id] : chunk_buffer_length;
if (buffer == nullptr) {
buffer = new uint8_t[size_buffer_needed(total, session_data->section_type[source_id],
memcpy(buffer + buffer_length + offset, data, len);
if (flags & PKT_PDU_TAIL) {
ProcessResult send_to_detection;
- if (!is_chunk_body) {
+ if (!is_chunk) {
// start line/headers/body individual section processing with aggregation prior to being sent to detection
// only the last section added to the buffer goes to the inspector
send_to_detection = my_inspector->process(buffer + buffer_length, offset + len, flow, source_id,
buffer_length == 0);
}
else {
- // Because of aggregation chunk body sections do not go to Inspector on schedule or in chronological order
- // with respect to chunk header sections. That means NHttpMsgChunkBody::update_flow() cannot do it design-intended
- // job of updating type_expected in time for StreamSplitter to find the next chunk header. So we do it here.
- session_data->type_expected[source_id] = SEC_CHUNKHEAD;
-
// small chunks are aggregated before processing and are kept here until the buffer is full (paf_max)
- // all the chunks in the buffer go to the inspector together
+ // all the chunks in the buffer go to the inspector together. Zero-length chunk flushes accumulated chunks.
int32_t total_chunk_len = chunk_buffer_length + offset + len;
- if (total_chunk_len < 16384) {
- paf_max = 16384 - total_chunk_len;
+ if ((total_chunk_len < 16384) && (len != 0)) {
chunk_buffer_length = total_chunk_len;
return nullptr;
}
- else {
- paf_max = 16384;
+ if (total_chunk_len == 0) {
+ // Zero-length chunk cannot be processed by itself.
+ delete[] chunk_buffer;
+ chunk_buffer = nullptr;
+ chunk_buffer_length = 0;
+ return nullptr;
}
+ paf_max = 16384;
send_to_detection = my_inspector->process(chunk_buffer, total_chunk_len, flow, source_id, true);
}
case RES_AGGREGATE:
buffer_length += offset + len;
return nullptr;
- case RES_FLUSHCHUNKS:
- buffer = nullptr;
- buffer_length = 0;
- if (chunk_buffer != nullptr) {
- // FIXIT-M these three variables about the buffered chunks need to be managed properly
- session_data->section_type[source_id] = SEC_CHUNKBODY;
- session_data->tcp_close[source_id] = false;
- session_data->infractions[source_id] = 0;
-
- my_inspector->process(chunk_buffer, chunk_buffer_length, flow, source_id, true);
- nhttp_buf.data = chunk_buffer;
- nhttp_buf.length = chunk_buffer_length;
- chunk_buffer = nullptr;
- chunk_buffer_length = 0;
- if (NHttpTestManager::use_test_output()) {
- fprintf(NHttpTestManager::get_output_file(), "Flushed chunks for detection %u octets\n\n", nhttp_buf.length);
- }
- return &nhttp_buf;
- }
- return nullptr;
}
}
return nullptr;
#include "nhttp_msg_header.h"
#include "nhttp_module.h"
#include "nhttp_uri_norm.h"
+#include "nhttp_splitter.h"
using namespace NHttpEnums;
{ 0, nullptr }
};
+const int8_t NHttpSplitter::as_hex[256] = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1,
+ -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-
-
-
-
-
-
-
-
-
-
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 };
void NHttpTestInput::reassemble(uint8_t **buffer, unsigned &length, SourceId &source_id, NHttpFlowData* session_data) {
+ if (!just_flushed) {
+ length = 0;
+ *buffer = nullptr;
+ return;
+ }
source_id = last_source_id;
*buffer = msg_buf;
-# Enter HTTP test message text as you want it to be presented to PAF.
+# Enter HTTP test message text as you want it to be presented to the StreamSplitter.
#
-# The easiest way to format is to put a blank line between message sections so that each message section is its own "paragraph".
-# Within a paragraph the placement of new lines does not have any effect. Format a paragraph any way you are comfortable. Extra blank lines
-# between paragraphs also do not have any effect.
+# The easiest way to format is to put a blank line between message sections so that each message section is its own "paragraph". Within a paragraph
+# the placement of single new lines does not have any effect. Format a paragraph any way you are comfortable. Extra blank lines between paragraphs
+# also do not have any effect.
#
# Each paragraph represents a TCP segment. The splitter can be tested by putting multiple sections in the same paragraph (splitter must split)
-# or continuing a section in the next paragraph (splitter must search and reassamble).
+# or continuing a section in the next paragraph (splitter must search and reassemble).
#
# It is not necessary to specify complete body and chunk body sections. Specify at least one octet that will begin the section and the
# remainder will be autofilled in 16384-octet sections up to the length flushed.
# least one character of body/chunk data either as part of the previous header segment or at the beginning of a new segment following the headers.
# All data bytes included in the file will be used followed by required fill data in the pattern ABC...XYZABC...
#
+# Fill data will not be provided for a paragraph that is preceded by tcpclose. The body or chunk will terminate at the end of the paragraph.
+#
# There must not be excess data for a test case. Once a data stream ends with a TCP close there must be a break command before further data is sent.
# Similarly a message section that triggers an abort of processing must end the paragraph and be followed by a break. These rules apply half-duplex
# so it would be possible to send data in the opposite direction before the break.
#
-# FIXIT-L it would be useful if a paragraph could continue following an abort to simulate a processing abort mid-segment.
-#
# This test tool does not implement the feature of being hardened against bad input. If you write a badly formatted or improper test case the
# program may assert or crash. The responsibility is on the developer to get it right. Currently that is the best use of resources.
chunkoflength138
-e6\r\nchunkoflength230
+\r\ne6\r\nchunkoflength230
-0\r\n
+\r\n0\r\n
\r\n
1C320\r\n
chunk1
-1F40; testing-the-extension-feature\r\n
+\r\n1F40; testing-the-extension-feature\r\n
chunk2
-13FFE; \r\n
+\r\n13FFE; \r\n
chunk3
-4000;\r\n
+\r\n4000;\r\n
chunk4
-3FFF;\r\n
+\r\n3FFF;\r\n
chunk5
-3FFE\r\n
+\r\n3FFE\r\n
chunk6
-4001;\r\n
+\r\n4001;\r\n
chunk7
-00000;chunkextension=3\r\n
+\r\n00000;chunkextension=3\r\n
date: Sun, 01 Oct 2000 23:25:17 GMT\r\n
X-madeupdate: Mon, 02 Oct 2000 23:25:17 GMT\r\n
A000\r\n
chunk1-start
-B000\r\n
+\r\nB000\r\n
chunk2-start
-C000\r\n
+\r\nC000\r\n
chunk3-start
+\r\n
+
0\r\n
date: Mon, 02 Oct 2000 20:25:00 GMT\r\n
fakeheader: 1234\r\n
#include "nhttp_msg_request.h"
#include "nhttp_msg_status.h"
#include "nhttp_msg_header.h"
-#include "nhttp_msg_body.h"
-#include "nhttp_msg_chunk_head.h"
-#include "nhttp_msg_chunk_body.h"
#include "nhttp_msg_trailer.h"
using namespace NHttpEnums;
}
-
-