From: Jaime Andres Castillo Leon -X (jaimeaca - SOFTSERVE INC at Cisco) Date: Thu, 24 Oct 2024 17:27:08 +0000 (+0000) Subject: Pull request #4475: http2_inspect: HTTP/2 handle multiple cookie headers X-Git-Tag: 3.5.1.0~8 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=4382d73f01a3102b39aae580b075c00d8712879d;p=thirdparty%2Fsnort3.git Pull request #4475: http2_inspect: HTTP/2 handle multiple cookie headers Merge in SNORT/snort3 from ~JAIMEACA/snort3:http2_handle_multiple_cookie to master Squashed commit of the following: commit 856c312ef84bee12338f759883bac06d5cc70983 Author: Jaime Andres Castillo Leon -X (jaimeaca - SOFTSERVE INC at Cisco) Date: Tue Oct 8 12:52:29 2024 -0400 http2_inspect: handle multiple cookie header fields --- diff --git a/src/service_inspectors/http2_inspect/CMakeLists.txt b/src/service_inspectors/http2_inspect/CMakeLists.txt index d3cf4c993..8e04a65ed 100644 --- a/src/service_inspectors/http2_inspect/CMakeLists.txt +++ b/src/service_inspectors/http2_inspect/CMakeLists.txt @@ -31,6 +31,8 @@ set (FILE_LIST http2_headers_frame_with_startline.h http2_hpack.cc http2_hpack.h + http2_hpack_cookie_header_buffer.cc + http2_hpack_cookie_header_buffer.h http2_hpack_dynamic_table.cc http2_hpack_dynamic_table.h http2_hpack_int_decode.h diff --git a/src/service_inspectors/http2_inspect/http2_hpack.cc b/src/service_inspectors/http2_inspect/http2_hpack.cc index c6ffd48f7..8f20b0b9d 100644 --- a/src/service_inspectors/http2_inspect/http2_hpack.cc +++ b/src/service_inspectors/http2_inspect/http2_hpack.cc @@ -28,13 +28,13 @@ #include "http2_enum.h" #include "http2_flow_data.h" +#include "http2_hpack_cookie_header_buffer.h" #include "http2_start_line.h" using namespace Http2Enums; #include "http2_varlen_int_decode_impl.h" #include "http2_varlen_string_decode_impl.h" -using namespace HttpCommon; Http2HpackIntDecode Http2HpackDecoder::decode_int7(7); Http2HpackIntDecode Http2HpackDecoder::decode_int6(6); @@ -300,7 +300,7 @@ bool Http2HpackDecoder::handle_dynamic_size_update(const uint8_t* encoded_header 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 uint32_t decoded_header_length, uint32_t& bytes_written, Http2CookieHeaderBuffer* cookie_buffer) { const static uint8_t DYN_TABLE_SIZE_UPDATE_MASK = 0xe0; const static uint8_t DYN_TABLE_SIZE_UPDATE_PATTERN = 0x20; @@ -339,7 +339,7 @@ bool Http2HpackDecoder::decode_header_line(const uint8_t* encoded_header_buffer, LITERAL_NO_INDEX_NAME_INDEX_MASK, decode_int4, false, bytes_consumed, decoded_header_buffer, decoded_header_length, bytes_written, name, value); - // Handle pseudoheaders + // Handle pseudoheaders or concatenated http1 headers (e.g, cookies) if (ret and bytes_written > 0) { if (decoded_header_buffer[0] == ':' and name.length() != 0) @@ -363,6 +363,12 @@ bool Http2HpackDecoder::decode_header_line(const uint8_t* encoded_header_buffer, } else if (pseudo_headers_allowed) pseudo_headers_allowed = false; + + if (Http2CookieHeaderBuffer::is_cookie(name.start(), name.length())) + { + cookie_buffer->append_value(value.start(), value.length()); + bytes_written = 0; + } } return ret; } @@ -382,6 +388,8 @@ bool Http2HpackDecoder::decode_headers(const uint8_t* encoded_headers, bool success = true; start_line = start_line_generator; decoded_headers = new uint8_t[MAX_OCTETS]; + Http2CookieHeaderBuffer cookie_buffer; + is_trailers = trailers; pseudo_headers_allowed = !is_trailers; @@ -395,11 +403,20 @@ bool Http2HpackDecoder::decode_headers(const uint8_t* encoded_headers, success = decode_header_line(encoded_headers + total_bytes_consumed, encoded_headers_length - total_bytes_consumed, line_bytes_consumed, decoded_headers + decoded_headers_size, MAX_OCTETS - decoded_headers_size, - line_bytes_written); + line_bytes_written, &cookie_buffer); total_bytes_consumed += line_bytes_consumed; decoded_headers_size += line_bytes_written; } + // append cookie header + if (success and cookie_buffer.has_headers()) + { + success = cookie_buffer.append_header_in_decoded_headers( + decoded_headers, decoded_headers_size, MAX_OCTETS, + line_bytes_written, infractions); + decoded_headers_size += line_bytes_written; + } + // Write the last CRLF to end the header. A truncated header may not have encountered an error // if the truncation is between header lines, but still shouldn't complete the header block // with the final CRLF. @@ -431,6 +448,7 @@ void Http2HpackDecoder::settings_table_size_update(uint32_t new_size) void Http2HpackDecoder::set_decoded_headers(Field& http1_header) { assert(decoded_headers); + http1_header.set(decoded_headers_size, decoded_headers, true); // The headers frame object now owns this buffer diff --git a/src/service_inspectors/http2_inspect/http2_hpack.h b/src/service_inspectors/http2_inspect/http2_hpack.h index f8dfae38c..46bd69b4f 100644 --- a/src/service_inspectors/http2_inspect/http2_hpack.h +++ b/src/service_inspectors/http2_inspect/http2_hpack.h @@ -31,6 +31,7 @@ class Field; class Http2FlowData; class Http2StartLine; +class Http2CookieHeaderBuffer; using Http2Infractions = Infractions; using Http2EventGen = EventGen + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "http2_hpack_cookie_header_buffer.h" + +const uint8_t* Http2CookieHeaderBuffer::cookie_key = (const uint8_t*)"cookie"; + +void Http2CookieHeaderBuffer::append_value(const uint8_t* start, int32_t length) +{ + // (RFC 7230) section 3.2.6 describes the syntax for the value of a header in general, including + // quoting (RFC 6265) for specifics to cookies. + if ( !buffer.empty() ) + { + buffer += (const uint8_t*)"; "; + } + else + { + // let's initialize the buffer to reduce dynamic allocation for std::basic_string; + buffer.reserve(Http2CookieHeaderBuffer::initial_buffer_size); + buffer = (const uint8_t*)"cookie: "; + } + buffer.append(start, length); +} + +bool Http2CookieHeaderBuffer::append_header_in_decoded_headers(uint8_t* decoded_header_buffer, + const uint32_t decoded_header_length, const uint32_t decoded_header_capacity, + uint32_t& bytes_written, Http2Infractions* const infractions) +{ + if ( !buffer.empty() ) + { + buffer += (const uint8_t*)"\r\n"; + } + const u8string& in = buffer; + const uint32_t in_length = in.length(); + + bytes_written = 0; + + const uint32_t new_decoded_header_length = decoded_header_length + in_length; + if (new_decoded_header_length > decoded_header_capacity) + { + *infractions += Http2Enums::Infraction::INF_DECODED_HEADER_BUFF_OUT_OF_SPACE; + return false; + } + else + { + std::copy(in.begin(), in.end(), decoded_header_buffer + decoded_header_length); + bytes_written = in_length; + } + return true; +} + +bool Http2CookieHeaderBuffer::has_headers() const +{ + return !buffer.empty(); +} diff --git a/src/service_inspectors/http2_inspect/http2_hpack_cookie_header_buffer.h b/src/service_inspectors/http2_inspect/http2_hpack_cookie_header_buffer.h new file mode 100644 index 000000000..27b7b4687 --- /dev/null +++ b/src/service_inspectors/http2_inspect/http2_hpack_cookie_header_buffer.h @@ -0,0 +1,55 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2024-2024 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_hpack_cookie_header_buffer.h author Jaime Andres Castillo Leon + +#ifndef HTTP2_HPACK_COOKIE_HEADER_BUFFER_H +#define HTTP2_HPACK_COOKIE_HEADER_BUFFER_H + +#include +#include + +#include "helpers/infractions.h" +#include "http2_enum.h" + +using Http2Infractions = Infractions; + +class Http2CookieHeaderBuffer final +{ + using u8string = std::basic_string; +public: + void append_value(const uint8_t* start, int32_t length); + bool append_header_in_decoded_headers(uint8_t* decoded_header_buffer, + const uint32_t decoded_header_length, const uint32_t decoded_header_capacity, + uint32_t& bytes_written, Http2Infractions* const infractions); + bool has_headers() const; + + static bool is_cookie(const uint8_t* start, int32_t length) + { + return length > 0 && (uint32_t)length == cookie_key_size && + std::equal(start, start + length, cookie_key); + } + +private: + u8string buffer = (const uint8_t*)""; + + static const uint32_t initial_buffer_size = 1024; + static const uint8_t* cookie_key; + static const uint32_t cookie_key_size = 6; +}; + +#endif // HTTP2_HPACK_COOKIE_HEADER_BUFFER_H diff --git a/src/service_inspectors/http2_inspect/test/CMakeLists.txt b/src/service_inspectors/http2_inspect/test/CMakeLists.txt index fc8346535..b7bf32820 100644 --- a/src/service_inspectors/http2_inspect/test/CMakeLists.txt +++ b/src/service_inspectors/http2_inspect/test/CMakeLists.txt @@ -4,3 +4,15 @@ add_cpputest( http2_hpack_string_decode_test SOURCES ../http2_huffman_state_machine.cc ) + +add_cpputest( http2_hpack_test + SOURCES + ../../http_inspect/http_field.cc + ../http2_hpack_table.cc + ../http2_hpack_dynamic_table.cc + ../http2_huffman_state_machine.cc + ../http2_start_line.cc + ../http2_request_line.cc + ../http2_hpack_cookie_header_buffer.cc + ../http2_hpack.cc +) diff --git a/src/service_inspectors/http2_inspect/test/http2_hpack_test.cc b/src/service_inspectors/http2_inspect/test/http2_hpack_test.cc new file mode 100644 index 000000000..f4dbd5498 --- /dev/null +++ b/src/service_inspectors/http2_inspect/test/http2_hpack_test.cc @@ -0,0 +1,504 @@ +//-------------------------------------------------------------------------- +// Copyright (C) 2024-2024 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_hpack_test.cc author Jaime Andres Castillo + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include "../../../flow/flow_data.h" +#include "../../../framework/counts.h" +#include "../../http_inspect/http_field.h" +#include "../../http_inspect/http_test_manager.h" + +#include "../http2_enum.h" +#include "../http2_flow_data.h" +#include "../http2_hpack.h" +#include "../http2_hpack_cookie_header_buffer.h" +#include "../http2_hpack_dynamic_table.h" +#include "../http2_hpack_int_decode.h" +#include "../http2_module.h" +#include "../http2_request_line.h" +#include "../http2_settings_frame.h" + + +#include +#include +#include + + +int snort::DetectionEngine::queue_event(unsigned int, unsigned int) { return 0; } +long HttpTestManager::print_amount {}; +bool HttpTestManager::print_hex {}; + +snort::FlowData::~FlowData() = default; +snort::FlowData::FlowData(unsigned int, snort::Inspector*) { } +THREAD_LOCAL PegCount Http2Module::peg_counts[Http2Enums::PEG_COUNT__MAX] = { }; +uint32_t Http2ConnectionSettings::get_param(uint16_t) { return 42; } +Http2DataCutter::Http2DataCutter(Http2FlowData* _session_data, HttpCommon::SourceId src_id) + : session_data(_session_data), source_id(src_id) { } +Http2Stream::~Http2Stream() = default; +Http2FlowData::Http2FlowData(snort::Flow* flow_) + : snort::FlowData(0, nullptr) , flow(flow_) , hi(nullptr) + , hpack_decoder { + Http2HpackDecoder(this, HttpCommon::SRC_CLIENT, events[HttpCommon::SRC_CLIENT], infractions[HttpCommon::SRC_CLIENT]), + Http2HpackDecoder(this, HttpCommon::SRC_SERVER, events[HttpCommon::SRC_SERVER], infractions[HttpCommon::SRC_SERVER]) + } + , data_cutter { Http2DataCutter(this, HttpCommon::SRC_CLIENT), Http2DataCutter(this, HttpCommon::SRC_SERVER) } +{ } +Http2FlowData::~Http2FlowData() +{ + delete infractions[0]; + delete infractions[1]; + + delete events[0]; + delete events[1]; +} + + +TEST_GROUP(http2_hpack_cookie_header_buffer) +{ + using u8string = std::basic_string; + + Http2CookieHeaderBuffer cookie_buffer; + // constant arrays used in several tests + const u8string cookie_key = (uint8_t*)"cookie"; + const uint32_t cookie_line_offset = u8string(cookie_key + (uint8_t*)": \r\n").length(); + + struct TestBuffer + { + public: + TestBuffer(u8string headers_content, uint32_t max_buffer_size) + : max_buffer_size(max_buffer_size), headers_buffer(new uint8_t[max_buffer_size]) + , headers_buffer_size(headers_content.length()) + { + std::copy(headers_content.begin(), headers_content.end(), headers_buffer.get()); + } + + uint32_t max_buffer_size = 0; + std::unique_ptr headers_buffer = nullptr; + uint32_t headers_buffer_size = 0; + Http2Infractions infra; + }; + + struct DecodedBuffer + { + explicit DecodedBuffer(uint32_t capacity) + : decoded_header_capacity(capacity), decoded_header_buffer(new uint8_t[capacity]) + {} + uint32_t decoded_header_capacity = 1024; + uint32_t decoded_header_length = 0; + uint32_t bytes_written = 0; + std::unique_ptr decoded_header_buffer; + Http2Infractions infra; + }; + + void setup() override + { + } + void teardown() override + { + } +}; + +// Ensure overflow situation get an INF_DECODED_HEADER_BUFF_OUT_OF_SPACE infraction +TEST(http2_hpack_cookie_header_buffer, append_to_header_line_cause_overflow_in_decoded_buffer) +{ + // arrange + // create buffer, initialize content and set capacity + TestBuffer buffer((uint8_t*)"", 5); + u8string cookie_value = (uint8_t*)"n=value"; + cookie_buffer.append_value(cookie_value.c_str(), cookie_value.length()); + // act + uint32_t bytes_written = 0; + bool success =cookie_buffer.append_header_in_decoded_headers( + buffer.headers_buffer.get(), buffer.headers_buffer_size, buffer.max_buffer_size, + bytes_written, &buffer.infra); + buffer.headers_buffer_size += bytes_written; + // assert + CHECK(bytes_written == 0); + CHECK(success == false); + using hi = Http2Enums::Infraction; + CHECK(buffer.infra & hi::INF_DECODED_HEADER_BUFF_OUT_OF_SPACE); + u8string expected_headers_value = (uint8_t*)""; + u8string headers_value(buffer.headers_buffer.get(), buffer.headers_buffer_size); + CHECK(headers_value == expected_headers_value); +} + +// Ensure cookies can be added to empty decoded buffer and no separator added +TEST(http2_hpack_cookie_header_buffer, append_to_header_line_in_empty_decoded_buffer) +{ + // arrange + // create buffer, initialize content and set capacity + TestBuffer buffer((uint8_t*)"", 1000); + u8string cookie_value = (uint8_t*)"n=value"; + + cookie_buffer.append_value(cookie_value.c_str(), cookie_value.length()); + // act + uint32_t bytes_written = 0; + cookie_buffer.append_header_in_decoded_headers( + buffer.headers_buffer.get(), buffer.headers_buffer_size, buffer.max_buffer_size, + bytes_written, &buffer.infra); + buffer.headers_buffer_size += bytes_written; + // assert + CHECK(bytes_written == cookie_line_offset + cookie_value.length()); + u8string expected_headers_value = (uint8_t*)"cookie: n=value\r\n"; + u8string headers_value(buffer.headers_buffer.get(), buffer.headers_buffer_size); + CHECK(headers_value == expected_headers_value); +} + +// Ensure cookies can be added to non-empty decoded buffer and no separator added +TEST(http2_hpack_cookie_header_buffer, append_to_header_line_at_end_of_decoded_buffer) +{ + // arrange + u8string headers_content = (uint8_t*)"accept: *\r\naccept-language: en-US,en;q=0.9\r\n"; + const uint32_t max_buffer_size = 1000; + TestBuffer buffer(headers_content, max_buffer_size); + u8string cookie_value = (uint8_t*)"n=value"; + cookie_buffer.append_value(cookie_value.c_str(), cookie_value.length()); + // act + uint32_t bytes_written = 0; + cookie_buffer.append_header_in_decoded_headers( + buffer.headers_buffer.get(), buffer.headers_buffer_size, max_buffer_size, + bytes_written, &buffer.infra); + buffer.headers_buffer_size += bytes_written; + // assert + CHECK(bytes_written == cookie_line_offset + cookie_value.length()); + u8string expected_headers_value = (uint8_t*)"accept: *\r\n" + "accept-language: en-US,en;q=0.9\r\n" + "cookie: n=value\r\n"; + u8string headers_value(buffer.headers_buffer.get(), buffer.headers_buffer_size); + CHECK(headers_value == expected_headers_value); +} + +// Ensure appended headers are separated by "; ", and the last one a "\r\n" +TEST(http2_hpack_cookie_header_buffer, appending_separators_to_multiple_cookies_one_by_one) +{ + // arrange + DecodedBuffer buf(1024); + u8string value1 = (uint8_t*)"n1=value1"; + u8string value2 = (uint8_t*)"n2=value2"; + u8string value3 = (uint8_t*)"n3=value3"; + // act + cookie_buffer.append_value(value1.c_str(), value1.length()); + cookie_buffer.append_value(value2.c_str(), value2.length()); + cookie_buffer.append_value(value3.c_str(), value3.length()); + cookie_buffer.append_header_in_decoded_headers(buf.decoded_header_buffer.get(), + buf.decoded_header_length, buf.decoded_header_capacity, buf.bytes_written, &buf.infra); + // assert + u8string expected_cookie_header_line = (uint8_t*)"cookie: n1=value1; n2=value2; n3=value3\r\n"; + u8string cookie_header_line(buf.decoded_header_buffer.get(), buf.bytes_written); + CHECK(cookie_header_line == expected_cookie_header_line); +} + +// Ensure appended headers are separated by "; ", even when finishing in ";" +TEST(http2_hpack_cookie_header_buffer, appending_separators_to_ended_semicolon_and_appending_multiple) +{ + // arrange + DecodedBuffer buf(1024); + u8string value = (uint8_t*)"n=value;"; + u8string valueN = (uint8_t*)"n1=value1; n2=value2; n3=value3"; + // act + cookie_buffer.append_value(value.c_str(), value.length()); + cookie_buffer.append_value(valueN.c_str(), valueN.length()); + cookie_buffer.append_header_in_decoded_headers(buf.decoded_header_buffer.get(), + buf.decoded_header_length, buf.decoded_header_capacity, buf.bytes_written, &buf.infra); + // assert + u8string expected_cookie_header_line = (uint8_t*)"cookie: n=value;; n1=value1; n2=value2; n3=value3\r\n"; + u8string cookie_header_line(buf.decoded_header_buffer.get(), buf.bytes_written); + CHECK(cookie_header_line == expected_cookie_header_line); +} + +// Ensure appended headers are separated by "; ", even when finishing in "; " +TEST(http2_hpack_cookie_header_buffer, appending_separators_to_ended_semicolon_space_and_appending_multiple) +{ + // arrange + DecodedBuffer buf(1024); + u8string value = (uint8_t*)"n=value; "; + u8string valueN = (uint8_t*)"n1=value1; n2=value2; n3=value3"; + // act + cookie_buffer.append_value(value.c_str(), value.length()); + cookie_buffer.append_value(valueN.c_str(), valueN.length()); + cookie_buffer.append_header_in_decoded_headers(buf.decoded_header_buffer.get(), + buf.decoded_header_length, buf.decoded_header_capacity, buf.bytes_written, &buf.infra); + // assert + u8string expected_cookie_header_line = (uint8_t*)"cookie: n=value; ; n1=value1; n2=value2; n3=value3\r\n"; + u8string cookie_header_line(buf.decoded_header_buffer.get(), buf.bytes_written); + CHECK(cookie_header_line == expected_cookie_header_line); +} + +TEST_GROUP(http2_hpack_decoder_decode_header_buffers) +{ + using u8string = std::basic_string; + + Http2EventGen* const events[2] = { new Http2EventGen, new Http2EventGen }; + Http2Infractions* const infractions[2] = { new Http2Infractions, new Http2Infractions }; + Http2HpackDecoder* hpack_decoder; + Http2StartLine* start_client_line; + Http2FlowData* session_data = new Http2FlowData(nullptr); + + void setup() override + { + hpack_decoder = new Http2HpackDecoder(session_data, HttpCommon::SRC_CLIENT, events[HttpCommon::SRC_CLIENT], infractions[HttpCommon::SRC_CLIENT]); + start_client_line = new Http2RequestLine(events[0], infractions[0]); + } + void teardown() override + { + delete session_data; + delete start_client_line; + + delete hpack_decoder; + + delete infractions[0]; + delete infractions[1]; + + delete events[0]; + delete events[1]; + } +}; + +// Ensure headers with no cookies are kept the same example with pseudoheaders +TEST(http2_hpack_decoder_decode_header_buffers, simple_test_pseudoheaders_no_cookies) +{ + // arrange + const uint8_t encoded_headers[] = { + 0x82, + 0x84, + 0x41, 0xc, 'w', 'w', 'w', '.', 't', 'e', 's', 't', '.', 'c', 'o', 'm', + 0x86, + 0x53, 0x03, 0x2a, 0x2f, 0x2a, + 0x50, 0x8d, 0x9b, 0xd9, 0xab, 0xfa, 0x52, 0x42, 0xcb, 0x40, 0xd2, 0x5f, 0xa5, 0x23, 0xb3, + 0x51, 0x8b, 0x2d, 0x4b, 0x70, 0xdd, 0xf4, 0x5a, 0xbe, 0xfb, 0x40, 0x05, 0xdf + }; + const uint32_t encoded_headers_length {sizeof(encoded_headers)/sizeof(const uint8_t)}; + u8string expected_headers_str = + (uint8_t*) "accept: */*\r\n" + "accept-encoding: gzip, deflate, br\r\n" + "accept-language: en-US,en;q=0.9\r\n" + "\r\n"; + Field http1_header; + bool trailers {false}; + // act + bool success = hpack_decoder->decode_headers(( + const uint8_t*)encoded_headers, encoded_headers_length, start_client_line, trailers); + hpack_decoder->set_decoded_headers(http1_header); + // assert + CHECK(success == true); + u8string headers_str(http1_header.start(), http1_header.start() + http1_header.length()); + CHECK(expected_headers_str == headers_str); +} + +// Ensure headers with no cookies are kept the same +TEST(http2_hpack_decoder_decode_header_buffers, simple_header_with_no_cookies) +{ + // arrange + const uint8_t encoded_headers[] = { + 0x53, 0x01, 0x2a, + }; + const uint32_t encoded_headers_length {sizeof(encoded_headers)/sizeof(const uint8_t)}; + u8string expected_headers_str = + (uint8_t*) "accept: *\r\n" + "\r\n"; + Field http1_header; + bool trailers {false}; + // act + bool success = hpack_decoder->decode_headers(( + const uint8_t*)encoded_headers, encoded_headers_length, start_client_line, trailers); + hpack_decoder->set_decoded_headers(http1_header); + // assert + CHECK(success == true); + u8string headers_str(http1_header.start(), http1_header.start() + http1_header.length()); + CHECK(expected_headers_str == headers_str); +} + +// Ensure headers with one cookies are kept the same +TEST(http2_hpack_decoder_decode_header_buffers, header_with_one_cookies) +{ + // arrange + const uint8_t encoded_headers[] = { + 0x53, 0x01, 0x2a, + 0x60, 0x07, 'n', '=', 'v', 'a', 'l', 'u', 'e', + }; + const uint32_t encoded_headers_length {sizeof(encoded_headers)/sizeof(const uint8_t)}; + u8string expected_headers_str = + (uint8_t*) "accept: *\r\n" + "cookie: n=value\r\n" + "\r\n"; + Field http1_header; + bool trailers {false}; + // act + bool success = hpack_decoder->decode_headers(( + const uint8_t*)encoded_headers, encoded_headers_length, start_client_line, trailers); + hpack_decoder->set_decoded_headers(http1_header); + // assert + CHECK(success == true); + u8string headers_str(http1_header.start(), http1_header.start() + http1_header.length()); + CHECK(expected_headers_str == headers_str); +} + +// Ensure headers with one weird cookie and several semicolon is ok +TEST(http2_hpack_decoder_decode_header_buffers, header_with_one_cookies_and_mutiple_semicolon) +{ + // arrange + const uint8_t encoded_headers[] = { + 0x53, 0x01, 0x2a, + 0x60, 0x07, 'n', '=', ';', ';', ' ', ';', ' ' + }; + const uint32_t encoded_headers_length {sizeof(encoded_headers)/sizeof(const uint8_t)}; + u8string expected_headers_str = + (uint8_t*) "accept: *\r\n" + "cookie: n=;; ; \r\n" + "\r\n"; + Field http1_header; + bool trailers {false}; + // act + bool success = hpack_decoder->decode_headers(( + const uint8_t*)encoded_headers, encoded_headers_length, start_client_line, trailers); + hpack_decoder->set_decoded_headers(http1_header); + // assert + CHECK(success == true); + u8string headers_str(http1_header.start(), http1_header.start() + http1_header.length()); + CHECK(expected_headers_str == headers_str); +} + +// Ensure the location of all cookies are concatenated and cookie header is appended at the end of the headers +TEST(http2_hpack_decoder_decode_header_buffers, header_with_cookies_at_middle_and_clutte_all_appended) +{ + // arrange + const uint8_t encoded_headers[] = { + 0x60, 0x07, 'n', '=', 'v', 'a', 'l', 'u', 'e', + 0x53, 0x01, 0x2a, + 0x60, 0x09, 'n', '2', '=', 'v', 'a', 'l', 'u', 'e', '2', + 0x60, 0x09, 'n', '3', '=', 'v', 'a', 'l', 'u', 'e', '3', + 0x51, 0x8b, 0x2d, 0x4b, 0x70, 0xdd, 0xf4, 0x5a, 0xbe, 0xfb, 0x40, 0x05, 0xdf, + }; + const uint32_t encoded_headers_length {sizeof(encoded_headers)/sizeof(const uint8_t)}; + u8string expected_headers_str = + (uint8_t*) "accept: *\r\n" + "accept-language: en-US,en;q=0.9\r\n" + "cookie: n=value; n2=value2; n3=value3\r\n" + "\r\n"; + Field http1_header; + bool trailers {false}; + // act + bool success = hpack_decoder->decode_headers(( + const uint8_t*)encoded_headers, encoded_headers_length, start_client_line, trailers); + hpack_decoder->set_decoded_headers(http1_header); + // assert + CHECK(success == true); + u8string headers_str(http1_header.start(), http1_header.start() + http1_header.length()); + CHECK(expected_headers_str == headers_str); +} + +// Corner case: Ensure empty headers are kept empty +TEST(http2_hpack_decoder_decode_header_buffers, empty_header) +{ + // arrange + const uint8_t encoded_headers[0] = {}; + const uint32_t encoded_headers_length {sizeof(encoded_headers)/sizeof(const uint8_t)}; + u8string expected_headers_str = + (uint8_t*) "\r\n"; + Field http1_header; + bool trailers {false}; + // act + bool success = hpack_decoder->decode_headers(( + const uint8_t*)encoded_headers, encoded_headers_length, start_client_line, trailers); + hpack_decoder->set_decoded_headers(http1_header); + // assert + CHECK(success == true); + u8string headers_str(http1_header.start(), http1_header.start() + http1_header.length()); + CHECK(expected_headers_str == headers_str); +} + +// Ensure that header with cookie 16384 long cookie is processed correctly +TEST(http2_hpack_decoder_decode_header_buffers, header_with_16384_long_cookie) +{ + // arrange + const uint8_t pre_header[] = { + 0x53, 0x01, 0x2a, + 0x51, 0x8b, 0x2d, 0x4b, 0x70, 0xdd, 0xf4, 0x5a, 0xbe, 0xfb, 0x40, 0x05, 0xdf, + 0x60, // 0x60: means cookie header + 0x7f, 0x81, 0x7f, // the size of the cookie content + }; + const uint32_t preheader_length = sizeof(pre_header)/sizeof(const uint8_t); + const uint32_t cookie_value_length = 16384; + const uint32_t encoded_headers_length {preheader_length + cookie_value_length}; + uint8_t encoded_headers[encoded_headers_length]; + std::copy(std::begin(pre_header), std::end(pre_header), std::begin(encoded_headers)); + encoded_headers[preheader_length] = (const uint8_t)'n'; + encoded_headers[preheader_length+1] = (const uint8_t)'='; + std::fill(std::begin(encoded_headers)+preheader_length+2, std::end(encoded_headers), (const uint8_t)'.'); + + u8string expected_headers_str = + (uint8_t*) "accept: *\r\n" + "accept-language: en-US,en;q=0.9\r\n" + "cookie: n="; + expected_headers_str += u8string(cookie_value_length-2, (uint8_t)'.') + (uint8_t*)"\r\n\r\n"; + Field http1_header; + bool trailers {false}; + // act + bool success = hpack_decoder->decode_headers( + (const uint8_t*)encoded_headers, encoded_headers_length, start_client_line, trailers); + hpack_decoder->set_decoded_headers(http1_header); + // assert + CHECK(success == true); + u8string headers_str(http1_header.start(), http1_header.start() + http1_header.length()); + CHECK(expected_headers_str == headers_str); +} + +// Ensure that header with cookie 65536 long produces an overflow +TEST(http2_hpack_decoder_decode_header_buffers, header_with_65536_long_cookie_overflow) +{ + // arrange + const uint8_t pre_header[] = { + 0x53, 0x01, 0x2a, + 0x51, 0x8b, 0x2d, 0x4b, 0x70, 0xdd, 0xf4, 0x5a, 0xbe, 0xfb, 0x40, 0x05, 0xdf, + 0x60, // 0x60: means cookie header + 0x7f, 0x81, 0xff, 0x03 // the size of the cookie content + }; + const uint32_t preheader_length = sizeof(pre_header)/sizeof(const uint8_t); + const uint32_t cookie_value_length = 65536; + const uint32_t encoded_headers_length {preheader_length + cookie_value_length}; + uint8_t encoded_headers[encoded_headers_length]; + std::copy(std::begin(pre_header), std::end(pre_header), std::begin(encoded_headers)); + encoded_headers[preheader_length] = (const uint8_t)'n'; + encoded_headers[preheader_length+1] = (const uint8_t)'='; + std::fill(std::begin(encoded_headers)+preheader_length+2, std::end(encoded_headers), (const uint8_t)'.'); + + Field http1_header; + bool trailers {false}; + // act + bool success = hpack_decoder->decode_headers( + (const uint8_t*)encoded_headers, encoded_headers_length, start_client_line, trailers); + hpack_decoder->set_decoded_headers(http1_header); + // assert + CHECK(success == false); + using hi = Http2Enums::Infraction; + CHECK(*infractions[HttpCommon::SRC_CLIENT] & hi::INF_DECODED_HEADER_BUFF_OUT_OF_SPACE); +} + + +int main(int argc, char** argv) +{ + return CommandLineTestRunner::RunAllTests(argc, argv); +}