| #include "quiche/http2/adapter/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(HeaderValidatorTest, HeaderNameEmpty) { | |
| HeaderValidator v; | |
| HeaderValidator::HeaderStatus status = v.ValidateSingleHeader("", "value"); | |
| EXPECT_EQ(HeaderValidator::HEADER_FIELD_INVALID, status); | |
| } | |
| TEST(HeaderValidatorTest, HeaderValueEmpty) { | |
| HeaderValidator v; | |
| HeaderValidator::HeaderStatus status = v.ValidateSingleHeader("name", ""); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, status); | |
| } | |
| TEST(HeaderValidatorTest, ExceedsMaxSize) { | |
| HeaderValidator v; | |
| v.SetMaxFieldSize(64u); | |
| HeaderValidator::HeaderStatus status = | |
| v.ValidateSingleHeader("name", "value"); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, status); | |
| status = v.ValidateSingleHeader( | |
| "name2", | |
| "Antidisestablishmentariansism is supercalifragilisticexpialodocious."); | |
| EXPECT_EQ(HeaderValidator::HEADER_FIELD_TOO_LONG, status); | |
| } | |
| TEST(HeaderValidatorTest, NameHasInvalidChar) { | |
| HeaderValidator v; | |
| for (const bool is_pseudo_header : {true, false}) { | |
| // These characters should be allowed. (Not exhaustive.) | |
| for (const char* c : {"!", "3", "a", "_", "|", "~"}) { | |
| const std::string name = is_pseudo_header ? absl::StrCat(":met", c, "hod") | |
| : absl::StrCat("na", c, "me"); | |
| HeaderValidator::HeaderStatus status = | |
| v.ValidateSingleHeader(name, "value"); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, status); | |
| } | |
| // These should not. (Not exhaustive.) | |
| for (const char* c : {"\\", "<", ";", "[", "=", " ", "\r", "\n", ",", "\"", | |
| "\x1F", "\x91"}) { | |
| const std::string name = is_pseudo_header ? absl::StrCat(":met", c, "hod") | |
| : absl::StrCat("na", c, "me"); | |
| HeaderValidator::HeaderStatus status = | |
| v.ValidateSingleHeader(name, "value"); | |
| EXPECT_EQ(HeaderValidator::HEADER_FIELD_INVALID, status); | |
| } | |
| // Test nul separately. | |
| { | |
| const absl::string_view name = is_pseudo_header | |
| ? absl::string_view(":met\0hod", 8) | |
| : absl::string_view("na\0me", 5); | |
| HeaderValidator::HeaderStatus status = | |
| v.ValidateSingleHeader(name, "value"); | |
| EXPECT_EQ(HeaderValidator::HEADER_FIELD_INVALID, status); | |
| } | |
| // Uppercase characters in header names should not be allowed. | |
| const std::string uc_name = is_pseudo_header ? ":Method" : "Name"; | |
| HeaderValidator::HeaderStatus status = | |
| v.ValidateSingleHeader(uc_name, "value"); | |
| EXPECT_EQ(HeaderValidator::HEADER_FIELD_INVALID, status); | |
| } | |
| } | |
| TEST(HeaderValidatorTest, ValueHasInvalidChar) { | |
| HeaderValidator v; | |
| // These characters should be allowed. (Not exhaustive.) | |
| for (const char* c : | |
| {"!", "3", "a", "_", "|", "~", "\\", "<", ";", "[", "=", "A", "\t"}) { | |
| HeaderValidator::HeaderStatus status = | |
| v.ValidateSingleHeader("name", absl::StrCat("val", c, "ue")); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, status); | |
| } | |
| // These should not. | |
| for (const char* c : {"\r", "\n"}) { | |
| HeaderValidator::HeaderStatus status = | |
| v.ValidateSingleHeader("name", absl::StrCat("val", c, "ue")); | |
| EXPECT_EQ(HeaderValidator::HEADER_FIELD_INVALID, status); | |
| } | |
| // Test nul separately. | |
| { | |
| HeaderValidator::HeaderStatus status = | |
| v.ValidateSingleHeader("name", absl::string_view("val\0ue", 6)); | |
| EXPECT_EQ(HeaderValidator::HEADER_FIELD_INVALID, status); | |
| } | |
| { | |
| // Test that obs-text is disallowed by default. | |
| EXPECT_EQ(HeaderValidator::HEADER_FIELD_INVALID, | |
| v.ValidateSingleHeader("name", "val\xa9ue")); | |
| // Test that obs-text is disallowed when configured. | |
| v.SetObsTextOption(ObsTextOption::kDisallow); | |
| EXPECT_EQ(HeaderValidator::HEADER_FIELD_INVALID, | |
| v.ValidateSingleHeader("name", "val\xa9ue")); | |
| // Test that obs-text is allowed when configured. | |
| v.SetObsTextOption(ObsTextOption::kAllow); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader("name", "val\xa9ue")); | |
| } | |
| } | |
| TEST(HeaderValidatorTest, StatusHasInvalidChar) { | |
| HeaderValidator v; | |
| for (HeaderType type : {HeaderType::RESPONSE, HeaderType::RESPONSE_100}) { | |
| // When `:status` has a non-digit value, validation will fail. | |
| v.StartHeaderBlock(); | |
| EXPECT_EQ(HeaderValidator::HEADER_FIELD_INVALID, | |
| v.ValidateSingleHeader(":status", "bar")); | |
| EXPECT_FALSE(v.FinishHeaderBlock(type)); | |
| // When `:status` is too short, validation will fail. | |
| v.StartHeaderBlock(); | |
| EXPECT_EQ(HeaderValidator::HEADER_FIELD_INVALID, | |
| v.ValidateSingleHeader(":status", "10")); | |
| EXPECT_FALSE(v.FinishHeaderBlock(type)); | |
| // When `:status` is too long, validation will fail. | |
| v.StartHeaderBlock(); | |
| EXPECT_EQ(HeaderValidator::HEADER_FIELD_INVALID, | |
| v.ValidateSingleHeader(":status", "9000")); | |
| EXPECT_FALSE(v.FinishHeaderBlock(type)); | |
| // When `:status` is just right, validation will succeed. | |
| v.StartHeaderBlock(); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(":status", "400")); | |
| EXPECT_TRUE(v.FinishHeaderBlock(type)); | |
| } | |
| } | |
| TEST(HeaderValidatorTest, AuthorityHasInvalidChar) { | |
| for (absl::string_view key : {":authority", "host"}) { | |
| // These characters should be allowed. (Not exhaustive.) | |
| for (const absl::string_view c : {"1", "-", "!", ":", "+", "=", ","}) { | |
| HeaderValidator v; | |
| v.StartHeaderBlock(); | |
| HeaderValidator::HeaderStatus status = | |
| v.ValidateSingleHeader(key, absl::StrCat("ho", c, "st.example.com")); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, status); | |
| } | |
| // These should not. | |
| for (const absl::string_view c : {"\r", "\n", "|", "\\", "`"}) { | |
| HeaderValidator v; | |
| v.StartHeaderBlock(); | |
| HeaderValidator::HeaderStatus status = | |
| v.ValidateSingleHeader(key, absl::StrCat("ho", c, "st.example.com")); | |
| EXPECT_EQ(HeaderValidator::HEADER_FIELD_INVALID, status); | |
| } | |
| { | |
| // IPv4 example | |
| HeaderValidator v; | |
| v.StartHeaderBlock(); | |
| HeaderValidator::HeaderStatus status = | |
| v.ValidateSingleHeader(key, "123.45.67.89"); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, status); | |
| } | |
| { | |
| // IPv6 examples | |
| HeaderValidator v; | |
| v.StartHeaderBlock(); | |
| HeaderValidator::HeaderStatus status = v.ValidateSingleHeader( | |
| key, "2001:0db8:85a3:0000:0000:8a2e:0370:7334"); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, status); | |
| HeaderValidator v2; | |
| v2.StartHeaderBlock(); | |
| status = v2.ValidateSingleHeader(key, "[::1]:80"); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, status); | |
| } | |
| { | |
| // Empty field | |
| HeaderValidator v; | |
| v.StartHeaderBlock(); | |
| HeaderValidator::HeaderStatus status = v.ValidateSingleHeader(key, ""); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, status); | |
| } | |
| } | |
| } | |
| TEST(HeaderValidatorTest, RequestHostAndAuthority) { | |
| HeaderValidator v; | |
| v.StartHeaderBlock(); | |
| for (Header to_add : kSampleRequestPseudoheaders) { | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(to_add.first, to_add.second)); | |
| } | |
| // If both "host" and ":authority" have the same value, validation succeeds. | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader("host", "www.foo.com")); | |
| EXPECT_TRUE(v.FinishHeaderBlock(HeaderType::REQUEST)); | |
| v.StartHeaderBlock(); | |
| for (Header to_add : kSampleRequestPseudoheaders) { | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(to_add.first, to_add.second)); | |
| } | |
| // If "host" and ":authority" have different values, validation fails. | |
| EXPECT_EQ(HeaderValidator::HEADER_FIELD_INVALID, | |
| v.ValidateSingleHeader("host", "www.bar.com")); | |
| } | |
| TEST(HeaderValidatorTest, RequestPseudoHeaders) { | |
| HeaderValidator v; | |
| for (Header to_skip : kSampleRequestPseudoheaders) { | |
| v.StartHeaderBlock(); | |
| for (Header to_add : kSampleRequestPseudoheaders) { | |
| if (to_add != to_skip) { | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(to_add.first, to_add.second)); | |
| } | |
| } | |
| // When any pseudo-header is missing, final validation will fail. | |
| EXPECT_FALSE(v.FinishHeaderBlock(HeaderType::REQUEST)); | |
| } | |
| // When all pseudo-headers are present, final validation will succeed. | |
| v.StartHeaderBlock(); | |
| for (Header to_add : kSampleRequestPseudoheaders) { | |
| EXPECT_EQ(HeaderValidator::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 fail. | |
| v.StartHeaderBlock(); | |
| for (Header to_add : kSampleRequestPseudoheaders) { | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(to_add.first, to_add.second)); | |
| } | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(":extra", "blah")); | |
| EXPECT_FALSE(v.FinishHeaderBlock(HeaderType::REQUEST)); | |
| // When a required pseudo-header is repeated, final validation will fail. | |
| for (Header to_repeat : kSampleRequestPseudoheaders) { | |
| v.StartHeaderBlock(); | |
| for (Header to_add : kSampleRequestPseudoheaders) { | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(to_add.first, to_add.second)); | |
| if (to_add == to_repeat) { | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(to_add.first, to_add.second)); | |
| } | |
| } | |
| EXPECT_FALSE(v.FinishHeaderBlock(HeaderType::REQUEST)); | |
| } | |
| } | |
| TEST(HeaderValidatorTest, ConnectHeaders) { | |
| // Too few headers. | |
| HeaderValidator v; | |
| v.StartHeaderBlock(); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(":authority", "athena.dialup.mit.edu:23")); | |
| EXPECT_FALSE(v.FinishHeaderBlock(HeaderType::REQUEST)); | |
| v.StartHeaderBlock(); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(":method", "CONNECT")); | |
| EXPECT_FALSE(v.FinishHeaderBlock(HeaderType::REQUEST)); | |
| // Too many headers. | |
| v.StartHeaderBlock(); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(":authority", "athena.dialup.mit.edu:23")); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(":method", "CONNECT")); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, v.ValidateSingleHeader(":path", "/")); | |
| EXPECT_FALSE(v.FinishHeaderBlock(HeaderType::REQUEST)); | |
| // Empty :authority | |
| v.StartHeaderBlock(); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(":authority", "")); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(":method", "CONNECT")); | |
| EXPECT_FALSE(v.FinishHeaderBlock(HeaderType::REQUEST)); | |
| // Just right. | |
| v.StartHeaderBlock(); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(":authority", "athena.dialup.mit.edu:23")); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(":method", "CONNECT")); | |
| EXPECT_TRUE(v.FinishHeaderBlock(HeaderType::REQUEST)); | |
| v.SetAllowExtendedConnect(); | |
| // "Classic" CONNECT headers should still be accepted. | |
| v.StartHeaderBlock(); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(":authority", "athena.dialup.mit.edu:23")); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(":method", "CONNECT")); | |
| EXPECT_TRUE(v.FinishHeaderBlock(HeaderType::REQUEST)); | |
| } | |
| TEST(HeaderValidatorTest, WebsocketPseudoHeaders) { | |
| HeaderValidator v; | |
| v.StartHeaderBlock(); | |
| for (Header to_add : kSampleRequestPseudoheaders) { | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(to_add.first, to_add.second)); | |
| } | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(":protocol", "websocket")); | |
| // At this point, `:protocol` is treated as an extra pseudo-header. | |
| EXPECT_FALSE(v.FinishHeaderBlock(HeaderType::REQUEST)); | |
| // Future header blocks may send the `:protocol` pseudo-header for CONNECT | |
| // requests. | |
| v.SetAllowExtendedConnect(); | |
| v.StartHeaderBlock(); | |
| for (Header to_add : kSampleRequestPseudoheaders) { | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(to_add.first, to_add.second)); | |
| } | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(":protocol", "websocket")); | |
| // The method is not "CONNECT", so `:protocol` is still treated as an extra | |
| // pseudo-header. | |
| EXPECT_FALSE(v.FinishHeaderBlock(HeaderType::REQUEST)); | |
| v.StartHeaderBlock(); | |
| for (Header to_add : kSampleRequestPseudoheaders) { | |
| if (to_add.first == ":method") { | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(to_add.first, "CONNECT")); | |
| } else { | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(to_add.first, to_add.second)); | |
| } | |
| } | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(":protocol", "websocket")); | |
| // After allowing the method, `:protocol` is acepted for CONNECT requests. | |
| EXPECT_TRUE(v.FinishHeaderBlock(HeaderType::REQUEST)); | |
| } | |
| TEST(HeaderValidatorTest, AsteriskPathPseudoHeader) { | |
| HeaderValidator v; | |
| // An asterisk :path should not be allowed for non-OPTIONS requests. | |
| v.StartHeaderBlock(); | |
| for (Header to_add : kSampleRequestPseudoheaders) { | |
| if (to_add.first == ":path") { | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(to_add.first, "*")); | |
| } else { | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(to_add.first, to_add.second)); | |
| } | |
| } | |
| EXPECT_FALSE(v.FinishHeaderBlock(HeaderType::REQUEST)); | |
| // An asterisk :path should be allowed for OPTIONS requests. | |
| v.StartHeaderBlock(); | |
| for (Header to_add : kSampleRequestPseudoheaders) { | |
| if (to_add.first == ":path") { | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(to_add.first, "*")); | |
| } else if (to_add.first == ":method") { | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(to_add.first, "OPTIONS")); | |
| } else { | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(to_add.first, to_add.second)); | |
| } | |
| } | |
| EXPECT_TRUE(v.FinishHeaderBlock(HeaderType::REQUEST)); | |
| } | |
| TEST(HeaderValidatorTest, InvalidPathPseudoHeader) { | |
| HeaderValidator v; | |
| // An empty path should fail on single header validation and finish. | |
| v.StartHeaderBlock(); | |
| for (Header to_add : kSampleRequestPseudoheaders) { | |
| if (to_add.first == ":path") { | |
| EXPECT_EQ(HeaderValidator::HEADER_FIELD_INVALID, | |
| v.ValidateSingleHeader(to_add.first, "")); | |
| } else { | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(to_add.first, to_add.second)); | |
| } | |
| } | |
| EXPECT_FALSE(v.FinishHeaderBlock(HeaderType::REQUEST)); | |
| // A path that does not start with a slash should fail on finish. | |
| v.StartHeaderBlock(); | |
| for (Header to_add : kSampleRequestPseudoheaders) { | |
| if (to_add.first == ":path") { | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(to_add.first, "shawarma")); | |
| } else { | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(to_add.first, to_add.second)); | |
| } | |
| } | |
| EXPECT_FALSE(v.FinishHeaderBlock(HeaderType::REQUEST)); | |
| } | |
| TEST(HeaderValidatorTest, ResponsePseudoHeaders) { | |
| HeaderValidator v; | |
| for (HeaderType type : {HeaderType::RESPONSE, HeaderType::RESPONSE_100}) { | |
| // When `:status` is missing, validation will fail. | |
| v.StartHeaderBlock(); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, v.ValidateSingleHeader("foo", "bar")); | |
| EXPECT_FALSE(v.FinishHeaderBlock(type)); | |
| // When all pseudo-headers are present, final validation will succeed. | |
| v.StartHeaderBlock(); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(":status", "199")); | |
| EXPECT_TRUE(v.FinishHeaderBlock(type)); | |
| EXPECT_EQ("199", v.status_header()); | |
| // When `:status` is repeated, validation will fail. | |
| v.StartHeaderBlock(); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(":status", "199")); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(":status", "299")); | |
| EXPECT_FALSE(v.FinishHeaderBlock(type)); | |
| // When an extra pseudo-header is present, final validation will fail. | |
| v.StartHeaderBlock(); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(":status", "199")); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(":extra", "blorp")); | |
| EXPECT_FALSE(v.FinishHeaderBlock(type)); | |
| } | |
| } | |
| TEST(HeaderValidatorTest, ResponseWithHost) { | |
| HeaderValidator v; | |
| v.StartHeaderBlock(); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(":status", "200")); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader("host", "myserver.com")); | |
| EXPECT_TRUE(v.FinishHeaderBlock(HeaderType::RESPONSE)); | |
| } | |
| TEST(HeaderValidatorTest, Response204) { | |
| HeaderValidator v; | |
| v.StartHeaderBlock(); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(":status", "204")); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader("x-content", "is not present")); | |
| EXPECT_TRUE(v.FinishHeaderBlock(HeaderType::RESPONSE)); | |
| } | |
| TEST(HeaderValidatorTest, ResponseWithMultipleIdenticalContentLength) { | |
| HeaderValidator v; | |
| v.StartHeaderBlock(); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(":status", "200")); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader("content-length", "13")); | |
| EXPECT_EQ(HeaderValidator::HEADER_SKIP, | |
| v.ValidateSingleHeader("content-length", "13")); | |
| } | |
| TEST(HeaderValidatorTest, ResponseWithMultipleDifferingContentLength) { | |
| HeaderValidator v; | |
| v.StartHeaderBlock(); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(":status", "200")); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader("content-length", "13")); | |
| EXPECT_EQ(HeaderValidator::HEADER_FIELD_INVALID, | |
| v.ValidateSingleHeader("content-length", "17")); | |
| } | |
| TEST(HeaderValidatorTest, Response204WithContentLengthZero) { | |
| HeaderValidator v; | |
| v.StartHeaderBlock(); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(":status", "204")); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader("x-content", "is not present")); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader("content-length", "0")); | |
| EXPECT_TRUE(v.FinishHeaderBlock(HeaderType::RESPONSE)); | |
| } | |
| TEST(HeaderValidatorTest, Response204WithContentLength) { | |
| HeaderValidator v; | |
| v.StartHeaderBlock(); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(":status", "204")); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader("x-content", "is not present")); | |
| EXPECT_EQ(HeaderValidator::HEADER_FIELD_INVALID, | |
| v.ValidateSingleHeader("content-length", "1")); | |
| } | |
| TEST(HeaderValidatorTest, Response100) { | |
| HeaderValidator v; | |
| v.StartHeaderBlock(); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(":status", "100")); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader("x-content", "is not present")); | |
| EXPECT_TRUE(v.FinishHeaderBlock(HeaderType::RESPONSE)); | |
| } | |
| TEST(HeaderValidatorTest, Response100WithContentLengthZero) { | |
| HeaderValidator v; | |
| v.StartHeaderBlock(); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(":status", "100")); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader("x-content", "is not present")); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader("content-length", "0")); | |
| EXPECT_TRUE(v.FinishHeaderBlock(HeaderType::RESPONSE)); | |
| } | |
| TEST(HeaderValidatorTest, Response100WithContentLength) { | |
| HeaderValidator v; | |
| v.StartHeaderBlock(); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(":status", "100")); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader("x-content", "is not present")); | |
| EXPECT_EQ(HeaderValidator::HEADER_FIELD_INVALID, | |
| v.ValidateSingleHeader("content-length", "1")); | |
| } | |
| TEST(HeaderValidatorTest, ResponseTrailerPseudoHeaders) { | |
| HeaderValidator v; | |
| // When no pseudo-headers are present, validation will succeed. | |
| v.StartHeaderBlock(); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, v.ValidateSingleHeader("foo", "bar")); | |
| EXPECT_TRUE(v.FinishHeaderBlock(HeaderType::RESPONSE_TRAILER)); | |
| // When any pseudo-header is present, final validation will fail. | |
| v.StartHeaderBlock(); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(":status", "200")); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, v.ValidateSingleHeader("foo", "bar")); | |
| EXPECT_FALSE(v.FinishHeaderBlock(HeaderType::RESPONSE_TRAILER)); | |
| } | |
| TEST(HeaderValidatorTest, ValidContentLength) { | |
| HeaderValidator v; | |
| v.StartHeaderBlock(); | |
| EXPECT_EQ(v.content_length(), absl::nullopt); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader("content-length", "41")); | |
| EXPECT_THAT(v.content_length(), Optional(41)); | |
| v.StartHeaderBlock(); | |
| EXPECT_EQ(v.content_length(), absl::nullopt); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader("content-length", "42")); | |
| EXPECT_THAT(v.content_length(), Optional(42)); | |
| } | |
| TEST(HeaderValidatorTest, InvalidContentLength) { | |
| HeaderValidator v; | |
| v.StartHeaderBlock(); | |
| EXPECT_EQ(v.content_length(), absl::nullopt); | |
| EXPECT_EQ(HeaderValidator::HEADER_FIELD_INVALID, | |
| v.ValidateSingleHeader("content-length", "")); | |
| EXPECT_EQ(v.content_length(), absl::nullopt); | |
| EXPECT_EQ(HeaderValidator::HEADER_FIELD_INVALID, | |
| v.ValidateSingleHeader("content-length", "nan")); | |
| EXPECT_EQ(v.content_length(), absl::nullopt); | |
| EXPECT_EQ(HeaderValidator::HEADER_FIELD_INVALID, | |
| v.ValidateSingleHeader("content-length", "-42")); | |
| EXPECT_EQ(v.content_length(), absl::nullopt); | |
| // End on a positive note. | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader("content-length", "42")); | |
| EXPECT_THAT(v.content_length(), Optional(42)); | |
| } | |
| TEST(HeaderValidatorTest, TeHeader) { | |
| HeaderValidator v; | |
| v.StartHeaderBlock(); | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader("te", "trailers")); | |
| v.StartHeaderBlock(); | |
| EXPECT_EQ(HeaderValidator::HEADER_FIELD_INVALID, | |
| v.ValidateSingleHeader("te", "trailers, deflate")); | |
| } | |
| TEST(HeaderValidatorTest, 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) { | |
| HeaderValidator v; | |
| v.StartHeaderBlock(); | |
| for (const auto& [sample_key, sample_value] : kSampleRequestPseudoheaders) { | |
| EXPECT_EQ(HeaderValidator::HEADER_OK, | |
| v.ValidateSingleHeader(sample_key, sample_value)); | |
| } | |
| EXPECT_EQ(HeaderValidator::HEADER_FIELD_INVALID, | |
| v.ValidateSingleHeader(connection_key, connection_value)); | |
| } | |
| } | |
| } // namespace test | |
| } // namespace adapter | |
| } // namespace http2 |