Validates request paths according to RFC 3986 Section 3.3.

PiperOrigin-RevId: 535379163
diff --git a/quiche/http2/adapter/header_validator.cc b/quiche/http2/adapter/header_validator.cc
index e474558..2119b64 100644
--- a/quiche/http2/adapter/header_validator.cc
+++ b/quiche/http2/adapter/header_validator.cc
@@ -28,6 +28,10 @@
     "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~%!$&'()["
     "]*+,;=:";
 
+const absl::string_view kValidPathChars =
+    "/abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~%!$&'()"
+    "*+,;=:@?";
+
 using CharMap = std::array<bool, 256>;
 
 CharMap BuildValidCharMap(absl::string_view valid_chars) {
@@ -177,8 +181,7 @@
     } else if (key == ":authority" && !ValidateAndSetAuthority(value)) {
       return HEADER_FIELD_INVALID;
     } else if (key == ":path") {
-      if (value.empty()) {
-        // For now, reject an empty path regardless of scheme.
+      if (value.empty() || !IsValidPath(value)) {
         return HEADER_FIELD_INVALID;
       }
       path_ = std::string(value);
@@ -252,6 +255,11 @@
   return AllCharsInMap(authority, valid_chars);
 }
 
+bool HeaderValidator::IsValidPath(absl::string_view path) {
+  static const CharMap valid_chars = BuildValidCharMap(kValidPathChars);
+  return AllCharsInMap(path, valid_chars);
+}
+
 HeaderValidator::ContentLengthStatus HeaderValidator::HandleContentLength(
     absl::string_view value) {
   if (value.empty()) {
diff --git a/quiche/http2/adapter/header_validator.h b/quiche/http2/adapter/header_validator.h
index a6070db..c6eb3a4 100644
--- a/quiche/http2/adapter/header_validator.h
+++ b/quiche/http2/adapter/header_validator.h
@@ -33,6 +33,10 @@
   // Returns whether `authority` is valid according to RFC 3986 Section 3.2.
   static bool IsValidAuthority(absl::string_view authority);
 
+  // Returns whether `path` is valid according to RFC 3986 Section 3.3. May
+  // contain the query part of a URI.
+  static bool IsValidPath(absl::string_view path);
+
  private:
   enum ContentLengthStatus {
     CONTENT_LENGTH_OK,
diff --git a/quiche/http2/adapter/header_validator_test.cc b/quiche/http2/adapter/header_validator_test.cc
index 6aaf974..3727003 100644
--- a/quiche/http2/adapter/header_validator_test.cc
+++ b/quiche/http2/adapter/header_validator_test.cc
@@ -457,7 +457,8 @@
     for (Header to_add : kSampleRequestPseudoheaders) {
       if (to_add.first == ":path") {
         EXPECT_EQ(HeaderValidator::HEADER_OK,
-                  validator.ValidateSingleHeader(to_add.first, value));
+                  validator.ValidateSingleHeader(to_add.first, value))
+            << "Problematic char: [" << c << "]";
       } else {
         EXPECT_EQ(HeaderValidator::HEADER_OK,
                   validator.ValidateSingleHeader(to_add.first, to_add.second));
@@ -466,7 +467,7 @@
     EXPECT_TRUE(validator.FinishHeaderBlock(HeaderType::REQUEST));
   }
 
-  // BUG: Various invalid path characters.
+  // Various invalid path characters.
   for (const absl::string_view c : {"[", "<", "}", "`", "\\", " ", "\t"}) {
     const std::string value = absl::StrCat("/shawa", c, "rma");
 
@@ -474,14 +475,14 @@
     validator.StartHeaderBlock();
     for (Header to_add : kSampleRequestPseudoheaders) {
       if (to_add.first == ":path") {
-        EXPECT_EQ(HeaderValidator::HEADER_OK,
+        EXPECT_EQ(HeaderValidator::HEADER_FIELD_INVALID,
                   validator.ValidateSingleHeader(to_add.first, value));
       } else {
         EXPECT_EQ(HeaderValidator::HEADER_OK,
                   validator.ValidateSingleHeader(to_add.first, to_add.second));
       }
     }
-    EXPECT_TRUE(validator.FinishHeaderBlock(HeaderType::REQUEST));
+    EXPECT_FALSE(validator.FinishHeaderBlock(HeaderType::REQUEST));
   }
 }
 
diff --git a/quiche/http2/adapter/oghttp2_adapter_test.cc b/quiche/http2/adapter/oghttp2_adapter_test.cc
index 9b3c193..c6f4d47 100644
--- a/quiche/http2/adapter/oghttp2_adapter_test.cc
+++ b/quiche/http2/adapter/oghttp2_adapter_test.cc
@@ -171,18 +171,19 @@
   EXPECT_CALL(visitor, OnHeaderForStream(1, ":method", "GET"));
   EXPECT_CALL(visitor, OnHeaderForStream(1, ":scheme", "https"));
   EXPECT_CALL(visitor, OnHeaderForStream(1, ":authority", "example.com"));
-  EXPECT_CALL(visitor, OnHeaderForStream(1, ":path", "/ fragment"));
-  EXPECT_CALL(visitor, OnEndHeadersForStream(1));
-  EXPECT_CALL(visitor, OnEndStream(1));
+  EXPECT_CALL(
+      visitor,
+      OnInvalidFrame(1, Http2VisitorInterface::InvalidFrameError::kHttpHeader));
+
   // Stream 3
   EXPECT_CALL(visitor, OnFrameHeader(3, _, HEADERS, 5));
   EXPECT_CALL(visitor, OnBeginHeadersForStream(3));
   EXPECT_CALL(visitor, OnHeaderForStream(3, ":method", "GET"));
   EXPECT_CALL(visitor, OnHeaderForStream(3, ":scheme", "https"));
   EXPECT_CALL(visitor, OnHeaderForStream(3, ":authority", "example.com"));
-  EXPECT_CALL(visitor, OnHeaderForStream(3, ":path", "/\tfragment2"));
-  EXPECT_CALL(visitor, OnEndHeadersForStream(3));
-  EXPECT_CALL(visitor, OnEndStream(3));
+  EXPECT_CALL(
+      visitor,
+      OnInvalidFrame(3, Http2VisitorInterface::InvalidFrameError::kHttpHeader));
 
   const int64_t result = adapter->ProcessBytes(frames);
   EXPECT_EQ(frames.size(), static_cast<size_t>(result));