]> git.ipfire.org Git - thirdparty/snort3.git/commitdiff
Merge pull request #2449 in SNORT/snort3 from ~KATHARVE/snort3:h2i_trailers_2 to...
authorMike Stepanek (mstepane) <mstepane@cisco.com>
Fri, 4 Sep 2020 17:03:30 +0000 (17:03 +0000)
committerMike Stepanek (mstepane) <mstepane@cisco.com>
Fri, 4 Sep 2020 17:03:30 +0000 (17:03 +0000)
Squashed commit of the following:

commit 95037139d8ecd2ec236ecfa747e8411b08f81912
Author: Katura Harvey <katharve@cisco.com>
Date:   Tue Sep 1 17:26:13 2020 -0400

    http2_inspect: fix hpack dynamic table init

commit 79454c069e4247d33cbb565fa1a9cba643d1360d
Author: Katura Harvey <katharve@cisco.com>
Date:   Thu Aug 27 09:18:45 2020 -0400

    http2_inspect: refactor hpack decoding and send trailer to http_inspect for processing

27 files changed:
src/service_inspectors/http2_inspect/CMakeLists.txt
src/service_inspectors/http2_inspect/dev_notes.txt
src/service_inspectors/http2_inspect/http2_data_cutter.cc
src/service_inspectors/http2_inspect/http2_enum.h
src/service_inspectors/http2_inspect/http2_flow_data.h
src/service_inspectors/http2_inspect/http2_frame.cc
src/service_inspectors/http2_inspect/http2_headers_frame.cc
src/service_inspectors/http2_inspect/http2_headers_frame.h
src/service_inspectors/http2_inspect/http2_headers_frame_header.cc [new file with mode: 0644]
src/service_inspectors/http2_inspect/http2_headers_frame_header.h [new file with mode: 0644]
src/service_inspectors/http2_inspect/http2_headers_frame_trailer.cc [new file with mode: 0644]
src/service_inspectors/http2_inspect/http2_headers_frame_trailer.h [new file with mode: 0644]
src/service_inspectors/http2_inspect/http2_hpack.cc
src/service_inspectors/http2_inspect/http2_hpack.h
src/service_inspectors/http2_inspect/http2_hpack_dynamic_table.h
src/service_inspectors/http2_inspect/http2_request_line.cc
src/service_inspectors/http2_inspect/http2_request_line.h
src/service_inspectors/http2_inspect/http2_start_line.cc
src/service_inspectors/http2_inspect/http2_start_line.h
src/service_inspectors/http2_inspect/http2_status_line.cc
src/service_inspectors/http2_inspect/http2_status_line.h
src/service_inspectors/http2_inspect/http2_stream.cc
src/service_inspectors/http2_inspect/http2_stream.h
src/service_inspectors/http2_inspect/http2_stream_splitter_impl.cc
src/service_inspectors/http2_inspect/http2_tables.cc
src/service_inspectors/http_inspect/http_flow_data.cc
src/service_inspectors/http_inspect/http_flow_data.h

index 5fd32c3328919e68deadf5b830d1bf4f77b1e9e8..c44b395ea37b3b9251ff8644835b49c404f41f0d 100644 (file)
@@ -13,6 +13,10 @@ set (FILE_LIST
     http2_frame.h
     http2_headers_frame.cc
     http2_headers_frame.h
+    http2_headers_frame_header.cc
+    http2_headers_frame_header.h
+    http2_headers_frame_trailer.cc
+    http2_headers_frame_trailer.h
     http2_hpack.cc
     http2_hpack.h
     http2_hpack_dynamic_table.cc
index 0804f45fd0b8f84ff0425371add87c77b3023125..23fb9cdfbb971be0322e5385b8d25cb44b7766cb 100644 (file)
@@ -1,10 +1,6 @@
-The HTTP/2 inspector (H2I) will convert HTTP/2 frames into HTTP/1.1 message sections and feed them
+The HTTP/2 inspector (H2I) converts HTTP/2 frames into HTTP/1.1 message sections and feeds them
 to the new HTTP inspector (NHI) for further processing.
 
-The current implementation is the very first step. It splits an HTTP/2 stream into frame sections
-and forwards them for inspection. It does not interface with NHI and does not address the
-multiplexed nature of HTTP/2.
-
 The Http2StreamSplitter strips the frame headers from the frame data and stores them in separate
 buffers. As in NHI, long data frames are split into 16kb chunks for inspection. If a header frame
 is followed by continuation frames, all the header frames are flushed together for inspection. The
@@ -12,14 +8,19 @@ frame headers from each frame are stored contiguously in the frame_header buffer
 the frame headers, the frame data is stored as a single block, consisting of the HPACK encoded
 HTTP/2 headers.
 
-HPACK decoding is under ongoing development. In the current implementation, reassemble() makes a
-first copy of the encoded headers, which is stored in the frame_data buffer. The frame_data buffer
-is passed to the function decode_headers(), which is the main loop driving HPACK decoding. The
-function allocates a second buffer, raw_decoded_header, to which the decoding routine will
-progressively write. As part of decoding the pseudo-headers (described below), data that must not be
-sent to NHI may be written to the start of the buffer. In order to avoid making any extra copies,
-the decoding routine sets a pointer to the start of the regular headers inside raw_decoded_header
-that will be processed by NHI, called http2_decoded_header.
+HTTP/2 headers frames can come at the start of a stream and contain the header block, or at the end
+of a stream and contain trailers. H2I contains headers frame subclasses Http2HeadersFrameHeader and
+Http2HeadersFrameTrailer to support these two types of headers frames. Headers frames containing the
+header block will contain pseudo-headers that must be converted into an HTTP/1.1 start line in
+addition to regular HTTP/1.1 headers. The two Http2StartLine subclasses, Http2RequestLine and
+Http2ResponseLine perform this translation and generate the start-line, which is stored in a new
+buffer inside the Http2StartLine object. Trailers may only contain regular headers. 
+
+Both headers and trailers must undergo HPACK decoding before being sent to NHI for processing. To
+perform decoding, reassemble() makes a first copy of the encoded headers, which is stored in the
+frame_data buffer. The frame_data buffer is passed to the function decode_headers(), which is the
+main loop driving HPACK decoding. Each decoded header line is progressively written to a second
+decoded_headers buffer that will ultimately be sent to NHI.
 
 The main loop in decode_headers() finds the cut point for a single header line. The line is is
 passed to decode_header_line(), which parses the line and calls the appropriate decoding function
@@ -28,18 +29,9 @@ in the table and copied to the decoded header buffer. The index may belong to ei
 the dynamic table. The static table is 61-elements defined in the HPACK RFC. The dynamic table,
 which starts at index 62, is specific to each direction of each flow. For the second type, literal
 to be indexed, the header name may be indexed or a string literal, while the value is always a
-literal. The resulting header line will then be added to the dynamic table (not yet implemented).
-The third representation type is literal not to be indexed, which is the same as literal to be
-indexed, except the header line is not added to the dynamic table.
-
-HTTP/2 uses pseudo-headers to convey the information included in an HTTP/1.1 start-line.
-Pseudo-headers are not valid HTTP/1.1 headers, so must be translated into a start-line before the
-decoded headers can be passed to NHI. The two Http2StartLine subclasses, Http2RequestLine and
-Http2ResponseLine perform this translation and generate the start-line, which is stored in a new
-buffer inside the Http2StartLine object. The start-line buffer must be passed to NHI before the
-http2_decoded_header buffer, which contains the regular HTTP/1.1 headers. Note that HTTP/2 does not
-use status reason phrases, so the status line passed to NHI will not include one. It will include
-only the HTTP version and the status code. 
+literal. The resulting header line is then added to the dynamic table. The third representation type
+is literal not to be indexed, which is the same as literal to be indexed, except the header line is
+not added to the dynamic table.
 
 H2I supports the NHI test tool. See ../http_inspect/dev_notes.txt for usage instructions.
 
index e8d99d3c049f2520802d7f811dabedcbc6e88b92..b6e6877c3dce39cf4aa7fb5521bf8fd47298e05e 100644 (file)
@@ -172,7 +172,10 @@ StreamSplitter::Status Http2DataCutter::http_scan(const uint8_t* data, uint32_t*
             {
                 Http2Stream* const stream = session_data->find_stream(
                     session_data->current_stream[source_id]);
-                stream->finish_msg_body(source_id);
+                bool clear_partial_buffers = false;
+                if (!bytes_sent_http)
+                    clear_partial_buffers = true;
+                stream->finish_msg_body(source_id, false, clear_partial_buffers);
 
                 // Since there may be multiple frame headers or zero frame headers in the flushed
                 // data section, remember END_STREAM flag in the stream object
index 0550c07a4a5461bd0c49b763cca941c48f106925..44ef7b7b4800a6c4f59c0138075682057bc8520d 100644 (file)
@@ -67,6 +67,9 @@ enum EventSid
     EVENT_DYNAMIC_TABLE_OVERFLOW = 14,
     EVENT_INVALID_STARTLINE = 15,
     EVENT_PADDING_LEN = 16,
+    EVENT_PSEUDO_HEADER_AFTER_REGULAR_HEADER = 17,
+    EVENT_PSEUDO_HEADER_IN_TRAILERS = 18,
+    EVENT_INVALID_PSEUDO_HEADER = 19,
     EVENT__MAX_VALUE
 };
 
@@ -103,6 +106,7 @@ enum Infraction
     INF_INVALID_HEADER = 26,
     INF_PADDING_LEN = 27,
     INF_TRAILERS_AFTER_END_STREAM = 28,
+    INF_PSEUDO_HEADER_IN_TRAILERS = 29,
     INF__MAX_VALUE
 };
 
@@ -115,17 +119,6 @@ enum HeaderFrameFlags
     NO_HEADER = 0x80, //No valid flags use this bit
 };
 
