Adds validation for the `:authority` header field value.
The field value must contain only characters from the `host` ABNF in RFC 3986 Section 3.2.2.
PiperOrigin-RevId: 423335883
diff --git a/http2/adapter/header_validator.cc b/http2/adapter/header_validator.cc
index d5ae7a2..d4c5ee4 100644
--- a/http2/adapter/header_validator.cc
+++ b/http2/adapter/header_validator.cc
@@ -21,6 +21,30 @@
const absl::string_view kHttp2StatusValueAllowedChars = "0123456789";
+const absl::string_view kValidAuthorityChars =
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~%!$&'()["
+ "]*+,;=:";
+
+// 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 bool* valid_chars = []() {
+ using ValidCharArray = bool[256];
+ bool* chars = new ValidCharArray;
+ memset(chars, 0, sizeof(ValidCharArray));
+ for (char c : kValidAuthorityChars) {
+ chars[static_cast<uint8_t>(c)] = true;
+ }
+ return chars;
+ }();
+ for (char c : authority) {
+ if (!valid_chars[static_cast<uint8_t>(c)]) {
+ return false;
+ }
+ }
+ return true;
+}
+
bool ValidateRequestHeaders(const std::vector<std::string>& pseudo_headers,
absl::string_view method, bool allow_connect) {
QUICHE_VLOG(2) << "Request pseudo-headers: ["
@@ -102,6 +126,8 @@
status_ = std::string(value);
} else if (key == ":method") {
method_ = std::string(value);
+ } else if (key == ":authority" && !IsValidAuthority(value)) {
+ return HEADER_FIELD_INVALID;
}
pseudo_headers_.push_back(std::string(key));
} else if (key == "content-length") {
diff --git a/http2/adapter/header_validator_test.cc b/http2/adapter/header_validator_test.cc
index 0d4d7fc..addb35a 100644
--- a/http2/adapter/header_validator_test.cc
+++ b/http2/adapter/header_validator_test.cc
@@ -109,6 +109,40 @@
}
}
+TEST(HeaderValidatorTest, AuthorityHasInvalidChar) {
+ 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);
+ }
+ // 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);
+ }
+
+ // 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);
+}
+
TEST(HeaderValidatorTest, RequestPseudoHeaders) {
HeaderValidator v;
const absl::string_view headers[] = {":authority", ":method", ":path",
diff --git a/http2/adapter/nghttp2_adapter_test.cc b/http2/adapter/nghttp2_adapter_test.cc
index f2dbd67..9771f3c 100644
--- a/http2/adapter/nghttp2_adapter_test.cc
+++ b/http2/adapter/nghttp2_adapter_test.cc
@@ -2928,6 +2928,57 @@
EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::GOAWAY}));
}
+TEST(NgHttp2AdapterTest, ServerReceivesInvalidAuthority) {
+ DataSavingVisitor visitor;
+ auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor);
+
+ const std::string frames = TestFrameSequence()
+ .ClientPreface()
+ .Headers(1,
+ {{":method", "POST"},
+ {":scheme", "https"},
+ {":authority", "ex|ample.com"},
+ {":path", "/this/is/request/one"}},
+ /*fin=*/false)
+ .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, 4));
+ EXPECT_CALL(visitor, OnBeginHeadersForStream(1));
+ EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "POST"));
+ EXPECT_CALL(visitor, OnHeaderForStream(1, ":scheme", "https"));
+ EXPECT_CALL(
+ visitor,
+ OnErrorDebug("Invalid HTTP header field was received: frame type: 1, "
+ "stream: 1, name: [:authority], value: [ex|ample.com]"));
+ EXPECT_CALL(
+ visitor,
+ OnInvalidFrame(1, Http2VisitorInterface::InvalidFrameError::kHttpHeader));
+
+ const int64_t result = adapter->ProcessBytes(frames);
+ EXPECT_EQ(frames.size(), result);
+
+ EXPECT_TRUE(adapter->want_write());
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0x0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 1, 4, 0x0));
+ EXPECT_CALL(visitor,
+ OnFrameSent(RST_STREAM, 1, 4, 0x0,
+ static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR)));
+ EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::PROTOCOL_ERROR));
+
+ int send_result = adapter->Send();
+ EXPECT_EQ(0, send_result);
+ EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS,
+ spdy::SpdyFrameType::RST_STREAM}));
+}
+
TEST(NgHttp2AdapterTest, ServerSubmitResponse) {
DataSavingVisitor visitor;
auto adapter = NgHttp2Adapter::CreateServerAdapter(visitor);
diff --git a/http2/adapter/oghttp2_adapter_test.cc b/http2/adapter/oghttp2_adapter_test.cc
index d785891..47638dd 100644
--- a/http2/adapter/oghttp2_adapter_test.cc
+++ b/http2/adapter/oghttp2_adapter_test.cc
@@ -3694,6 +3694,57 @@
spdy::SpdyFrameType::GOAWAY}));
}
+TEST(OgHttp2AdapterServerTest, ServerReceivesInvalidAuthority) {
+ 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"},
+ {":authority", "ex|ample.com"},
+ {":path", "/this/is/request/one"}},
+ /*fin=*/false)
+ .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, 4));
+ EXPECT_CALL(visitor, OnBeginHeadersForStream(1));
+ EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "POST"));
+ EXPECT_CALL(visitor, OnHeaderForStream(1, ":scheme", "https"));
+ EXPECT_CALL(
+ visitor,
+ OnInvalidFrame(1, Http2VisitorInterface::InvalidFrameError::kHttpHeader));
+
+ const int64_t result = adapter->ProcessBytes(frames);
+ EXPECT_EQ(static_cast<int64_t>(frames.size()), result);
+
+ EXPECT_TRUE(adapter->want_write());
+
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x0));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x0, 0x0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(SETTINGS, 0, _, 0x1));
+ EXPECT_CALL(visitor, OnFrameSent(SETTINGS, 0, _, 0x1, 0x0));
+ EXPECT_CALL(visitor, OnBeforeFrameSent(RST_STREAM, 1, 4, 0x0));
+ EXPECT_CALL(visitor,
+ OnFrameSent(RST_STREAM, 1, 4, 0x0,
+ static_cast<int>(Http2ErrorCode::PROTOCOL_ERROR)));
+ EXPECT_CALL(visitor, OnCloseStream(1, Http2ErrorCode::HTTP2_NO_ERROR));
+
+ int send_result = adapter->Send();
+ EXPECT_EQ(0, send_result);
+ EXPECT_THAT(visitor.data(), EqualsFrames({spdy::SpdyFrameType::SETTINGS,
+ spdy::SpdyFrameType::SETTINGS,
+ spdy::SpdyFrameType::RST_STREAM}));
+}
+
TEST(OgHttp2AdapterServerTest, ServerRejectsStreamData) {
DataSavingVisitor visitor;
OgHttp2Adapter::Options options{.perspective = Perspective::kServer};