Disables Cookie header field crumbling in OgHttp2Session by default. Addresses https://github.com/envoyproxy/envoy/issues/32611. PiperOrigin-RevId: 613371306
diff --git a/quiche/http2/adapter/nghttp2_adapter_test.cc b/quiche/http2/adapter/nghttp2_adapter_test.cc index d118ed8..27770f5 100644 --- a/quiche/http2/adapter/nghttp2_adapter_test.cc +++ b/quiche/http2/adapter/nghttp2_adapter_test.cc
@@ -5884,6 +5884,64 @@ EXPECT_EQ(client_visitor.data().size(), static_cast<size_t>(result)); } +TEST(NgHttp2AdapterInteractionTest, ClientServerInteractionWithCookies) { + DataSavingVisitor client_visitor; + auto client_adapter = NgHttp2Adapter::CreateClientAdapter(client_visitor); + + client_adapter->SubmitSettings({}); + + const std::vector<Header> headers1 = + ToHeaders({{":method", "GET"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}, + {"cookie", "a; b=2; c"}, + {"cookie", "d=e, f, g; h"}}); + + const int32_t stream_id1 = + client_adapter->SubmitRequest(headers1, nullptr, nullptr); + ASSERT_GT(stream_id1, 0); + + EXPECT_CALL(client_visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(client_visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + EXPECT_CALL(client_visitor, + OnBeforeFrameSent(HEADERS, stream_id1, _, + END_STREAM_FLAG | END_HEADERS_FLAG)); + EXPECT_CALL(client_visitor, + OnFrameSent(HEADERS, stream_id1, _, + END_STREAM_FLAG | END_HEADERS_FLAG, 0)); + int send_result = client_adapter->Send(); + EXPECT_EQ(0, send_result); + + DataSavingVisitor server_visitor; + auto server_adapter = NgHttp2Adapter::CreateServerAdapter(server_visitor); + + testing::InSequence s; + + // Client preface (empty SETTINGS) + EXPECT_CALL(server_visitor, OnFrameHeader(0, _, SETTINGS, 0)); + EXPECT_CALL(server_visitor, OnSettingsStart()); + EXPECT_CALL(server_visitor, OnSetting).Times(testing::AnyNumber()); + EXPECT_CALL(server_visitor, OnSettingsEnd()); + // Stream 1 + EXPECT_CALL(server_visitor, OnFrameHeader(1, _, HEADERS, 5)); + EXPECT_CALL(server_visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(server_visitor, OnHeaderForStream(1, ":method", "GET")); + EXPECT_CALL(server_visitor, OnHeaderForStream(1, ":scheme", "http")); + EXPECT_CALL(server_visitor, + OnHeaderForStream(1, ":authority", "example.com")); + EXPECT_CALL(server_visitor, + OnHeaderForStream(1, ":path", "/this/is/request/one")); + // Cookie values are preserved verbatim. + EXPECT_CALL(server_visitor, OnHeaderForStream(1, "cookie", "a; b=2; c")); + EXPECT_CALL(server_visitor, OnHeaderForStream(1, "cookie", "d=e, f, g; h")); + EXPECT_CALL(server_visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(server_visitor, OnEndStream(1)); + + int64_t result = server_adapter->ProcessBytes(client_visitor.data()); + EXPECT_EQ(client_visitor.data().size(), static_cast<size_t>(result)); +} + TEST(NgHttp2AdapterTest, ServerForbidsWindowUpdateOnIdleStream) { DataSavingVisitor visitor; auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor);
diff --git a/quiche/http2/adapter/oghttp2_adapter_test.cc b/quiche/http2/adapter/oghttp2_adapter_test.cc index 9cc9f57..92b9b86 100644 --- a/quiche/http2/adapter/oghttp2_adapter_test.cc +++ b/quiche/http2/adapter/oghttp2_adapter_test.cc
@@ -6676,6 +6676,67 @@ EXPECT_EQ(client_visitor.data().size(), static_cast<size_t>(result)); } +TEST(OgHttp2AdapterInteractionTest, ClientServerInteractionWithCookies) { + DataSavingVisitor client_visitor; + OgHttp2Adapter::Options client_options; + client_options.perspective = Perspective::kClient; + auto client_adapter = OgHttp2Adapter::Create(client_visitor, client_options); + + // The Cookie header field value will be consolidated during HEADERS frame + // serialization. + const std::vector<Header> headers1 = + ToHeaders({{":method", "GET"}, + {":scheme", "http"}, + {":authority", "example.com"}, + {":path", "/this/is/request/one"}, + {"cookie", "a; b=2; c"}, + {"cookie", "d=e, f, g; h"}}); + + const int32_t stream_id1 = + client_adapter->SubmitRequest(headers1, nullptr, nullptr); + ASSERT_GT(stream_id1, 0); + + EXPECT_CALL(client_visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0)); + EXPECT_CALL(client_visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0)); + EXPECT_CALL(client_visitor, + OnBeforeFrameSent(HEADERS, stream_id1, _, + END_STREAM_FLAG | END_HEADERS_FLAG)); + EXPECT_CALL(client_visitor, + OnFrameSent(HEADERS, stream_id1, _, + END_STREAM_FLAG | END_HEADERS_FLAG, 0)); + int send_result = client_adapter->Send(); + EXPECT_EQ(0, send_result); + + DataSavingVisitor server_visitor; + OgHttp2Adapter::Options server_options; + server_options.perspective = Perspective::kServer; + auto server_adapter = OgHttp2Adapter::Create(server_visitor, server_options); + + testing::InSequence s; + + // Client preface (empty SETTINGS) + EXPECT_CALL(server_visitor, OnFrameHeader(0, _, SETTINGS, 0)); + EXPECT_CALL(server_visitor, OnSettingsStart()); + EXPECT_CALL(server_visitor, OnSetting).Times(testing::AnyNumber()); + EXPECT_CALL(server_visitor, OnSettingsEnd()); + // Stream 1 + EXPECT_CALL(server_visitor, OnFrameHeader(1, _, HEADERS, 5)); + EXPECT_CALL(server_visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(server_visitor, OnHeaderForStream(1, ":method", "GET")); + EXPECT_CALL(server_visitor, OnHeaderForStream(1, ":scheme", "http")); + EXPECT_CALL(server_visitor, + OnHeaderForStream(1, ":authority", "example.com")); + EXPECT_CALL(server_visitor, + OnHeaderForStream(1, ":path", "/this/is/request/one")); + EXPECT_CALL(server_visitor, + OnHeaderForStream(1, "cookie", "a; b=2; c; d=e, f, g; h")); + EXPECT_CALL(server_visitor, OnEndHeadersForStream(1)); + EXPECT_CALL(server_visitor, OnEndStream(1)); + + int64_t result = server_adapter->ProcessBytes(client_visitor.data()); + EXPECT_EQ(client_visitor.data().size(), static_cast<size_t>(result)); +} + TEST(OgHttp2AdapterTest, ServerForbidsNewStreamBelowWatermark) { DataSavingVisitor visitor; OgHttp2Adapter::Options options;
diff --git a/quiche/http2/adapter/oghttp2_session.cc b/quiche/http2/adapter/oghttp2_session.cc index e43939f..c29c848 100644 --- a/quiche/http2/adapter/oghttp2_session.cc +++ b/quiche/http2/adapter/oghttp2_session.cc
@@ -376,6 +376,11 @@ headers_handler_.SetMaxFieldSize(*options_.max_header_field_size); } headers_handler_.SetAllowObsText(options_.allow_obs_text); + if (!options_.crumble_cookies) { + // As seen in https://github.com/envoyproxy/envoy/issues/32611, some HTTP/2 + // endpoints don't properly handle multiple `Cookie` header fields. + framer_.GetHpackEncoder()->DisableCookieCrumbling(); + } } OgHttp2Session::~OgHttp2Session() {}
diff --git a/quiche/http2/adapter/oghttp2_session.h b/quiche/http2/adapter/oghttp2_session.h index dead807..d61106a 100644 --- a/quiche/http2/adapter/oghttp2_session.h +++ b/quiche/http2/adapter/oghttp2_session.h
@@ -84,6 +84,9 @@ // If true, allows different values for `host` and `:authority` headers to // be present in request headers. bool allow_different_host_and_authority = false; + // If true, crumbles `Cookie` header field values for potentially better + // HPACK compression. + bool crumble_cookies = false; }; OgHttp2Session(Http2VisitorInterface& visitor, Options options);