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};