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