Adds a NoopHeaderValidator, which does not perform any validation. OgHttp2Session will use NoopHeaderValidator iff the constructor option `validate_http_headers` is set to false. Protected by new behavior is guarded by a constructor option, default false. PiperOrigin-RevId: 448102524
diff --git a/quiche/http2/adapter/noop_header_validator.cc b/quiche/http2/adapter/noop_header_validator.cc new file mode 100644 index 0000000..f39342d --- /dev/null +++ b/quiche/http2/adapter/noop_header_validator.cc
@@ -0,0 +1,22 @@ +#include "quiche/http2/adapter/noop_header_validator.h" + +#include "absl/strings/escaping.h" +#include "quiche/common/platform/api/quiche_logging.h" + +namespace http2 { +namespace adapter { + +HeaderValidatorBase::HeaderStatus NoopHeaderValidator::ValidateSingleHeader( + absl::string_view key, absl::string_view value) { + if (key == ":status") { + status_ = std::string(value); + } + return HEADER_OK; +} + +bool NoopHeaderValidator::FinishHeaderBlock(HeaderType /* type */) { + return true; +} + +} // namespace adapter +} // namespace http2
diff --git a/quiche/http2/adapter/noop_header_validator.h b/quiche/http2/adapter/noop_header_validator.h new file mode 100644 index 0000000..52d1791 --- /dev/null +++ b/quiche/http2/adapter/noop_header_validator.h
@@ -0,0 +1,25 @@ +#ifndef QUICHE_HTTP2_ADAPTER_NOOP_HEADER_VALIDATOR_H_ +#define QUICHE_HTTP2_ADAPTER_NOOP_HEADER_VALIDATOR_H_ + +#include "absl/strings/string_view.h" +#include "quiche/http2/adapter/header_validator_base.h" +#include "quiche/common/platform/api/quiche_export.h" + +namespace http2 { +namespace adapter { + +// A validator that does not actually perform any validation. +class QUICHE_EXPORT_PRIVATE NoopHeaderValidator : public HeaderValidatorBase { + public: + NoopHeaderValidator() = default; + + HeaderStatus ValidateSingleHeader(absl::string_view key, + absl::string_view value) override; + + bool FinishHeaderBlock(HeaderType type) override; +}; + +} // namespace adapter +} // namespace http2 + +#endif // QUICHE_HTTP2_ADAPTER_NOOP_HEADER_VALIDATOR_H_
diff --git a/quiche/http2/adapter/noop_header_validator_test.cc b/quiche/http2/adapter/noop_header_validator_test.cc new file mode 100644 index 0000000..078a84b --- /dev/null +++ b/quiche/http2/adapter/noop_header_validator_test.cc
@@ -0,0 +1,523 @@ +#include "quiche/http2/adapter/noop_header_validator.h" + +#include <utility> +#include <vector> + +#include "absl/strings/str_cat.h" +#include "absl/types/optional.h" +#include "quiche/common/platform/api/quiche_test.h" + +namespace http2 { +namespace adapter { +namespace test { + +using ::testing::Optional; + +using Header = std::pair<absl::string_view, absl::string_view>; +constexpr Header kSampleRequestPseudoheaders[] = {{":authority", "www.foo.com"}, + {":method", "GET"}, + {":path", "/foo"}, + {":scheme", "https"}}; + +TEST(NoopHeaderValidatorTest, HeaderNameEmpty) { + NoopHeaderValidator v; + NoopHeaderValidator::HeaderStatus status = + v.ValidateSingleHeader("", "value"); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, status); +} + +TEST(NoopHeaderValidatorTest, HeaderValueEmpty) { + NoopHeaderValidator v; + NoopHeaderValidator::HeaderStatus status = v.ValidateSingleHeader("name", ""); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, status); +} + +TEST(NoopHeaderValidatorTest, ExceedsMaxSize) { + NoopHeaderValidator v; + v.SetMaxFieldSize(64u); + NoopHeaderValidator::HeaderStatus status = + v.ValidateSingleHeader("name", "value"); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, status); + status = v.ValidateSingleHeader( + "name2", + "Antidisestablishmentariansism is supercalifragilisticexpialodocious."); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, status); +} + +TEST(NoopHeaderValidatorTest, AnyNameCharIsValid) { + NoopHeaderValidator v; + char pseudo_name[] = ":met hod"; + char name[] = "na me"; + for (int i = std::numeric_limits<char>::min(); + i < std::numeric_limits<char>::max(); ++i) { + char c = static_cast<char>(i); + // Test a pseudo-header name with this char. + pseudo_name[3] = c; + auto sv = absl::string_view(pseudo_name, 8); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(sv, "value")); + // Test a regular header name with this char. + name[2] = c; + sv = absl::string_view(name, 5); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(sv, "value")); + } +} + +TEST(NoopHeaderValidatorTest, AnyValueCharIsValid) { + NoopHeaderValidator v; + char value[] = "val ue"; + for (int i = std::numeric_limits<char>::min(); + i < std::numeric_limits<char>::max(); ++i) { + char c = static_cast<char>(i); + value[3] = c; + auto sv = absl::string_view(value, 6); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader("name", sv)); + } +} + +TEST(NoopHeaderValidatorTest, AnyStatusIsValid) { + NoopHeaderValidator v; + + for (HeaderType type : {HeaderType::RESPONSE, HeaderType::RESPONSE_100}) { + v.StartHeaderBlock(); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(":status", "bar")); + EXPECT_TRUE(v.FinishHeaderBlock(type)); + + v.StartHeaderBlock(); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(":status", "10")); + EXPECT_TRUE(v.FinishHeaderBlock(type)); + + v.StartHeaderBlock(); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(":status", "9000")); + EXPECT_TRUE(v.FinishHeaderBlock(type)); + + v.StartHeaderBlock(); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(":status", "400")); + EXPECT_TRUE(v.FinishHeaderBlock(type)); + } +} + +TEST(NoopHeaderValidatorTest, AnyAuthorityCharIsValid) { + char value[] = "ho st.example.com"; + for (int i = std::numeric_limits<char>::min(); + i < std::numeric_limits<char>::max(); ++i) { + char c = static_cast<char>(i); + value[2] = c; + auto sv = absl::string_view(value, 17); + for (absl::string_view key : {":authority", "host"}) { + NoopHeaderValidator v; + v.StartHeaderBlock(); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(key, sv)); + } + } +} + +TEST(NoopHeaderValidatorTest, RequestHostAndAuthority) { + NoopHeaderValidator v; + v.StartHeaderBlock(); + for (Header to_add : kSampleRequestPseudoheaders) { + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(to_add.first, to_add.second)); + } + // If both "host" and ":authority" have the same value, validation succeeds. + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader("host", "www.foo.com")); + EXPECT_TRUE(v.FinishHeaderBlock(HeaderType::REQUEST)); + + v.StartHeaderBlock(); + for (Header to_add : kSampleRequestPseudoheaders) { + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(to_add.first, to_add.second)); + } + // If "host" and ":authority" have different values, validation still + // succeeds. + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader("host", "www.bar.com")); +} + +TEST(NoopHeaderValidatorTest, RequestPseudoHeaders) { + NoopHeaderValidator v; + for (Header to_skip : kSampleRequestPseudoheaders) { + v.StartHeaderBlock(); + for (Header to_add : kSampleRequestPseudoheaders) { + if (to_add != to_skip) { + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(to_add.first, to_add.second)); + } + } + // Even if a pseudo-header is missing, final validation will succeed. + EXPECT_TRUE(v.FinishHeaderBlock(HeaderType::REQUEST)); + } + + // When all pseudo-headers are present, final validation will succeed. + v.StartHeaderBlock(); + for (Header to_add : kSampleRequestPseudoheaders) { + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(to_add.first, to_add.second)); + } + EXPECT_TRUE(v.FinishHeaderBlock(HeaderType::REQUEST)); + + // When an extra pseudo-header is present, final validation will still + // succeed. + v.StartHeaderBlock(); + for (Header to_add : kSampleRequestPseudoheaders) { + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(to_add.first, to_add.second)); + } + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(":extra", "blah")); + EXPECT_TRUE(v.FinishHeaderBlock(HeaderType::REQUEST)); + + // When a required pseudo-header is repeated, final validation will succeed. + for (Header to_repeat : kSampleRequestPseudoheaders) { + v.StartHeaderBlock(); + for (Header to_add : kSampleRequestPseudoheaders) { + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(to_add.first, to_add.second)); + if (to_add == to_repeat) { + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(to_add.first, to_add.second)); + } + } + EXPECT_TRUE(v.FinishHeaderBlock(HeaderType::REQUEST)); + } +} + +TEST(NoopHeaderValidatorTest, WebsocketPseudoHeaders) { + NoopHeaderValidator v; + v.StartHeaderBlock(); + for (Header to_add : kSampleRequestPseudoheaders) { + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(to_add.first, to_add.second)); + } + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(":protocol", "websocket")); + // Validation always succeeds. + EXPECT_TRUE(v.FinishHeaderBlock(HeaderType::REQUEST)); + + // This is a no-op for NoopHeaderValidator. + v.AllowConnect(); + + v.StartHeaderBlock(); + for (Header to_add : kSampleRequestPseudoheaders) { + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(to_add.first, to_add.second)); + } + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(":protocol", "websocket")); + // The validator does not check for a CONNECT request. + EXPECT_TRUE(v.FinishHeaderBlock(HeaderType::REQUEST)); + + v.StartHeaderBlock(); + for (Header to_add : kSampleRequestPseudoheaders) { + if (to_add.first == ":method") { + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(to_add.first, "CONNECT")); + } else { + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(to_add.first, to_add.second)); + } + } + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(":protocol", "websocket")); + // After allowing the method, `:protocol` is acepted for CONNECT requests. + EXPECT_TRUE(v.FinishHeaderBlock(HeaderType::REQUEST)); +} + +TEST(NoopHeaderValidatorTest, AsteriskPathPseudoHeader) { + NoopHeaderValidator v; + + // The validator does not perform any path validation. + v.StartHeaderBlock(); + for (Header to_add : kSampleRequestPseudoheaders) { + if (to_add.first == ":path") { + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(to_add.first, "*")); + } else { + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(to_add.first, to_add.second)); + } + } + EXPECT_TRUE(v.FinishHeaderBlock(HeaderType::REQUEST)); + + v.StartHeaderBlock(); + for (Header to_add : kSampleRequestPseudoheaders) { + if (to_add.first == ":path") { + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(to_add.first, "*")); + } else if (to_add.first == ":method") { + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(to_add.first, "OPTIONS")); + } else { + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(to_add.first, to_add.second)); + } + } + EXPECT_TRUE(v.FinishHeaderBlock(HeaderType::REQUEST)); +} + +TEST(NoopHeaderValidatorTest, InvalidPathPseudoHeader) { + NoopHeaderValidator v; + + // An empty path is allowed. + v.StartHeaderBlock(); + for (Header to_add : kSampleRequestPseudoheaders) { + if (to_add.first == ":path") { + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(to_add.first, "")); + } else { + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(to_add.first, to_add.second)); + } + } + EXPECT_TRUE(v.FinishHeaderBlock(HeaderType::REQUEST)); + + // A path that does not start with a slash is allowed. + v.StartHeaderBlock(); + for (Header to_add : kSampleRequestPseudoheaders) { + if (to_add.first == ":path") { + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(to_add.first, "shawarma")); + } else { + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(to_add.first, to_add.second)); + } + } + EXPECT_TRUE(v.FinishHeaderBlock(HeaderType::REQUEST)); +} + +TEST(NoopHeaderValidatorTest, ResponsePseudoHeaders) { + NoopHeaderValidator v; + + for (HeaderType type : {HeaderType::RESPONSE, HeaderType::RESPONSE_100}) { + // When `:status` is missing, validation succeeds. + v.StartHeaderBlock(); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader("foo", "bar")); + EXPECT_TRUE(v.FinishHeaderBlock(type)); + + // When all pseudo-headers are present, final validation succeeds. + v.StartHeaderBlock(); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(":status", "199")); + EXPECT_TRUE(v.FinishHeaderBlock(type)); + EXPECT_EQ("199", v.status_header()); + + // When `:status` is repeated, validation succeeds. + v.StartHeaderBlock(); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(":status", "199")); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(":status", "299")); + EXPECT_TRUE(v.FinishHeaderBlock(type)); + + // When an extra pseudo-header is present, final validation succeeds. + v.StartHeaderBlock(); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(":status", "199")); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(":extra", "blorp")); + EXPECT_TRUE(v.FinishHeaderBlock(type)); + } +} + +TEST(NoopHeaderValidatorTest, ResponseWithHost) { + NoopHeaderValidator v; + + v.StartHeaderBlock(); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(":status", "200")); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader("host", "myserver.com")); + EXPECT_TRUE(v.FinishHeaderBlock(HeaderType::RESPONSE)); +} + +TEST(NoopHeaderValidatorTest, Response204) { + NoopHeaderValidator v; + + v.StartHeaderBlock(); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(":status", "204")); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader("x-content", "is not present")); + EXPECT_TRUE(v.FinishHeaderBlock(HeaderType::RESPONSE)); +} + +TEST(NoopHeaderValidatorTest, ResponseWithMultipleIdenticalContentLength) { + NoopHeaderValidator v; + + v.StartHeaderBlock(); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(":status", "200")); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader("content-length", "13")); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader("content-length", "13")); +} + +TEST(NoopHeaderValidatorTest, ResponseWithMultipleDifferingContentLength) { + NoopHeaderValidator v; + + v.StartHeaderBlock(); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(":status", "200")); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader("content-length", "13")); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader("content-length", "17")); +} + +TEST(NoopHeaderValidatorTest, Response204WithContentLengthZero) { + NoopHeaderValidator v; + + v.StartHeaderBlock(); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(":status", "204")); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader("x-content", "is not present")); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader("content-length", "0")); + EXPECT_TRUE(v.FinishHeaderBlock(HeaderType::RESPONSE)); +} + +TEST(NoopHeaderValidatorTest, Response204WithContentLength) { + NoopHeaderValidator v; + + v.StartHeaderBlock(); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(":status", "204")); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader("x-content", "is not present")); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader("content-length", "1")); +} + +TEST(NoopHeaderValidatorTest, Response100) { + NoopHeaderValidator v; + + v.StartHeaderBlock(); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(":status", "100")); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader("x-content", "is not present")); + EXPECT_TRUE(v.FinishHeaderBlock(HeaderType::RESPONSE)); +} + +TEST(NoopHeaderValidatorTest, Response100WithContentLengthZero) { + NoopHeaderValidator v; + + v.StartHeaderBlock(); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(":status", "100")); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader("x-content", "is not present")); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader("content-length", "0")); + EXPECT_TRUE(v.FinishHeaderBlock(HeaderType::RESPONSE)); +} + +TEST(NoopHeaderValidatorTest, Response100WithContentLength) { + NoopHeaderValidator v; + + v.StartHeaderBlock(); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(":status", "100")); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader("x-content", "is not present")); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader("content-length", "1")); +} + +TEST(NoopHeaderValidatorTest, ResponseTrailerPseudoHeaders) { + NoopHeaderValidator v; + + // When no pseudo-headers are present, validation will succeed. + v.StartHeaderBlock(); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader("foo", "bar")); + EXPECT_TRUE(v.FinishHeaderBlock(HeaderType::RESPONSE_TRAILER)); + + // When a pseudo-header is present, validation will succeed. + v.StartHeaderBlock(); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(":status", "200")); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader("foo", "bar")); + EXPECT_TRUE(v.FinishHeaderBlock(HeaderType::RESPONSE_TRAILER)); +} + +TEST(NoopHeaderValidatorTest, ValidContentLength) { + NoopHeaderValidator v; + + v.StartHeaderBlock(); + EXPECT_EQ(v.content_length(), absl::nullopt); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader("content-length", "41")); + EXPECT_EQ(v.content_length(), absl::nullopt); + + v.StartHeaderBlock(); + EXPECT_EQ(v.content_length(), absl::nullopt); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader("content-length", "42")); + EXPECT_EQ(v.content_length(), absl::nullopt); +} + +TEST(NoopHeaderValidatorTest, InvalidContentLength) { + NoopHeaderValidator v; + + v.StartHeaderBlock(); + EXPECT_EQ(v.content_length(), absl::nullopt); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader("content-length", "")); + EXPECT_EQ(v.content_length(), absl::nullopt); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader("content-length", "nan")); + EXPECT_EQ(v.content_length(), absl::nullopt); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader("content-length", "-42")); + EXPECT_EQ(v.content_length(), absl::nullopt); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader("content-length", "42")); + EXPECT_EQ(v.content_length(), absl::nullopt); +} + +TEST(NoopHeaderValidatorTest, TeHeader) { + NoopHeaderValidator v; + + v.StartHeaderBlock(); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader("te", "trailers")); + + v.StartHeaderBlock(); + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader("te", "trailers, deflate")); +} + +TEST(NoopHeaderValidatorTest, ConnectionSpecificHeaders) { + const std::vector<Header> connection_headers = { + {"connection", "keep-alive"}, {"proxy-connection", "keep-alive"}, + {"keep-alive", "timeout=42"}, {"transfer-encoding", "chunked"}, + {"upgrade", "h2c"}, + }; + for (const auto& [connection_key, connection_value] : connection_headers) { + NoopHeaderValidator v; + v.StartHeaderBlock(); + for (const auto& [sample_key, sample_value] : kSampleRequestPseudoheaders) { + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(sample_key, sample_value)); + } + EXPECT_EQ(NoopHeaderValidator::HEADER_OK, + v.ValidateSingleHeader(connection_key, connection_value)); + } +} + +} // namespace test +} // namespace adapter +} // namespace http2
diff --git a/quiche/http2/adapter/oghttp2_adapter_test.cc b/quiche/http2/adapter/oghttp2_adapter_test.cc index 606cd22..077e33c 100644 --- a/quiche/http2/adapter/oghttp2_adapter_test.cc +++ b/quiche/http2/adapter/oghttp2_adapter_test.cc
@@ -7433,6 +7433,129 @@ SpdyFrameType::WINDOW_UPDATE})); } +// Verifies that NoopHeaderValidator allows several header combinations that +// would otherwise be invalid. +TEST(OgHttp2AdapterTest, NoopHeaderValidatorTest) { + DataSavingVisitor visitor; + OgHttp2Adapter::Options options; + options.perspective = Perspective::kServer; + options.validate_http_headers = false; + auto adapter = OgHttp2Adapter::Create(visitor, options); + + const std::string frames = TestFrameSequence() + .ClientPreface() + .Headers(1, + {{":method", "POST"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/1"}, + {"content-length", "7"}, + {"content-length", "7"}}, + /*fin=*/false) + .Headers(3, + {{":method", "POST"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/3"}, + {"content-length", "11"}, + {"content-length", "13"}}, + /*fin=*/false) + .Headers(5, + {{":method", "POST"}, + {":scheme", "https"}, + {":authority", "foo.com"}, + {":path", "/"}, + {"host", "bar.com"}}, + /*fin=*/true) + .Headers(7, + {{":method", "POST"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/"}, + {"Accept", "uppercase, oh boy!"}}, + /*fin=*/false) + .Headers(9, + {{":method", "POST"}, + {":scheme", "https"}, + {":authority", "ex|ample.com"}, + {":path", "/"}}, + /*fin=*/false) + .Headers(11, + {{":method", "GET"}, + {":scheme", "https"}, + {":authority", "example.com"}, + {":path", "/"}, + {"content-length", "nan"}}, + /*fin=*/true) + .Serialize(); + testing::InSequence s; + + // Client preface (empty SETTINGS) + EXPECT_CALL(visitor, OnFrameHeader(0, 0, SETTINGS, 0)); + EXPECT_CALL(visitor, OnSettingsStart()); + EXPECT_CALL(visitor, OnSettingsEnd()); + // Stream 1 + EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 4)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(1)); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "POST")); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":scheme", "https")); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":authority", "example.com")); + EXPECT_CALL(visitor, OnHeaderForStream(1, ":path", "/1")); + EXPECT_CALL(visitor, OnHeaderForStream(1, "content-length", "7")); + EXPECT_CALL(visitor, OnHeaderForStream(1, "content-length", "7")); + EXPECT_CALL(visitor, OnEndHeadersForStream(1)); + // Stream 3 + EXPECT_CALL(visitor, OnFrameHeader(3, _, HEADERS, 4)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(3)); + EXPECT_CALL(visitor, OnHeaderForStream(3, ":method", "POST")); + EXPECT_CALL(visitor, OnHeaderForStream(3, ":scheme", "https")); + EXPECT_CALL(visitor, OnHeaderForStream(3, ":authority", "example.com")); + EXPECT_CALL(visitor, OnHeaderForStream(3, ":path", "/3")); + EXPECT_CALL(visitor, OnHeaderForStream(3, "content-length", "11")); + EXPECT_CALL(visitor, OnHeaderForStream(3, "content-length", "13")); + EXPECT_CALL(visitor, OnEndHeadersForStream(3)); + // Stream 5 + EXPECT_CALL(visitor, OnFrameHeader(5, _, HEADERS, 5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(5)); + EXPECT_CALL(visitor, OnHeaderForStream(5, ":method", "POST")); + EXPECT_CALL(visitor, OnHeaderForStream(5, ":scheme", "https")); + EXPECT_CALL(visitor, OnHeaderForStream(5, ":authority", "foo.com")); + EXPECT_CALL(visitor, OnHeaderForStream(5, ":path", "/")); + EXPECT_CALL(visitor, OnHeaderForStream(5, "host", "bar.com")); + EXPECT_CALL(visitor, OnEndHeadersForStream(5)); + EXPECT_CALL(visitor, OnEndStream(5)); + // Stream 7 + EXPECT_CALL(visitor, OnFrameHeader(7, _, HEADERS, 4)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(7)); + EXPECT_CALL(visitor, OnHeaderForStream(7, ":method", "POST")); + EXPECT_CALL(visitor, OnHeaderForStream(7, ":scheme", "https")); + EXPECT_CALL(visitor, OnHeaderForStream(7, ":authority", "example.com")); + EXPECT_CALL(visitor, OnHeaderForStream(7, ":path", "/")); + EXPECT_CALL(visitor, OnHeaderForStream(7, "Accept", "uppercase, oh boy!")); + EXPECT_CALL(visitor, OnEndHeadersForStream(7)); + // Stream 9 + EXPECT_CALL(visitor, OnFrameHeader(9, _, HEADERS, 4)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(9)); + EXPECT_CALL(visitor, OnHeaderForStream(9, ":method", "POST")); + EXPECT_CALL(visitor, OnHeaderForStream(9, ":scheme", "https")); + EXPECT_CALL(visitor, OnHeaderForStream(9, ":authority", "ex|ample.com")); + EXPECT_CALL(visitor, OnHeaderForStream(9, ":path", "/")); + EXPECT_CALL(visitor, OnEndHeadersForStream(9)); + // Stream 11 + EXPECT_CALL(visitor, OnFrameHeader(11, _, HEADERS, 5)); + EXPECT_CALL(visitor, OnBeginHeadersForStream(11)); + EXPECT_CALL(visitor, OnHeaderForStream(11, ":method", "GET")); + EXPECT_CALL(visitor, OnHeaderForStream(11, ":scheme", "https")); + EXPECT_CALL(visitor, OnHeaderForStream(11, ":authority", "example.com")); + EXPECT_CALL(visitor, OnHeaderForStream(11, ":path", "/")); + EXPECT_CALL(visitor, OnHeaderForStream(11, "content-length", "nan")); + EXPECT_CALL(visitor, OnEndHeadersForStream(11)); + EXPECT_CALL(visitor, OnEndStream(11)); + + const int64_t result = adapter->ProcessBytes(frames); + EXPECT_EQ(frames.size(), static_cast<size_t>(result)); +} + } // namespace } // namespace test } // namespace adapter
diff --git a/quiche/http2/adapter/oghttp2_session.cc b/quiche/http2/adapter/oghttp2_session.cc index 563b6db..d495ae0 100644 --- a/quiche/http2/adapter/oghttp2_session.cc +++ b/quiche/http2/adapter/oghttp2_session.cc
@@ -10,6 +10,7 @@ #include "quiche/http2/adapter/http2_protocol.h" #include "quiche/http2/adapter/http2_util.h" #include "quiche/http2/adapter/http2_visitor_interface.h" +#include "quiche/http2/adapter/noop_header_validator.h" #include "quiche/http2/adapter/oghttp2_util.h" #include "quiche/spdy/core/spdy_protocol.h" @@ -219,7 +220,13 @@ OgHttp2Session::PassthroughHeadersHandler::PassthroughHeadersHandler( OgHttp2Session& session, Http2VisitorInterface& visitor) : session_(session), visitor_(visitor) { - validator_ = absl::make_unique<HeaderValidator>(); + if (session_.options_.validate_http_headers) { + QUICHE_LOG(INFO) << "birenroy | instantiating regular header validator"; + validator_ = absl::make_unique<HeaderValidator>(); + } else { + QUICHE_LOG(INFO) << "birenroy | instantiating noop header validator"; + validator_ = absl::make_unique<NoopHeaderValidator>(); + } } void OgHttp2Session::PassthroughHeadersHandler::OnHeaderBlockStart() { @@ -332,6 +339,7 @@ OgHttp2Session::OgHttp2Session(Http2VisitorInterface& visitor, Options options) : visitor_(visitor), + options_(options), event_forwarder_([this]() { return !latched_error_; }, *this), receive_logger_( &event_forwarder_, TracePerspectiveAsString(options.perspective), @@ -347,8 +355,7 @@ SendWindowUpdate(kConnectionStreamId, window_update_delta); }, options.should_window_update_fn, - /*update_window_on_notify=*/false), - options_(options) { + /*update_window_on_notify=*/false) { decoder_.set_visitor(&receive_logger_); decoder_.set_extension_visitor(this); if (options_.max_header_list_bytes) {
diff --git a/quiche/http2/adapter/oghttp2_session.h b/quiche/http2/adapter/oghttp2_session.h index 5c3caa0..458d0da 100644 --- a/quiche/http2/adapter/oghttp2_session.h +++ b/quiche/http2/adapter/oghttp2_session.h
@@ -72,6 +72,9 @@ // Whether to allow `obs-text` (characters from hexadecimal 0x80 to 0xff) in // header field values. bool allow_obs_text = true; + // If true, validates header field names and values according to RFC 7230 + // and RFC 7540. + bool validate_http_headers = true; }; OgHttp2Session(Http2VisitorInterface& visitor, Options options); @@ -426,6 +429,8 @@ // Receives events when inbound frames are parsed. Http2VisitorInterface& visitor_; + const Options options_; + // Forwards received events to the session if it can accept them. EventForwarder event_forwarder_; @@ -512,7 +517,6 @@ std::numeric_limits<uint32_t>::max(); uint32_t max_inbound_concurrent_streams_ = std::numeric_limits<uint32_t>::max(); - const Options options_; // The HPACK encoder header table capacity that will be applied when // acking SETTINGS from the peer. Only contains a value if the peer advertises