-enum PseudoHeaders
-{
-    HEADER__INVALID = -1,
-    HEADER__NONE = 0,
-    AUTHORITY = 1,
-    METHOD = 3,
-    PATH = 5,
-    SCHEME = 7,
-    STATUS = 14,
-};
-
 enum SettingsFrameIds
 {
     HEADER_TABLE_SIZE = 1,
index 4e10dfb0289a5002c3a0f37339bfaacb0f7a5b17..57b43fd136ea94963883d6c679434d9b6f081838 100644 (file)
@@ -65,6 +65,8 @@ public:
     friend class Http2DataFrame;
     friend class Http2DataCutter;
     friend class Http2HeadersFrame;
+    friend class Http2HeadersFrameHeader;
+    friend class Http2HeadersFrameTrailer;
     friend class Http2Hpack;
     friend class Http2Inspect;
     friend class Http2RequestLine;
index 87fab4036f26d86e706389cf85e662a859982f45..3520ed99d65532f99f514d4a808a3fb4082a382e 100644 (file)
@@ -26,7 +26,8 @@
 #include "http2_data_frame.h"
 #include "http2_enum.h"
 #include "http2_flow_data.h"
-#include "http2_headers_frame.h"
+#include "http2_headers_frame_header.h"
+#include "http2_headers_frame_trailer.h"
 #include "http2_settings_frame.h"
 #include "http2_stream.h"
 #include "service_inspectors/http_inspect/http_field.h"
@@ -54,8 +55,12 @@ Http2Frame* Http2Frame::new_frame(const uint8_t* header, const int32_t header_le
     switch(session_data->frame_type[source_id])
     {
         case FT_HEADERS:
-            return new Http2HeadersFrame(header, header_len, data, data_len, session_data,
-                source_id, stream);
+            if (stream->get_state(source_id) == STATE_IDLE)
+                return new Http2HeadersFrameHeader(header, header_len, data, data_len, session_data,
+                    source_id, stream);
+            else
+                return new Http2HeadersFrameTrailer(header, header_len, data, data_len,
+                    session_data, source_id, stream);
         case FT_SETTINGS:
             return new Http2SettingsFrame(header, header_len, data, data_len, session_data,
                 source_id, stream);
index fde267f75b2c5ca012b903c53df7cef07c98f9b9..00fbe1283e038e5d981de3ba8b415bc8fe9abac7 100644 (file)
@@ -46,122 +46,46 @@ Http2HeadersFrame::Http2HeadersFrame(const uint8_t* header_buffer, const int32_t
     Http2Frame(header_buffer, header_len, data_buffer, data_len, session_data_, source_id_, stream_)
 {
     // FIXIT-E check frame validity before creating frame
-    if (!check_frame_validity())
-        return;
-
-    // If the stream state is not IDLE, this frame contains trailers.
-    if (stream->get_state(source_id) >= STATE_OPEN)
+    if (!check_frame_validity() or data.length() <= 0)
     {
-        HttpFlowData* http_flow = session_data->get_current_stream(source_id)->get_hi_flow_data();
-        if (http_flow->get_type_expected(source_id) != HttpEnums::SEC_TRAILER)
-        {
-            // If there was no unflushed data on this stream when the trailers arrived, http_inspect
-            // will not yet be expecting trailers. Flush empty buffer through scan, reassemble, and
-            // eval to prepare http_inspect for trailers.
-            assert(http_flow->get_type_expected(source_id) == HttpEnums::SEC_BODY_H2);
-            stream->finish_msg_body(source_id, true); // calls http_inspect scan()
-
-            unsigned copied;
-            StreamBuffer stream_buf;
-            stream_buf = session_data->hi_ss[source_id]->reassemble(session_data->flow,
-                0, 0, nullptr, 0, PKT_PDU_TAIL, copied);
-            assert(stream_buf.data != nullptr);
-            assert(copied == 0);
-
-            Http2DummyPacket dummy_pkt;
-            dummy_pkt.flow = session_data->flow;
-            dummy_pkt.packet_flags = (source_id == SRC_CLIENT) ? PKT_FROM_CLIENT : PKT_FROM_SERVER;
-            dummy_pkt.dsize = stream_buf.length;
-            dummy_pkt.data = stream_buf.data;
-            session_data->hi->eval(&dummy_pkt);
-            assert (http_flow->get_type_expected(source_id) == HttpEnums::SEC_TRAILER);
-            if (http_flow->get_type_expected(source_id) == HttpEnums::SEC_ABORT)
-            {
-                hi_abort = true;
-                return;
-            }
-            session_data->hi->clear(&dummy_pkt);
-        }
-        // FIXIT-E Trailers are not yet being processed
-        trailer = true;
+        process_frame = false;
         return;
     }
 
-    // No need to process an empty headers frame
-    if (data.length() <= 0)
-        return;
-
-    uint8_t hpack_headers_offset = 0;
-
     // Remove stream dependency if present
     if (get_flags() & PRIORITY)
         hpack_headers_offset = 5;
 
-    // Set up the decoding context
-    Http2HpackDecoder& hpack_decoder = session_data->hpack_decoder[source_id];
-
-    // Allocate stuff
+    // Set up HPACK decoding
+    hpack_decoder = &session_data->hpack_decoder[source_id];
     decoded_headers = new uint8_t[MAX_OCTETS];
+}
 
-    start_line_generator = Http2StartLine::new_start_line_generator(source_id,
-        session_data->events[source_id], session_data->infractions[source_id]);
 
-    // Decode headers
-    if (!hpack_decoder.decode_headers((data.start() + hpack_headers_offset), data.length() -
-        hpack_headers_offset, decoded_headers, start_line_generator))
-    {
-        session_data->frame_type[source_id] = FT__ABORT;
-        error_during_decode = true;
-    }
-    start_line = hpack_decoder.get_start_line();
-    http1_header = hpack_decoder.get_decoded_headers(decoded_headers);
+Http2HeadersFrame::~Http2HeadersFrame()
+{
+    delete http1_header;
+    delete[] decoded_headers;
+}
 
-    if (error_during_decode)
+void Http2HeadersFrame::clear()
+{
+    if (error_during_decode || hi_abort)
         return;
+    Packet dummy_pkt(false);
+    dummy_pkt.flow = session_data->flow;
+    session_data->hi->clear(&dummy_pkt);
+}
 
-    // http_inspect scan() of start line
-    {
-        uint32_t flush_offset;
-        Http2DummyPacket dummy_pkt;
-        dummy_pkt.flow = session_data->flow;
-        const uint32_t unused = 0;
-        const StreamSplitter::Status start_scan_result =
-            session_data->hi_ss[source_id]->scan(&dummy_pkt, start_line->start(),
-            start_line->length(), unused, &flush_offset);
-        assert(start_scan_result == StreamSplitter::FLUSH);
-        UNUSED(start_scan_result);
-        assert((int64_t)flush_offset == start_line->length());
-    }
 
-    StreamBuffer stream_buf;
-    // http_inspect reassemble() of start line
-    {
-        unsigned copied;
-        stream_buf = session_data->hi_ss[source_id]->reassemble(session_data->flow,
-            start_line->length(), 0, start_line->start(), start_line->length(), PKT_PDU_TAIL,
-            copied);
-        assert(stream_buf.data != nullptr);
-        assert(copied == (unsigned)start_line->length());
-    }
 
-    HttpFlowData* http_flow = session_data->get_current_stream(source_id)->get_hi_flow_data();
-    // http_inspect eval() and clear() of start line
-    {
-        Http2DummyPacket dummy_pkt;
-        dummy_pkt.flow = session_data->flow;
-        dummy_pkt.packet_flags = (source_id == SRC_CLIENT) ? PKT_FROM_CLIENT : PKT_FROM_SERVER;
-        dummy_pkt.dsize = stream_buf.length;
-        dummy_pkt.data = stream_buf.data;
-        session_data->hi->eval(&dummy_pkt);
-        if (http_flow->get_type_expected(source_id) != HttpEnums::SEC_HEADER)
-        {
-            *session_data->infractions[source_id] += INF_INVALID_STARTLINE;
-            session_data->events[source_id]->create_event(EVENT_INVALID_STARTLINE);
-            hi_abort = true;
-            return;
-        }
-        session_data->hi->clear(&dummy_pkt);
-    }
+void Http2HeadersFrame::process_decoded_headers(HttpFlowData* http_flow)
+{
+    if (error_during_decode)
+        return;
+
+    http1_header = hpack_decoder->get_decoded_headers(decoded_headers);
+    StreamBuffer stream_buf;
 
     // http_inspect scan() of headers
     {
@@ -212,23 +136,6 @@ Http2HeadersFrame::Http2HeadersFrame(const uint8_t* header_buffer, const int32_t
     }
 }
 
-Http2HeadersFrame::~Http2HeadersFrame()
-{
-    delete start_line;
-    delete start_line_generator;
-    delete http1_header;
-    delete[] decoded_headers;
-}
-
-void Http2HeadersFrame::clear()
-{
-    if (error_during_decode || hi_abort)
-        return;
-    Packet dummy_pkt(false);
-    dummy_pkt.flow = session_data->flow;
-    session_data->hi->clear(&dummy_pkt);
-}
-
 const Field& Http2HeadersFrame::get_buf(unsigned id)
 {
     switch (id)
@@ -289,14 +196,8 @@ void Http2HeadersFrame::update_stream_state()
 #ifdef REG_TEST
 void Http2HeadersFrame::print_frame(FILE* output)
 {
-    if (!trailer)
-        fprintf(output, "Headers frame\n");
-    else
-        fprintf(output, "Trailing Headers frame\n");
     if (error_during_decode)
         fprintf(output, "Error decoding headers.\n");
-    if (start_line)
-        start_line->print(output, "Decoded start-line");
     if (http1_header)
         http1_header->print(output, "Decoded header");
     Http2Frame::print_frame(output);
index f44dc8c83e6dc1e0b341684276dfa894bae9d7a6..4e861b58cda18c00ee24ce0713c2b6c025ad1982 100644 (file)
@@ -27,6 +27,7 @@ class Http2HpackDecoder;
 class Http2StartLine;
 class Http2Frame;
 class Http2Stream;
+class HttpFlowData;
 
 class Http2HeadersFrame : public Http2Frame
 {
@@ -39,30 +40,26 @@ public:
     bool is_detection_required() const override { return detection_required; }
     void update_stream_state() override;
 
-    friend Http2Frame* Http2Frame::new_frame(const uint8_t*, const int32_t, const uint8_t*,
-        const int32_t, Http2FlowData*, HttpCommon::SourceId, Http2Stream* stream);
-
 #ifdef REG_TEST
     void print_frame(FILE* output) override;
 #endif
 
-private:
+protected:
     Http2HeadersFrame(const uint8_t* header_buffer, const int32_t header_len,
         const uint8_t* data_buffer, const int32_t data_len, Http2FlowData* ssn_data,
         HttpCommon::SourceId src_id, Http2Stream* stream);
     bool check_frame_validity();
+    void process_decoded_headers(HttpFlowData* http_flow);
 
-    Http2StartLine* start_line_generator = nullptr;
     uint8_t* decoded_headers = nullptr; // working buffer to store decoded headers
     uint32_t decoded_headers_size = 0;
     const Field* http1_header = nullptr; // finalized headers to be passed to NHI
-    const Field* start_line = nullptr;
     bool error_during_decode = false;
     bool hi_abort = false;
     uint32_t xtradata_mask = 0;
     bool detection_required = false;
-
-    // FIXIT-E Process trailers
-    bool trailer = false;
+    bool process_frame = true;
+    Http2HpackDecoder* hpack_decoder;
+    uint8_t hpack_headers_offset = 0;
 };
 #endif
diff --git a/src/service_inspectors/http2_inspect/http2_headers_frame_header.cc b/src/service_inspectors/http2_inspect/http2_headers_frame_header.cc
new file mode 100644 (file)
index 0000000..6036abc
--- /dev/null
@@ -0,0 +1,135 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2020-2020 Cisco and/or its affiliates. All rights reserved.
+//
+// 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.
+//--------------------------------------------------------------------------
+// http2_headers_frame_header.cc author Katura Harvey <katharve@cisco.com>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "http2_headers_frame_header.h"
+
+#include "protocols/packet.h"
+#include "service_inspectors/http_inspect/http_enum.h"
+#include "service_inspectors/http_inspect/http_flow_data.h"
+#include "service_inspectors/http_inspect/http_inspect.h"
+#include "service_inspectors/http_inspect/http_stream_splitter.h"
+
+#include "http2_dummy_packet.h"
+#include "http2_enum.h"
+#include "http2_flow_data.h"
+#include "http2_hpack.h"
+#include "http2_start_line.h"
+#include "http2_stream.h"
+
+using namespace snort;
+using namespace HttpCommon;
+using namespace Http2Enums;
+
+Http2HeadersFrameHeader::Http2HeadersFrameHeader(const uint8_t* header_buffer, const int32_t header_len,
+    const uint8_t* data_buffer, const int32_t data_len, Http2FlowData* session_data_,
+    HttpCommon::SourceId source_id_, Http2Stream* stream_) :
+    Http2HeadersFrame(header_buffer, header_len, data_buffer, data_len, session_data_, source_id_,
+        stream_)
+{
+    if (!process_frame)
+        return;
+
+    start_line_generator = Http2StartLine::new_start_line_generator(source_id,
+        session_data->events[source_id], session_data->infractions[source_id]);
+
+    // Decode headers
+    if (!hpack_decoder->decode_headers((data.start() + hpack_headers_offset), data.length() -
+        hpack_headers_offset, decoded_headers, start_line_generator, false))
+    {
+        session_data->frame_type[source_id] = FT__ABORT;
+        error_during_decode = true;
+    }
+
+    // process start line
+    if (!start_line_generator->generate_start_line(start_line))
+    {
+        // FIXIT-E set stream state to error
+        return;
+    }
+    
+    StreamBuffer stream_buf;
+    HttpFlowData* http_flow;
+
+    // http_inspect scan() of start line
+    {
+        uint32_t flush_offset;
+        Http2DummyPacket dummy_pkt;
+        dummy_pkt.flow = session_data->flow;
+        const uint32_t unused = 0;
+        const StreamSplitter::Status start_scan_result =
+            session_data->hi_ss[source_id]->scan(&dummy_pkt, start_line->start(),
+                start_line->length(), unused, &flush_offset);
+        assert(start_scan_result == StreamSplitter::FLUSH);
+        UNUSED(start_scan_result);
+        assert((int64_t)flush_offset == start_line->length());
+    }
+
+    // http_inspect reassemble() of start line
+    {
+        unsigned copied;
+        stream_buf = session_data->hi_ss[source_id]->reassemble(session_data->flow,
+            start_line->length(), 0, start_line->start(), start_line->length(), PKT_PDU_TAIL,
+            copied);
+        assert(stream_buf.data != nullptr);
+        assert(copied == (unsigned)start_line->length());
+    }
+
+    http_flow = session_data->get_current_stream(source_id)->get_hi_flow_data();
+    // http_inspect eval() and clear() of start line
+    {
+        Http2DummyPacket dummy_pkt;
+        dummy_pkt.flow = session_data->flow;
+        dummy_pkt.packet_flags = (source_id == SRC_CLIENT) ? PKT_FROM_CLIENT : PKT_FROM_SERVER;
+        dummy_pkt.dsize = stream_buf.length;
+        dummy_pkt.data = stream_buf.data;
+        session_data->hi->eval(&dummy_pkt);
+        if (http_flow->get_type_expected(source_id) != HttpEnums::SEC_HEADER)
+        {
+            *session_data->infractions[source_id] += INF_INVALID_STARTLINE;
+            session_data->events[source_id]->create_event(EVENT_INVALID_STARTLINE);
+            hi_abort = true;
+            return;
+        }
+        session_data->hi->clear(&dummy_pkt);
+    }
+
+    process_decoded_headers(http_flow);
+}
+
+Http2HeadersFrameHeader::~Http2HeadersFrameHeader()
+{
+    delete start_line;
+    delete start_line_generator;
+}
+
+#ifdef REG_TEST
+void Http2HeadersFrameHeader::print_frame(FILE* output)
+{
+    fprintf(output, "Headers frame\n");
+    if (start_line)
+        start_line->print(output, "Decoded start-line");
+    else
+        fprintf(output, "Error generating start line.\n");
+    Http2HeadersFrame::print_frame(output);
+}
+#endif
diff --git a/src/service_inspectors/http2_inspect/http2_headers_frame_header.h b/src/service_inspectors/http2_inspect/http2_headers_frame_header.h
new file mode 100644 (file)
index 0000000..1bb0b3d
--- /dev/null
@@ -0,0 +1,48 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2020-2020 Cisco and/or its affiliates. All rights reserved.
+//
+// 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.
+//--------------------------------------------------------------------------
+// http2_headers_frame_header.h author Katura Harvey <katharve@cisco.com>
+
+#ifndef HTTP2_HEADERS_FRAME_HEADER_H
+#define HTTP2_HEADERS_FRAME_HEADER_H
+
+#include "http2_frame.h"
+#include "http2_headers_frame.h"
+
+class Http2StartLine;
+
+class Http2HeadersFrameHeader : public Http2HeadersFrame
+{
+public:
+    ~Http2HeadersFrameHeader();
+    
+    friend Http2Frame* Http2Frame::new_frame(const uint8_t*, const int32_t, const uint8_t*,
+        const int32_t, Http2FlowData*, HttpCommon::SourceId, Http2Stream* stream);
+
+#ifdef REG_TEST
+    void print_frame(FILE* output) override;
+#endif
+
+private:
+    Http2HeadersFrameHeader(const uint8_t* header_buffer, const int32_t header_len,
+        const uint8_t* data_buffer, const int32_t data_len, Http2FlowData* ssn_data,
+        HttpCommon::SourceId src_id, Http2Stream* stream);
+
+    Http2StartLine* start_line_generator = nullptr;
+    const Field* start_line = nullptr;
+};
+#endif
diff --git a/src/service_inspectors/http2_inspect/http2_headers_frame_trailer.cc b/src/service_inspectors/http2_inspect/http2_headers_frame_trailer.cc
new file mode 100644 (file)
index 0000000..2c539ab
--- /dev/null
@@ -0,0 +1,103 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2020-2020 Cisco and/or its affiliates. All rights reserved.
+//
+// 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.
+//--------------------------------------------------------------------------
+// http2_headers_frame_trailer.cc author Katura Harvey <katharve@cisco.com>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "http2_headers_frame_trailer.h"
+
+#include "protocols/packet.h"
+#include "service_inspectors/http_inspect/http_enum.h"
+#include "service_inspectors/http_inspect/http_flow_data.h"
+#include "service_inspectors/http_inspect/http_inspect.h"
+#include "service_inspectors/http_inspect/http_stream_splitter.h"
+
+#include "http2_dummy_packet.h"
+#include "http2_enum.h"
+#include "http2_flow_data.h"
+#include "http2_hpack.h"
+#include "http2_stream.h"
+
+using namespace snort;
+using namespace HttpCommon;
+using namespace Http2Enums;
+
+Http2HeadersFrameTrailer::Http2HeadersFrameTrailer(const uint8_t* header_buffer, const int32_t header_len,
+    const uint8_t* data_buffer, const int32_t data_len, Http2FlowData* session_data_,
+    HttpCommon::SourceId source_id_, Http2Stream* stream_) :
+    Http2HeadersFrame(header_buffer, header_len, data_buffer, data_len, session_data_, source_id_,
+        stream_)
+{
+    if (!process_frame)
+        return;
+
+    StreamBuffer stream_buf;
+    HttpFlowData* http_flow;
+
+    http_flow = session_data->get_current_stream(source_id)->get_hi_flow_data();
+    assert(http_flow);
+    if (http_flow->get_type_expected(source_id) != HttpEnums::SEC_TRAILER)
+    {
+        // If there was no unflushed data on this stream when the trailers arrived, http_inspect
+        // will not yet be expecting trailers. Flush empty buffer through scan, reassemble, and
+        // eval to prepare http_inspect for trailers.
+        assert(http_flow->get_type_expected(source_id) == HttpEnums::SEC_BODY_H2);
+        stream->finish_msg_body(source_id, true, true); // calls http_inspect scan()
+
+        unsigned copied;
+        stream_buf = session_data->hi_ss[source_id]->reassemble(session_data->flow,
+            0, 0, nullptr, 0, PKT_PDU_TAIL, copied);
+        assert(stream_buf.data != nullptr);
+        assert(copied == 0);
+
+        Http2DummyPacket dummy_pkt;
+        dummy_pkt.flow = session_data->flow;
+        dummy_pkt.packet_flags = (source_id == SRC_CLIENT) ? PKT_FROM_CLIENT : PKT_FROM_SERVER;
+        dummy_pkt.dsize = stream_buf.length;
+        dummy_pkt.data = stream_buf.data;
+        session_data->hi->eval(&dummy_pkt);
+        assert (http_flow->get_type_expected(source_id) == HttpEnums::SEC_TRAILER);
+        if (http_flow->get_type_expected(source_id) == HttpEnums::SEC_ABORT)
+        {
+            hi_abort = true;
+            return;
+        }
+        session_data->hi->clear(&dummy_pkt);
+    }
+
+    // Decode headers
+    if (!hpack_decoder->decode_headers((data.start() + hpack_headers_offset), data.length() -
+        hpack_headers_offset, decoded_headers, nullptr, true))
+    {
+        session_data->frame_type[source_id] = FT__ABORT;
+        error_during_decode = true;
+    }
+
+    process_decoded_headers(http_flow);
+}
+
+#ifdef REG_TEST
+void Http2HeadersFrameTrailer::print_frame(FILE* output)
+{
+    fprintf(output, "Trailers frame\n");
+    Http2HeadersFrame::print_frame(output);
+
+}
+#endif
diff --git a/src/service_inspectors/http2_inspect/http2_headers_frame_trailer.h b/src/service_inspectors/http2_inspect/http2_headers_frame_trailer.h
new file mode 100644 (file)
index 0000000..d8b0aca
--- /dev/null
@@ -0,0 +1,41 @@
+//--------------------------------------------------------------------------
+// Copyright (C) 2020-2020 Cisco and/or its affiliates. All rights reserved.
+//
+// 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.
+//--------------------------------------------------------------------------
+// http2_headers_frame_trailer.h author Katura Harvey <katharve@cisco.com>
+
+#ifndef HTTP2_HEADERS_FRAME_TRAILER_H
+#define HTTP2_HEADERS_FRAME_TRAILER_H
+
+#include "http2_frame.h"
+#include "http2_headers_frame.h"
+
+class Http2HeadersFrameTrailer : public Http2HeadersFrame
+{
+public:
+    friend Http2Frame* Http2Frame::new_frame(const uint8_t*, const int32_t, const uint8_t*,
+        const int32_t, Http2FlowData*, HttpCommon::SourceId, Http2Stream* stream);
+
+#ifdef REG_TEST
+    void print_frame(FILE* output) override;
+#endif
+
+private:
+    Http2HeadersFrameTrailer(const uint8_t* header_buffer, const int32_t header_len,
+        const uint8_t* data_buffer, const int32_t data_len, Http2FlowData* ssn_data,
+        HttpCommon::SourceId src_id, Http2Stream* stream);
+};
+#endif
index c58c134827e596b244220e2f43a72d704d436527..93e82b3f98f673cfeca1ec38e1786bdfef91993d 100644 (file)
@@ -77,34 +77,44 @@ bool Http2HpackDecoder::decode_string_literal(const uint8_t* encoded_header_buff
     return true;
 }
 
-bool Http2HpackDecoder::decode_indexed_name(const uint8_t* encoded_header_buffer,
-    const uint32_t encoded_header_length, const Http2HpackIntDecode& decode_int,
-    uint32_t& bytes_consumed, uint8_t* decoded_header_buffer, const uint32_t decoded_header_length,
-    uint32_t& bytes_written, Field& name)
+const HpackTableEntry* Http2HpackDecoder::get_hpack_table_entry(
+    const uint8_t* encoded_header_buffer, const uint32_t encoded_header_length,
+    const Http2HpackIntDecode& decode_int, uint32_t& bytes_consumed)
 {
     uint64_t index;
-    bytes_written = 0;
     bytes_consumed = 0;
 
     if (!decode_int.translate(encoded_header_buffer, encoded_header_length, bytes_consumed,
-            index, events, infractions))
-        return false;
-
-    const HpackTableEntry* const entry = decode_table.lookup(index);
+        index, events, infractions))
+    {
+        return nullptr;
+    }
 
+    const HpackTableEntry* entry = decode_table.lookup(index);
     if (!entry)
     {
         *infractions += INF_HPACK_INDEX_OUT_OF_BOUNDS;
         events->create_event(EVENT_MISFORMATTED_HTTP2);
-        return false;
     }
+    return entry;
+}
+
+bool Http2HpackDecoder::decode_indexed_name(const uint8_t* encoded_header_buffer,
+    const uint32_t encoded_header_length, const Http2HpackIntDecode& decode_int,
+    uint32_t& bytes_consumed, uint8_t* decoded_header_buffer, const uint32_t decoded_header_length,
+    uint32_t& bytes_written, Field& name)
+{
+    bytes_written = bytes_consumed = 0;
 
-    // If not a pseudo-header, write to the decoded_header buffer
-    if (index > HpackIndexTable::PSEUDO_HEADER_MAX_STATIC_INDEX)
+    const HpackTableEntry* entry = get_hpack_table_entry(encoded_header_buffer,
+        encoded_header_length, decode_int, bytes_consumed);
+    if (!entry)
+        return false;
+
+    if (!write_decoded_headers(entry->name.start(), entry->name.length(), decoded_header_buffer,
+        decoded_header_length, bytes_written))
     {
-        if (!write_decoded_headers(entry->name.start(), entry->name.length(), decoded_header_buffer,
-                decoded_header_length, bytes_written))
-            return false;
+        return false;
     }
 
     name.set(entry->name);
@@ -114,15 +124,11 @@ bool Http2HpackDecoder::decode_indexed_name(const uint8_t* encoded_header_buffer
 bool Http2HpackDecoder::decode_literal_header_line(const uint8_t* encoded_header_buffer,
     const uint32_t encoded_header_length, const uint8_t name_index_mask,
     const Http2HpackIntDecode& decode_int, bool with_indexing, uint32_t& bytes_consumed,
-    uint8_t* decoded_header_buffer, const uint32_t decoded_header_length, uint32_t& bytes_written)
+    uint8_t* decoded_header_buffer, const uint32_t decoded_header_length, uint32_t& bytes_written,
+    Field& name, Field& value)
 {
-    bytes_written = 0;
-    bytes_consumed = 0;
-    uint32_t partial_bytes_consumed;
-    uint32_t partial_bytes_written;
-    Field name, value;
-
-    table_size_update_allowed = false;
+    bytes_written = bytes_consumed = 0;
+    uint32_t partial_bytes_consumed, partial_bytes_written;
 
     // Indexed field name
     if (encoded_header_buffer[0] & name_index_mask)
@@ -147,27 +153,10 @@ bool Http2HpackDecoder::decode_literal_header_line(const uint8_t* encoded_header
     bytes_consumed += partial_bytes_consumed;
     bytes_written += partial_bytes_written;
 
-    // If this was a pseudo-header value, give it to the start-line.
-    if (start_line->is_pseudo_name(name.start()))
-    {
-        start_line->process_pseudo_header_name(name.start(), name.length());
-        // Pseudo_header after regular header not allowed
-        if (start_line->is_finalized())
-            bytes_written -= partial_bytes_written;
-    }
-    else
-    {
-        if (!start_line->is_finalized())
-        {
-            if (!finalize_start_line())
-                return false;
-        }
-
-        if (!write_decoded_headers((const uint8_t*)": ", 2, decoded_header_buffer + bytes_written,
-                decoded_header_length - bytes_written, partial_bytes_written))
-            return false;
-        bytes_written += partial_bytes_written;
-    }
+    if (!write_decoded_headers((const uint8_t*)": ", 2, decoded_header_buffer + bytes_written,
+            decoded_header_length - bytes_written, partial_bytes_written))
+        return false;
+    bytes_written += partial_bytes_written;
 
     // Value is always a string literal
     if (!decode_string_literal(encoded_header_buffer + bytes_consumed, encoded_header_length -
@@ -177,21 +166,10 @@ bool Http2HpackDecoder::decode_literal_header_line(const uint8_t* encoded_header
     bytes_written += partial_bytes_written;
     bytes_consumed += partial_bytes_consumed;
 
-    // If this was a pseudo-header, give it to the start-line
-    if (start_line->is_pseudo_value())
-    {
-        start_line->process_pseudo_header_value(value.start(), value.length());
-        // Pseudo_header after regular header not allowed
-        if (start_line->is_finalized())
-            bytes_written -= partial_bytes_written;
-    }
-    else
-    {
-        if (!write_decoded_headers((const uint8_t*)"\r\n", 2, decoded_header_buffer + bytes_written,
-                decoded_header_length - bytes_written, partial_bytes_written))
-            return false;
-        bytes_written += partial_bytes_written;
-    }
+    if (!write_decoded_headers((const uint8_t*)"\r\n", 2, decoded_header_buffer + bytes_written,
+            decoded_header_length - bytes_written, partial_bytes_written))
+        return false;
+    bytes_written += partial_bytes_written;
 
     if (with_indexing)
     {
@@ -210,76 +188,61 @@ bool Http2HpackDecoder::decode_literal_header_line(const uint8_t* encoded_header
 bool Http2HpackDecoder::decode_indexed_header(const uint8_t* encoded_header_buffer,
     const uint32_t encoded_header_length, const Http2HpackIntDecode& decode_int,
     uint32_t &bytes_consumed, uint8_t* decoded_header_buffer, const uint32_t decoded_header_length,
-    uint32_t& bytes_written)
+    uint32_t& bytes_written, Field& name, Field& value)
 {
-    uint64_t index;
-    bytes_written = 0;
-    bytes_consumed = 0;
-
-    table_size_update_allowed = false;
-
-    if (!decode_int.translate(encoded_header_buffer, encoded_header_length, bytes_consumed,
-            index, events, infractions))
-        return false;
-
-    const HpackTableEntry* const entry = decode_table.lookup(index);
+    uint32_t partial_bytes_written = 0;
+    bytes_written = bytes_consumed = 0;
 
+    const HpackTableEntry* const entry = get_hpack_table_entry(encoded_header_buffer,
+        encoded_header_length, decode_int, bytes_consumed);
     if (!entry)
-    {
-        *infractions += INF_HPACK_INDEX_OUT_OF_BOUNDS;
-        events->create_event(EVENT_MISFORMATTED_HTTP2);
         return false;
-    }
+    name.set(entry->name);
+    value.set(entry->value);
 
-    if (entry->value.length() <= 0)
+    if (value.length() <= 0)
     {
         *infractions += INF_LOOKUP_EMPTY_VALUE;
         events->create_event(EVENT_MISFORMATTED_HTTP2);
     }
 
-    // If this was a pseudo-header value, give it to the start-line.
-    if (start_line->is_pseudo_name(entry->name.start()))
-    {
-        start_line->process_pseudo_header_name(entry->name.start(), entry->name.length());
-        start_line->process_pseudo_header_value(entry->value.start(), entry->value.length());
-    }
-    else
-    {
-        uint32_t local_bytes_written = 0;
-        if (!start_line->is_finalized())
-            if (!finalize_start_line())
-                return false;
-        if (!write_decoded_headers(entry->name.start(), entry->name.length(), decoded_header_buffer,
-                decoded_header_length, local_bytes_written))
-            return false;
-        bytes_written += local_bytes_written;
-        if (!write_decoded_headers((const uint8_t*) ": ", 2, decoded_header_buffer + bytes_written,
-                decoded_header_length - bytes_written, local_bytes_written))
-            return false;
-        bytes_written += local_bytes_written;
-        if (!write_decoded_headers(entry->value.start(), entry->value.length(),
-                decoded_header_buffer + bytes_written, decoded_header_length - bytes_written,
-                local_bytes_written))
-            return false;
-        bytes_written += local_bytes_written;
-        if (!write_decoded_headers((const uint8_t*) "\r\n", 2, decoded_header_buffer +
-                bytes_written, decoded_header_length - bytes_written, local_bytes_written))
-            return false;
-        bytes_written += local_bytes_written;
-    }
+    if (!write_header_part(name, (const uint8_t*)": ", 2, decoded_header_buffer,
+        decoded_header_length, partial_bytes_written))
+        return false;
+    bytes_written += partial_bytes_written;
+    if (!write_header_part(value, (const uint8_t*)"\r\n", 2, decoded_header_buffer + bytes_written,
+        decoded_header_length - bytes_written, partial_bytes_written))
+        return false;
+    bytes_written += partial_bytes_written;
+    return true;
+}
+
+bool Http2HpackDecoder::write_header_part(Field& header, const uint8_t* suffix,
+    uint32_t suffix_length, uint8_t* decoded_header_buffer, const uint32_t decoded_header_length,
+    uint32_t& bytes_written)
+{
+    bytes_written = 0;
+    uint32_t partial_bytes_written;
+
+    if (!write_decoded_headers(header.start(), header.length(), decoded_header_buffer,
+            decoded_header_length, partial_bytes_written))
+        return false;
+    bytes_written += partial_bytes_written;
+    if (!write_decoded_headers(suffix, suffix_length, decoded_header_buffer + bytes_written,
+            decoded_header_length - bytes_written, partial_bytes_written))
+        return false;
+    bytes_written += partial_bytes_written;
     return true;
 }
 
 bool Http2HpackDecoder::handle_dynamic_size_update(const uint8_t* encoded_header_buffer,
-    const uint32_t encoded_header_length, const Http2HpackIntDecode &decode_int,
-    uint32_t &bytes_consumed, uint32_t &bytes_written)
+    const uint32_t encoded_header_length, uint32_t &bytes_consumed)
 {
     uint64_t decoded_int;
     uint32_t encoded_bytes_consumed;
     bytes_consumed = 0;
-    bytes_written = 0;
 
-    if (!decode_int.translate(encoded_header_buffer, encoded_header_length,
+    if (!decode_int5.translate(encoded_header_buffer, encoded_header_length,
         encoded_bytes_consumed, decoded_int, events, infractions))
     {
         return false;
@@ -314,46 +277,79 @@ bool Http2HpackDecoder::decode_header_line(const uint8_t* encoded_header_buffer,
     const uint32_t encoded_header_length, uint32_t& bytes_consumed, uint8_t* decoded_header_buffer,
     const uint32_t decoded_header_length, uint32_t& bytes_written)
 {
+    const static uint8_t DYN_TABLE_SIZE_UPDATE_MASK = 0xe0;
+    const static uint8_t DYN_TABLE_SIZE_UPDATE_PATTERN = 0x20;
     const static uint8_t INDEX_MASK = 0x80;
     const static uint8_t LITERAL_INDEX_MASK = 0x40;
     const static uint8_t LITERAL_INDEX_NAME_INDEX_MASK = 0x3f;
-    const static uint8_t LITERAL_NO_INDEX_MASK = 0xf0;
-    const static uint8_t LITERAL_NEVER_INDEX_PATTERN = 0x10;
     const static uint8_t LITERAL_NO_INDEX_NAME_INDEX_MASK = 0x0f;
 
+    Field name, value;
+    bytes_consumed = bytes_written = 0;
+    bool ret;
+
+    if ((encoded_header_buffer[0] & DYN_TABLE_SIZE_UPDATE_MASK) == DYN_TABLE_SIZE_UPDATE_PATTERN)
+        return handle_dynamic_size_update(encoded_header_buffer,
+            encoded_header_length, bytes_consumed);
+
+    table_size_update_allowed = false;
+    
     // Indexed header representation
     if (encoded_header_buffer[0] & INDEX_MASK)
-        return decode_indexed_header(encoded_header_buffer,
+        ret = decode_indexed_header(encoded_header_buffer,
             encoded_header_length, decode_int7, bytes_consumed,
-            decoded_header_buffer, decoded_header_length, bytes_written);
+            decoded_header_buffer, decoded_header_length, bytes_written, name, value);
 
     // Literal header representation to be added to dynamic table
     else if (encoded_header_buffer[0] & LITERAL_INDEX_MASK)
-        return decode_literal_header_line(encoded_header_buffer, encoded_header_length,
+        ret = decode_literal_header_line(encoded_header_buffer, encoded_header_length,
             LITERAL_INDEX_NAME_INDEX_MASK, decode_int6, true, bytes_consumed, decoded_header_buffer,
-            decoded_header_length, bytes_written);
+            decoded_header_length, bytes_written, name, value);
 
     // Literal header field representation not to be added to dynamic table
     // Note that this includes two representation types from the RFC - literal without index and
     // literal never index. From a decoding standpoint these are identical.
-    else if ((encoded_header_buffer[0] & LITERAL_NO_INDEX_MASK) == 0 or
-            (encoded_header_buffer[0] & LITERAL_NO_INDEX_MASK) == LITERAL_NEVER_INDEX_PATTERN)
-        return decode_literal_header_line(encoded_header_buffer, encoded_header_length,
-            LITERAL_NO_INDEX_NAME_INDEX_MASK, decode_int4, false, bytes_consumed,
-            decoded_header_buffer, decoded_header_length, bytes_written);
     else
-        return handle_dynamic_size_update(encoded_header_buffer,
-            encoded_header_length, decode_int5, bytes_consumed, bytes_written);
+        ret = decode_literal_header_line(encoded_header_buffer, encoded_header_length,
+            LITERAL_NO_INDEX_NAME_INDEX_MASK, decode_int4, false, bytes_consumed,
+            decoded_header_buffer, decoded_header_length, bytes_written, name, value);
+
+    // Handle pseudoheaders
+    if (ret and bytes_written > 0)
+    {
+        if (decoded_header_buffer[0] == ':')
+        {
+            if (pseudo_headers_allowed)
+                start_line->process_pseudo_header(name, value);
+            else
+            {
+                if (is_trailers)
+                {
+                    *infractions += INF_PSEUDO_HEADER_IN_TRAILERS;
+                    events->create_event(EVENT_PSEUDO_HEADER_IN_TRAILERS);
+                }
+                else
+                {
+                    *infractions += INF_PSEUDO_HEADER_AFTER_REGULAR_HEADER;
+                    events->create_event(EVENT_PSEUDO_HEADER_AFTER_REGULAR_HEADER);
+                }
+            }
+            bytes_written = 0;
+        }
+        else if (pseudo_headers_allowed)
+            pseudo_headers_allowed = false;
+    }
+    return ret;
 }
 
 // Entry point to decode an HPACK-encoded header block. This function returns true on successful
 // decode and false on an unrecoverable decode error. Note that alerts may still be generated for
 // recoverable errors while the function returns true. This function performs all decoding, but does
 // not output the start line or decoded headers - this function must be followed by calls to
-// get_start_line() and get_decoded_headers() to generate and obtain these fields.
+// generate_start_line() and get_decoded_headers() to generate and obtain these fields.
 bool Http2HpackDecoder::decode_headers(const uint8_t* encoded_headers,
     const uint32_t encoded_headers_length, uint8_t* decoded_headers,
-    Http2StartLine *start_line_generator)
+    Http2StartLine *start_line_generator, bool trailers)
 {
     uint32_t total_bytes_consumed = 0;
     uint32_t line_bytes_consumed = 0;
@@ -361,8 +357,9 @@ bool Http2HpackDecoder::decode_headers(const uint8_t* encoded_headers,
     bool success = true;
     start_line = start_line_generator;
     decoded_headers_size = 0;
-    pseudo_headers_fragment_size = 0;
     decode_error = false;
+    is_trailers = trailers;
+    pseudo_headers_allowed = !is_trailers;
 
     // A maximum of two table size updates are allowed, and must be at the start of the header block
     table_size_update_allowed = true;
@@ -378,14 +375,7 @@ bool Http2HpackDecoder::decode_headers(const uint8_t* encoded_headers,
         decoded_headers_size += line_bytes_written;
     }
 
-    // If there were only pseudo-headers, finalize never got called, so create the start-line
-    if (!start_line->is_finalized())
-        success &= finalize_start_line();
-
-    /* Write the last CRLF to end the header
-
-       Adding artificial chunked header to end of HTTP/1.1 decoded header block for H2I to communicate
-       frame boundaries to http_inspect and http_inspect can expect chunked data during inspection */
+    // Write the last CRLF to end the header
     if (success)
     {
         success = write_decoded_headers((const uint8_t*)"\r\n", 2, decoded_headers +
@@ -397,25 +387,10 @@ bool Http2HpackDecoder::decode_headers(const uint8_t* encoded_headers,
     return success;
 }
 
-bool Http2HpackDecoder::finalize_start_line()
-{
-    // Save the current position in the decoded buffer so we can set the pointer to the start
-    // of the regular headers
-    pseudo_headers_fragment_size = decoded_headers_size;
-
-    return start_line->finalize();
-}
-
-const Field* Http2HpackDecoder::get_start_line()
-{
-    return start_line->get_start_line();
-}
-
 const Field* Http2HpackDecoder::get_decoded_headers(const uint8_t* const decoded_headers)
 {
     if (decode_error)
         return new Field(STAT_NO_SOURCE);
     else
-        return new Field(decoded_headers_size - pseudo_headers_fragment_size, decoded_headers +
-            pseudo_headers_fragment_size, false);
+        return new Field(decoded_headers_size, decoded_headers, false);
 }
index ce554d981b3606331d0891f0f42be9564506ecb4..09b188d3482b97c3c9dc9d29eebdbc5ddf13a986 100644 (file)
@@ -39,33 +39,39 @@ public:
         Http2EventGen* const _events, Http2Infractions* const _infractions) :
         events(_events), infractions(_infractions), decode_table(flow_data, src_id) { }
     bool decode_headers(const uint8_t* encoded_headers, const uint32_t encoded_headers_length,
-        uint8_t* decoded_headers, Http2StartLine* start_line);
+        uint8_t* decoded_headers, Http2StartLine* start_line, bool trailers);
     bool write_decoded_headers(const uint8_t* in_buffer, const uint32_t in_length,
         uint8_t* decoded_header_buffer, uint32_t decoded_header_length, uint32_t& bytes_written);
     bool decode_header_line(const uint8_t* encoded_header_buffer,
         const uint32_t encoded_header_length, uint32_t& bytes_consumed,
         uint8_t* decoded_header_buffer, const uint32_t decoded_header_length,
         uint32_t& bytes_written);
-    bool decode_literal_header_line(const uint8_t* encoded_header_buffer,
-        const uint32_t encoded_header_length, const uint8_t name_index_mask,
-        const Http2HpackIntDecode& decode_int, bool with_indexing, uint32_t& bytes_consumed,
+    bool handle_dynamic_size_update(const uint8_t* encoded_header_buffer,
+        const uint32_t encoded_header_length, uint32_t& bytes_consumed);
+    const HpackTableEntry* get_hpack_table_entry(const uint8_t* encoded_header_buffer,
+        const uint32_t encoded_header_length, const Http2HpackIntDecode& decode_int,
+        uint32_t& bytes_consumed);
+    bool write_header_part(Field& header, const uint8_t* suffix, uint32_t suffix_length,
         uint8_t* decoded_header_buffer, const uint32_t decoded_header_length,
         uint32_t& bytes_written);
-    bool decode_string_literal(const uint8_t* encoded_header_buffer,
-        const uint32_t encoded_header_length, uint32_t &bytes_consumed,
-        uint8_t* decoded_header_buffer, const uint32_t decoded_header_length,
-        uint32_t &bytes_written, Field& field);
     bool decode_indexed_name(const uint8_t* encoded_header_buffer,
         const uint32_t encoded_header_length, const Http2HpackIntDecode& decode_int,
-        uint32_t& bytes_consumed, uint8_t* decoded_header_buffer,
-        const uint32_t decoded_header_length, uint32_t& bytes_written, Field& name);
+        uint32_t& bytes_consumed, uint8_t* decoded_header_buffer, const uint32_t decoded_header_length,
+        uint32_t& bytes_written, Field& name);
+    bool decode_literal_header_line(const uint8_t* encoded_header_buffer,
+        const uint32_t encoded_header_length, const uint8_t name_index_mask,
+        const Http2HpackIntDecode& decode_int, bool with_indexing, uint32_t& bytes_consumed,
+        uint8_t* decoded_header_buffer, const uint32_t decoded_header_length, uint32_t& bytes_written,
+        Field& name, Field& value);
     bool decode_indexed_header(const uint8_t* encoded_header_buffer,
         const uint32_t encoded_header_length, const Http2HpackIntDecode& decode_int,
-        uint32_t& bytes_consumed, uint8_t* decoded_header_buffer,
-        const uint32_t decoded_header_length, uint32_t& bytes_written);
-    bool handle_dynamic_size_update(const uint8_t* encoded_header_buffer,
-        const uint32_t encoded_header_length, const Http2HpackIntDecode &decode_int,
-        uint32_t& bytes_consumed, uint32_t& bytes_written);
+        uint32_t &bytes_consumed, uint8_t* decoded_header_buffer, const uint32_t decoded_header_length,
+        uint32_t& bytes_written, Field& name, Field& value);
+    bool decode_string_literal(const uint8_t* encoded_header_buffer,
+        const uint32_t encoded_header_length, uint32_t& bytes_consumed,
+        uint8_t* decoded_header_buffer, const uint32_t decoded_header_length,
+        uint32_t& bytes_written, Field& field);
+
     bool finalize_start_line();
     const Field* get_start_line();
     const Field* get_decoded_headers(const uint8_t* const decoded_headers);
@@ -73,8 +79,8 @@ public:
 
 private:
     Http2StartLine* start_line;
+    bool pseudo_headers_allowed;
     uint32_t decoded_headers_size;
-    uint32_t pseudo_headers_fragment_size;
     bool decode_error;
     Http2EventGen* const events;
     Http2Infractions* const infractions;
@@ -88,6 +94,7 @@ private:
     HpackIndexTable decode_table;
     bool table_size_update_allowed = true;
     uint8_t num_table_size_updates = 0;
+    bool is_trailers = false;
 };
 
 #endif
index f062fb1a7400d2a2a19372cf2a87f5499344934a..7387ff47f2de9c25ac870158d24edcb5b678dfb3 100644 (file)
@@ -33,7 +33,7 @@ class HpackDynamicTable
 {
 public:
     // FIXIT-P This array can be optimized to start smaller and grow on demand 
-    HpackDynamicTable() : circular_buf(4096, nullptr) {}
+    HpackDynamicTable() : circular_buf(ARRAY_CAPACITY, nullptr) {}
     ~HpackDynamicTable();
     const HpackTableEntry* get_entry(uint32_t index) const;
     bool add_entry(const Field& name, const Field& value);
index ebfd226308fd715ff04ccb3d114d2670b2419fa2..8f7aada3ad1b3aafbee6a1c306fdea6359fc32f2 100644 (file)
@@ -42,56 +42,43 @@ const char* Http2RequestLine::SCHEME_NAME = ":scheme";
 const char* Http2RequestLine::OPTIONS = "OPTIONS";
 const char* Http2RequestLine::CONNECT = "CONNECT";
 
-void Http2RequestLine::process_pseudo_header_name(const uint8_t* const& name, uint32_t length)
+void Http2RequestLine::process_pseudo_header(const Field& name, const Field& value)
 {
-    process_pseudo_header_precheck();
-
-    if (length == AUTHORITY_NAME_LENGTH and memcmp(name, AUTHORITY_NAME, length) == 0 and
-            authority.length() <= 0)
-        value_coming = AUTHORITY;
-    else if (length == METHOD_NAME_LENGTH and memcmp(name, METHOD_NAME, length) == 0 and
-            method.length() <= 0)
-        value_coming = METHOD;
-    else if (length == PATH_NAME_LENGTH and memcmp(name, PATH_NAME, length) == 0 and
-            path.length() <= 0)
-        value_coming = PATH;
-    else if (length == SCHEME_NAME_LENGTH and memcmp(name, SCHEME_NAME, length) == 0 and
-            scheme.length() <= 0)
-        value_coming = SCHEME;
-    else
+    Field *field;
+    if ((name.length() == AUTHORITY_NAME_LENGTH) and
+        (memcmp(name.start(), AUTHORITY_NAME, name.length()) == 0) and (authority.length() <= 0))
     {
-        *infractions += INF_INVALID_PSEUDO_HEADER;
-        events->create_event(EVENT_INVALID_HEADER);
-        value_coming = HEADER__INVALID;
+        field = &authority;
     }
-}
-
-void Http2RequestLine::process_pseudo_header_value(const uint8_t* const& value, const uint32_t length)
-{
-    switch (value_coming)
+    else if ((name.length() == METHOD_NAME_LENGTH) and
+        (memcmp(name.start(), METHOD_NAME, name.length()) == 0) and (method.length() <= 0))
+    {
+        field = &method;
+    }
+    else if ((name.length() == PATH_NAME_LENGTH) and
+        (memcmp(name.start(), PATH_NAME, name.length()) == 0) and (path.length() <= 0))
     {
-        case AUTHORITY:
-            authority.set(length, value);
-            break;
-        case METHOD:
-            method.set(length, value);
-            break;
-        case PATH:
-            path.set(length, value);
-            break;
-        case SCHEME:
-            scheme.set(length, value);
-            break;
-        default:
-            // ignore invalid pseudo-header value - alert generated in process_pseudo_header_name
-            break;
+        field = &path;
     }
-    value_coming = HEADER__NONE;
+    else if ((name.length() == SCHEME_NAME_LENGTH) and
+        (memcmp(name.start(), SCHEME_NAME, name.length()) == 0) and (scheme.length() <= 0))
+    {
+        field = &scheme;
+    }
+    else
+    {
+        *infractions += INF_INVALID_PSEUDO_HEADER;
+        events->create_event(EVENT_INVALID_PSEUDO_HEADER);
+        return;
+    }
+    uint8_t* value_str = new uint8_t[value.length()];
+    memcpy(value_str, value.start(), value.length());
+    field->set(value.length(), value_str, true);
 }
 
 // This is called on the first non-pseudo-header. Select the appropriate URI form based on the
 // provided pseudo-headers and generate the start line
-bool Http2RequestLine::generate_start_line()
+bool Http2RequestLine::generate_start_line(const Field*& start_line)
 {
     uint32_t bytes_written = 0;
 
@@ -211,5 +198,7 @@ bool Http2RequestLine::generate_start_line()
     bytes_written += 2;
     assert(bytes_written == start_line_length);
 
+    start_line = new Field(start_line_length, start_line_buffer, false);
+
     return true;
 }
index db76a765b5d000e4fc3ddc2a954d62310b9d3b68..1b48eeb346e61a5e4519537d0904db8c5aaaff2f 100644 (file)
@@ -29,9 +29,8 @@
 class Http2RequestLine : public Http2StartLine
 {
 public:
-    void process_pseudo_header_name(const uint8_t* const& name, uint32_t length) override;
-    void process_pseudo_header_value(const uint8_t* const& value, const uint32_t length) override;
-    bool generate_start_line() override;
+    void process_pseudo_header(const Field& name, const Field& value) override;
+    bool generate_start_line(const Field*& start_line) override;
 
     friend Http2StartLine* Http2StartLine::new_start_line_generator(HttpCommon::SourceId source_id,
         Http2EventGen* const events, Http2Infractions* const infractions);
index aeb99056d23dc98266ff130dead5f5e1e7881419..940b6cc7d918d64b7ce9051b526669ea62c0b7d1 100644 (file)
@@ -50,22 +50,3 @@ Http2StartLine* Http2StartLine::new_start_line_generator(SourceId source_id,
         return new Http2StatusLine(events, infractions);
 }
 
-void Http2StartLine::process_pseudo_header_precheck()
-{
-    if (finalized)
-    {
-        *infractions += INF_PSEUDO_HEADER_AFTER_REGULAR_HEADER;
-        events->create_event(EVENT_INVALID_HEADER);
-    }
-}
-
-bool Http2StartLine::finalize()
-{
-    finalized = true;
-    return generate_start_line();
-}
-
-const Field* Http2StartLine::get_start_line()
-{
-    return new Field(start_line_length, start_line_buffer, false);
-}
index 0ffeceb0b2712b7049369e98a67f96f3435aeb93..a4dbff2e9e452e374baa0974778129841684339b 100644 (file)
@@ -43,29 +43,17 @@ public:
 
     friend class Http2Hpack;
 
-    const Field* get_start_line();
-    virtual void process_pseudo_header_name(const uint8_t* const& name, uint32_t length) = 0;
-    virtual void process_pseudo_header_value(const uint8_t* const& value, const uint32_t length) =
-        0;
-    bool finalize();
-    bool is_finalized() { return finalized; }
-    bool is_pseudo_value() { return value_coming != Http2Enums::HEADER__NONE; }
-    bool is_pseudo_name(const uint8_t* const& name) { return name[0] == ':'; }
+    virtual bool generate_start_line(const Field*& start_line) = 0;
+    virtual void process_pseudo_header(const Field& name, const Field& value) = 0;
 
 protected:
     Http2StartLine(Http2EventGen* const events, Http2Infractions* const infractions) :
         events(events), infractions(infractions) { }
 
-    void process_pseudo_header_precheck();
-    virtual bool generate_start_line() = 0;
-
     Http2EventGen* const events;
     Http2Infractions* const infractions;
-    bool finalized = false;
     uint32_t start_line_length = 0;
     uint8_t *start_line_buffer = nullptr;
-    Http2Enums::PseudoHeaders value_coming = Http2Enums::HEADER__NONE;
-    uint32_t pseudo_header_fragment_size = 0;
 
     // Version string is HTTP/1.1
     static const char* http_version_string;
index 6b5998431c613e22ed6b1f29bf6c360d7d423f26..f66fee8d3231a532967c8dc4449d3b1884192177 100644 (file)
@@ -36,32 +36,24 @@ using namespace Http2Enums;
 
 const char* Http2StatusLine::STATUS_NAME = ":status";
 
-void Http2StatusLine::process_pseudo_header_name(const uint8_t* const& name, uint32_t length)
+void Http2StatusLine::process_pseudo_header(const Field& name, const Field& value)
 {
-    process_pseudo_header_precheck();
-
-    if (length == STATUS_NAME_LENGTH and memcmp(name, STATUS_NAME, length) == 0 and
-            status.length() <= 0)
-        value_coming = STATUS;
+    if ((name.length() == STATUS_NAME_LENGTH) and
+        (memcmp(name.start(), STATUS_NAME, name.length()) == 0) and (status.length() <= 0))
+    {
+        uint8_t* value_str = new uint8_t[value.length()];
+        memcpy(value_str, value.start(), value.length());
+        status.set(value.length(), value_str, true);
+    }
     else
     {
         *infractions += INF_INVALID_PSEUDO_HEADER;
-        events->create_event(EVENT_INVALID_HEADER);
-        value_coming = HEADER__INVALID;
+        events->create_event(EVENT_INVALID_PSEUDO_HEADER);
     }
 }
 
-void Http2StatusLine::process_pseudo_header_value(const uint8_t* const& value, const uint32_t length)
-{
-    // ignore invalid pseudo-header value - alert generated in process_pseudo_header_name
-    if  (value_coming == STATUS)
-        status.set(length, (const uint8_t*) value);
-
-    value_coming = HEADER__NONE;
-}
-
 // This is called on the first non-pseudo-header.
-bool Http2StatusLine::generate_start_line()
+bool Http2StatusLine::generate_start_line(const Field*& start_line)
 {
     uint32_t bytes_written = 0;
 
@@ -88,5 +80,7 @@ bool Http2StatusLine::generate_start_line()
     bytes_written += 2;
     assert(bytes_written == start_line_length);
 
+    start_line = new Field(start_line_length, start_line_buffer, false);
+
     return true;
 }
index 2ef5dad966d5c8d080fe597fe6baf679db79e335..9b497089e0b4750b080923257d6e64d08fcef33a 100644 (file)
@@ -28,9 +28,8 @@
 class Http2StatusLine : public Http2StartLine
 {
 public:
-    void process_pseudo_header_name(const uint8_t* const& name, uint32_t length) override;
-    void process_pseudo_header_value(const uint8_t* const& value, const uint32_t length) override;
-    bool generate_start_line() override;
+    void process_pseudo_header(const Field& name, const Field& value) override;
+    bool generate_start_line(const Field*& start_line) override;
 
     friend Http2StartLine* Http2StartLine::new_start_line_generator(HttpCommon::SourceId source_id,
         Http2EventGen* const events, Http2Infractions* const infractions);
index 829e27983c471afe22daf8b30373779fcb1c7534..e08ae1f65f12655a287c55699048019e3b162948 100644 (file)
@@ -102,7 +102,8 @@ bool Http2Stream::is_open(HttpCommon::SourceId source_id)
     return (state[source_id] == STATE_OPEN) || (state[source_id] == STATE_OPEN_DATA);
 }
 
-void Http2Stream::finish_msg_body(HttpCommon::SourceId source_id, bool expect_trailers)
+void Http2Stream::finish_msg_body(HttpCommon::SourceId source_id, bool expect_trailers,
+    bool clear_partial_buffer)
 {
     uint32_t http_flush_offset = 0;
     Http2DummyPacket dummy_pkt;
@@ -110,7 +111,7 @@ void Http2Stream::finish_msg_body(HttpCommon::SourceId source_id, bool expect_tr
     uint32_t unused = 0;
     const H2BodyState body_state = expect_trailers ?
         H2_BODY_COMPLETE_EXPECT_TRAILERS : H2_BODY_COMPLETE;
-    get_hi_flow_data()->finish_h2_body(source_id, body_state);
+    get_hi_flow_data()->finish_h2_body(source_id, body_state, clear_partial_buffer);
     const snort::StreamSplitter::Status scan_result = session_data->hi_ss[source_id]->scan(
         &dummy_pkt, nullptr, 0, unused, &http_flush_offset);
     assert(scan_result == snort::StreamSplitter::FLUSH);
index b5c80f71c0a141437a38811ce2aed6d688cf72bb..2ae810eeee524075678339fb91df1a21c5afd5ca 100644 (file)
@@ -68,7 +68,8 @@ public:
         { end_stream_on_data_flush[source_id] = true; }
     bool is_end_stream_on_data_flush(HttpCommon::SourceId source_id)
         { return end_stream_on_data_flush[source_id]; }
-    void finish_msg_body(HttpCommon::SourceId source_id, bool expect_trailers = false);
+    void finish_msg_body(HttpCommon::SourceId source_id, bool expect_trailers,
+        bool clear_partial_buffer);
 
 #ifdef REG_TEST
     void print_frame(FILE* output);
index 5e1d07296caad69fcc9ec6cad50e6f91f931db84..9b57ef38238410507ab43fda3f5dc716830fa181 100644 (file)
@@ -321,7 +321,7 @@ StreamSplitter::Status Http2StreamSplitter::implement_scan(Http2FlowData* sessio
                     if ((type == FT_HEADERS) and
                         (session_data->current_stream[source_id]) == next_stream)
                     {
-                        stream->finish_msg_body(source_id, true);
+                        stream->finish_msg_body(source_id, true, false);
                     }
                     session_data->stream_in_hi = NO_STREAM_ID;
                     return StreamSplitter::FLUSH;
index 535f9345afbfb3d5c08ca9b19a45af3483f479bc..4a4808f9b1580bbc8b676c5f1fa7171a0a2a72e8 100644 (file)
@@ -47,6 +47,9 @@ const RuleMap Http2Module::http2_events[] =
     { EVENT_DYNAMIC_TABLE_OVERFLOW, "HTTP/2 dynamic table size limit exceeded" },
     { EVENT_INVALID_STARTLINE, "invalid HTTP/2 start line" },
     { EVENT_PADDING_LEN, "HTTP/2 padding length is bigger than frame data size" },
+    { EVENT_PSEUDO_HEADER_AFTER_REGULAR_HEADER, "HTTP/2 pseudo-header after regular header" },
+    { EVENT_PSEUDO_HEADER_IN_TRAILERS, "HTTP/2 pseudo-header in trailers" },
+    { EVENT_INVALID_PSEUDO_HEADER, "invalid HTTP/2 pseudo-header" },
     { 0, nullptr }
 };
 
index 3b2dc4ac341c74135964be8d3bed63d7b4397094..2a462d41c02b72d009fa14290dfc65c21baf0520 100644 (file)
@@ -254,11 +254,24 @@ uint16_t HttpFlowData::get_memory_usage_estimate()
     return memory_usage_estimate;
 }
 
-void HttpFlowData::finish_h2_body(HttpCommon::SourceId source_id, HttpEnums::H2BodyState state)
+void HttpFlowData::finish_h2_body(HttpCommon::SourceId source_id, HttpEnums::H2BodyState state,
+    bool clear_partial_buffer)
 {
     assert(h2_body_state[source_id] == H2_BODY_NOT_COMPLETE);
     h2_body_state[source_id] = state;
     partial_flush[source_id] = false;
+    if (clear_partial_buffer)
+    {
+        // We've already sent all data through detection so no need to reinspect. Just need to
+        // prep for trailers
+        partial_buffer_length[source_id] = 0;
+        delete[] partial_buffer[source_id];
+        partial_buffer[source_id] = nullptr;
+        partial_inspected_octets[source_id] = 0;
+        partial_detect_length[source_id] = 0;
+        delete[] partial_detect_buffer[source_id];
+        partial_detect_buffer[source_id] = nullptr;
+    }
 }
 
 #ifdef REG_TEST
index c0f914634b4acf0698049d0754beac06b9117216..1471efa51da1300becac992d54130ed16a6675f0 100644 (file)
@@ -71,7 +71,8 @@ public:
     HttpEnums::SectionType get_type_expected(HttpCommon::SourceId source_id) const
     { return type_expected[source_id]; }
 
-    void finish_h2_body(HttpCommon::SourceId source_id, HttpEnums::H2BodyState state);
+    void finish_h2_body(HttpCommon::SourceId source_id, HttpEnums::H2BodyState state,
+        bool clear_partial_buffer);
 
     void reset_partial_flush(HttpCommon::SourceId source_id)
     { partial_flush[source_id] = false; }