Explicitly adds "host" to the set of accepted request headers, and allows "host" in place of ":authority".
PiperOrigin-RevId: 437861314
diff --git a/http2/adapter/header_validator.cc b/http2/adapter/header_validator.cc
index 1220634..d269501 100644
--- a/http2/adapter/header_validator.cc
+++ b/http2/adapter/header_validator.cc
@@ -50,13 +50,6 @@
return true;
}
-// Returns whether `authority` contains only characters from the `host` ABNF
-// from RFC 3986 section 3.2.2.
-bool IsValidAuthority(absl::string_view authority) {
- static const CharMap valid_chars = BuildValidCharMap(kValidAuthorityChars);
- return AllCharsInMap(authority, valid_chars);
-}
-
bool IsValidHeaderName(absl::string_view name) {
static const CharMap valid_chars =
BuildValidCharMap(kHttp2HeaderNameAllowedChars);
@@ -127,6 +120,7 @@
status_.clear();
method_.clear();
path_.clear();
+ authority_ = absl::nullopt;
content_length_.reset();
}
@@ -166,7 +160,7 @@
status_ = std::string(value);
} else if (key == ":method") {
method_ = std::string(value);
- } else if (key == ":authority" && !IsValidAuthority(value)) {
+ } else if (key == ":authority" && !ValidateAndSetAuthority(value)) {
return HEADER_FIELD_INVALID;
} else if (key == ":path") {
if (value.empty()) {
@@ -176,6 +170,17 @@
path_ = std::string(value);
}
pseudo_headers_.push_back(std::string(key));
+ } else if (key == "host") {
+ if (!status_.empty()) {
+ // Response headers can contain "Host".
+ } else {
+ if (!authority_.has_value()) {
+ pseudo_headers_.push_back(std::string(":authority"));
+ }
+ if (!ValidateAndSetAuthority(value)) {
+ return HEADER_FIELD_INVALID;
+ }
+ }
} else if (key == "content-length") {
const bool success = HandleContentLength(value);
if (!success) {
@@ -234,5 +239,19 @@
return true;
}
+// Returns whether `authority` contains only characters from the `host` ABNF
+// from RFC 3986 section 3.2.2.
+bool HeaderValidator::ValidateAndSetAuthority(absl::string_view authority) {
+ static const CharMap valid_chars = BuildValidCharMap(kValidAuthorityChars);
+ if (!AllCharsInMap(authority, valid_chars)) {
+ return false;
+ }
+ if (authority_.has_value() && authority != authority_.value()) {
+ return false;
+ }
+ authority_ = std::string(authority);
+ return true;
+}
+
} // namespace adapter
} // namespace http2
diff --git a/http2/adapter/header_validator.h b/http2/adapter/header_validator.h
index 30f16d5..8b8744e 100644
--- a/http2/adapter/header_validator.h
+++ b/http2/adapter/header_validator.h
@@ -50,8 +50,10 @@
private:
bool HandleContentLength(absl::string_view value);
+ bool ValidateAndSetAuthority(absl::string_view authority);
std::vector<std::string> pseudo_headers_;
+ absl::optional<std::string> authority_ = absl::nullopt;
std::string status_;
std::string method_;
std::string path_;
diff --git a/http2/adapter/header_validator_test.cc b/http2/adapter/header_validator_test.cc
index 85ff3bb..a6d0326 100644
--- a/http2/adapter/header_validator_test.cc
+++ b/http2/adapter/header_validator_test.cc
@@ -134,37 +134,76 @@
}
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();
-
- // These characters should be allowed. (Not exhaustive.)
- for (const absl::string_view c : {"1", "-", "!", ":", "+", "=", ","}) {
- HeaderValidator::HeaderStatus status = v.ValidateSingleHeader(
- ":authority", absl::StrCat("ho", c, "st.example.com"));
- EXPECT_EQ(HeaderValidator::HEADER_OK, status);
+ for (Header to_add : kSampleRequestPseudoheaders) {
+ EXPECT_EQ(HeaderValidator::HEADER_OK,
+ v.ValidateSingleHeader(to_add.first, to_add.second));
}
- // These should not.
- for (const absl::string_view c : {"\r", "\n", "|", "\\", "`"}) {
- HeaderValidator::HeaderStatus status = v.ValidateSingleHeader(
- ":authority", absl::StrCat("ho", c, "st.example.com"));
- EXPECT_EQ(HeaderValidator::HEADER_FIELD_INVALID, status);
+ // 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));
}
-
- // IPv4 example
- HeaderValidator::HeaderStatus status =
- v.ValidateSingleHeader(":authority", "123.45.67.89");
- EXPECT_EQ(HeaderValidator::HEADER_OK, status);
-
- // IPv6 examples
- status = v.ValidateSingleHeader(":authority",
- "2001:0db8:85a3:0000:0000:8a2e:0370:7334");
- EXPECT_EQ(HeaderValidator::HEADER_OK, status);
- status = v.ValidateSingleHeader(":authority", "[::1]:80");
- EXPECT_EQ(HeaderValidator::HEADER_OK, status);
-
- // Empty field
- status = v.ValidateSingleHeader(":authority", "");
- EXPECT_EQ(HeaderValidator::HEADER_OK, status);
+ // If "host" and ":authority" have different values, validation fails.
+ EXPECT_EQ(HeaderValidator::HEADER_FIELD_INVALID,
+ v.ValidateSingleHeader("host", "www.bar.com"));
}
TEST(HeaderValidatorTest, RequestPseudoHeaders) {
@@ -354,6 +393,17 @@
}
}
+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;
diff --git a/http2/adapter/nghttp2_adapter_test.cc b/http2/adapter/nghttp2_adapter_test.cc
index d238fd2..7a2bb11 100644
--- a/http2/adapter/nghttp2_adapter_test.cc
+++ b/http2/adapter/nghttp2_adapter_test.cc
@@ -3228,6 +3228,72 @@
EXPECT_THAT(visitor.data(), EqualsFrames({SpdyFrameType::SETTINGS}));
}
+TEST(NgHttp2AdapterTest, ServerHandlesHostHeader) {
+ DataSavingVisitor visitor;
+ auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor);
+
+ const std::string frames = TestFrameSequence()
+ .ClientPreface()
+ .Headers(1,
+ {{":method", "POST"},
+ {":scheme", "https"},
+ {":path", "/this/is/request/one"},
+ {"host", "example.com"}},
+ /*fin=*/true)
+ .Headers(3,
+ {{":method", "POST"},
+ {":scheme", "https"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"},
+ {"host", "example.com"}},
+ /*fin=*/true)
+ .Headers(5,
+ {{":method", "POST"},
+ {":scheme", "https"},
+ {":authority", "foo.com"},
+ {":path", "/this/is/request/one"},
+ {"host", "bar.com"}},
+ /*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());
+
+ EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 5));
+ EXPECT_CALL(visitor, OnBeginHeadersForStream(1));
+ EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(4);
+ EXPECT_CALL(visitor, OnEndHeadersForStream(1));
+ EXPECT_CALL(visitor, OnEndStream(1));
+
+ EXPECT_CALL(visitor, OnFrameHeader(3, _, HEADERS, 5));
+ EXPECT_CALL(visitor, OnBeginHeadersForStream(3));
+ EXPECT_CALL(visitor, OnHeaderForStream(3, _, _)).Times(5);
+ EXPECT_CALL(visitor, OnEndHeadersForStream(3));
+ EXPECT_CALL(visitor, OnEndStream(3));
+
+ EXPECT_CALL(visitor, OnFrameHeader(5, _, HEADERS, 5));
+ EXPECT_CALL(visitor, OnBeginHeadersForStream(5));
+ EXPECT_CALL(visitor, OnHeaderForStream(5, _, _)).Times(5);
+ EXPECT_CALL(visitor, OnEndHeadersForStream(5));
+ EXPECT_CALL(visitor, OnEndStream(5));
+
+ const int64_t result = adapter->ProcessBytes(frames);
+ EXPECT_EQ(frames.size(), static_cast<size_t>(result));
+
+ EXPECT_TRUE(adapter->want_write());
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0));
+
+ int send_result = adapter->Send();
+ EXPECT_EQ(0, send_result);
+ visitor.Clear();
+}
+
// Tests the case where the response body is in the progress of being sent while
// trailers are queued.
TEST(NgHttp2AdapterTest, ServerSubmitsTrailersWhileDataDeferred) {
diff --git a/http2/adapter/oghttp2_adapter_test.cc b/http2/adapter/oghttp2_adapter_test.cc
index 9e3b1cc..eed1071 100644
--- a/http2/adapter/oghttp2_adapter_test.cc
+++ b/http2/adapter/oghttp2_adapter_test.cc
@@ -4178,6 +4178,81 @@
EqualsFrames({SpdyFrameType::SETTINGS, SpdyFrameType::SETTINGS}));
}
+TEST(OgHttp2AdapterTest, ServerHandlesHostHeader) {
+ DataSavingVisitor visitor;
+ OgHttp2Adapter::Options options{.perspective = Perspective::kServer};
+ auto adapter = OgHttp2Adapter::Create(visitor, options);
+
+ const std::string frames = TestFrameSequence()
+ .ClientPreface()
+ .Headers(1,
+ {{":method", "POST"},
+ {":scheme", "https"},
+ {":path", "/this/is/request/one"},
+ {"host", "example.com"}},
+ /*fin=*/true)
+ .Headers(3,
+ {{":method", "POST"},
+ {":scheme", "https"},
+ {":authority", "example.com"},
+ {":path", "/this/is/request/one"},
+ {"host", "example.com"}},
+ /*fin=*/true)
+ .Headers(5,
+ {{":method", "POST"},
+ {":scheme", "https"},
+ {":authority", "foo.com"},
+ {":path", "/this/is/request/one"},
+ {"host", "bar.com"}},
+ /*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());
+
+ EXPECT_CALL(visitor, OnFrameHeader(1, _, HEADERS, 5));
+ EXPECT_CALL(visitor, OnBeginHeadersForStream(1));
+ EXPECT_CALL(visitor, OnHeaderForStream(1, _, _)).Times(4);
+ EXPECT_CALL(visitor, OnEndHeadersForStream(1));
+ EXPECT_CALL(visitor, OnEndStream(1));
+
+ EXPECT_CALL(visitor, OnFrameHeader(3, _, HEADERS, 5));
+ EXPECT_CALL(visitor, OnBeginHeadersForStream(3));
+ EXPECT_CALL(visitor, OnHeaderForStream(3, _, _)).Times(5);
+ EXPECT_CALL(visitor, OnEndHeadersForStream(3));
+ EXPECT_CALL(visitor, OnEndStream(3));
+
+ EXPECT_CALL(visitor, OnFrameHeader(5, _, HEADERS, 5));
+ EXPECT_CALL(visitor, OnBeginHeadersForStream(5));
+ EXPECT_CALL(visitor, OnHeaderForStream(5, _, _)).Times(4);
+ EXPECT_CALL(
+ visitor,
+ OnInvalidFrame(5, Http2VisitorInterface::InvalidFrameError::kHttpHeader));
+
+ const int64_t result = adapter->ProcessBytes(frames);
+ EXPECT_EQ(frames.size(), static_cast<size_t>(result));
+
+ EXPECT_TRUE(adapter->want_write());
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, 0, 0x1));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, 0, 0x1, 0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 5, 4, 0x0));
+ EXPECT_CALL(visitor,
+ OnFrameSent(RST_STREAM, 5, 4, 0x0,
+ static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR)));
+ EXPECT_CALL(visitor, OnCloseStream(5, Http2ErrorCode::HTTP2_NO_ERROR));
+
+ int send_result = adapter->Send();
+ EXPECT_EQ(0, send_result);
+ visitor.Clear();
+}
+
// Tests the case where the response body is in the progress of being sent while
// trailers are queued.
TEST(OgHttp2AdapterTest, ServerSubmitsTrailersWhileDataDeferred) {