Adds HTTP/2 header validation logic, encapsulated in the HeaderValidator class.

PiperOrigin-RevId: 401516908
diff --git a/http2/adapter/header_validator.cc b/http2/adapter/header_validator.cc
new file mode 100644
index 0000000..832c629
--- /dev/null
+++ b/http2/adapter/header_validator.cc
@@ -0,0 +1,101 @@
+#include "http2/adapter/header_validator.h"
+
+#include "absl/strings/escaping.h"
+#include "common/platform/api/quiche_logging.h"
+
+namespace http2 {
+namespace adapter {
+
+namespace {
+
+const absl::string_view kHttp2HeaderNameAllowedChars =
+    "!#$%&\'*+-.0123456789"
+    "^_`abcdefghijklmnopqrstuvwxyz|~";
+
+const absl::string_view kHttp2HeaderValueAllowedChars =
+    "\t "
+    "!\"#$%&'()*+,-./"
+    "0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`"
+    "abcdefghijklmnopqrstuvwxyz{|}~";
+
+const absl::string_view kHttp2StatusValueAllowedChars = "0123456789";
+
+// TODO(birenroy): Support websocket requests, which contain an extra
+// `:protocol` pseudo-header.
+bool ValidateRequestHeaders(const std::vector<std::string>& pseudo_headers) {
+  static const std::vector<std::string> kRequiredHeaders = []() {
+    return std::vector<std::string>(
+        {":authority", ":method", ":path", ":scheme"});
+  }();
+  return pseudo_headers == kRequiredHeaders;
+}
+
+bool ValidateResponseHeaders(const std::vector<std::string>& pseudo_headers) {
+  static const std::vector<std::string> kRequiredHeaders = []() {
+    return std::vector<std::string>({":status"});
+  }();
+  return pseudo_headers == kRequiredHeaders;
+}
+
+bool ValidateResponseTrailers(const std::vector<std::string>& pseudo_headers) {
+  return pseudo_headers.empty();
+}
+
+}  // namespace
+
+void HeaderValidator::StartHeaderBlock() {
+  pseudo_headers_.clear();
+  status_.clear();
+}
+
+HeaderValidator::HeaderStatus HeaderValidator::ValidateSingleHeader(
+    absl::string_view key, absl::string_view value) {
+  if (key.empty()) {
+    return HEADER_NAME_EMPTY;
+  }
+  const absl::string_view validated_key = key[0] == ':' ? key.substr(1) : key;
+  if (validated_key.find_first_not_of(kHttp2HeaderNameAllowedChars) !=
+      absl::string_view::npos) {
+    QUICHE_VLOG(2) << "invalid chars in header name: ["
+                   << absl::CEscape(validated_key) << "]";
+    return HEADER_NAME_INVALID_CHAR;
+  }
+  if (value.find_first_not_of(kHttp2HeaderValueAllowedChars) !=
+      absl::string_view::npos) {
+    QUICHE_VLOG(2) << "invalid chars in header value: [" << absl::CEscape(value)
+                   << "]";
+    return HEADER_VALUE_INVALID_CHAR;
+  }
+  if (key[0] == ':') {
+    if (key == ":status") {
+      if (value.size() != 3 ||
+          value.find_first_not_of(kHttp2StatusValueAllowedChars) !=
+              absl::string_view::npos) {
+        QUICHE_VLOG(2) << "malformed status value: [" << absl::CEscape(value)
+                       << "]";
+        return HEADER_VALUE_INVALID_CHAR;
+      }
+      status_ = std::string(value);
+    }
+    pseudo_headers_.push_back(std::string(key));
+  }
+  return HEADER_OK;
+}
+
+// Returns true if all required pseudoheaders and no extra pseudoheaders are
+// present for the given header type.
+bool HeaderValidator::FinishHeaderBlock(HeaderType type) {
+  std::sort(pseudo_headers_.begin(), pseudo_headers_.end());
+  switch (type) {
+    case HeaderType::REQUEST:
+      return ValidateRequestHeaders(pseudo_headers_);
+    case HeaderType::RESPONSE_100:
+    case HeaderType::RESPONSE:
+      return ValidateResponseHeaders(pseudo_headers_);
+    case HeaderType::RESPONSE_TRAILER:
+      return ValidateResponseTrailers(pseudo_headers_);
+  }
+}
+
+}  // namespace adapter
+}  // namespace http2
diff --git a/http2/adapter/header_validator.h b/http2/adapter/header_validator.h
new file mode 100644
index 0000000..2d7585e
--- /dev/null
+++ b/http2/adapter/header_validator.h
@@ -0,0 +1,49 @@
+#ifndef QUICHE_HTTP2_ADAPTER_HEADER_VALIDATOR_H_
+#define QUICHE_HTTP2_ADAPTER_HEADER_VALIDATOR_H_
+
+#include <string>
+#include <vector>
+
+#include "absl/strings/string_view.h"
+#include "common/platform/api/quiche_export.h"
+
+namespace http2 {
+namespace adapter {
+
+enum class HeaderType : uint8_t {
+  REQUEST,
+  RESPONSE_100,
+  RESPONSE,
+  RESPONSE_TRAILER,
+};
+
+class QUICHE_EXPORT_PRIVATE HeaderValidator {
+ public:
+  HeaderValidator() {}
+
+  void StartHeaderBlock();
+
+  enum HeaderStatus {
+    HEADER_OK,
+    HEADER_NAME_EMPTY,
+    HEADER_NAME_INVALID_CHAR,
+    HEADER_VALUE_INVALID_CHAR,
+  };
+  HeaderStatus ValidateSingleHeader(absl::string_view key,
+                                    absl::string_view value);
+
+  // Returns true if all required pseudoheaders and no extra pseudoheaders are
+  // present for the given header type.
+  bool FinishHeaderBlock(HeaderType type);
+
+  absl::string_view status_header() const { return status_; }
+
+ private:
+  std::vector<std::string> pseudo_headers_;
+  std::string status_;
+};
+
+}  // namespace adapter
+}  // namespace http2
+
+#endif  // QUICHE_HTTP2_ADAPTER_HEADER_VALIDATOR_H_
diff --git a/http2/adapter/header_validator_test.cc b/http2/adapter/header_validator_test.cc
new file mode 100644
index 0000000..a2a4cd5
--- /dev/null
+++ b/http2/adapter/header_validator_test.cc
@@ -0,0 +1,212 @@
+#include "http2/adapter/header_validator.h"
+
+#include "common/platform/api/quiche_test.h"
+
+namespace http2 {
+namespace adapter {
+namespace test {
+
+TEST(HeaderValidatorTest, HeaderNameEmpty) {
+  HeaderValidator v;
+  HeaderValidator::HeaderStatus status = v.ValidateSingleHeader("", "value");
+  EXPECT_EQ(HeaderValidator::HEADER_NAME_EMPTY, status);
+}
+
+TEST(HeaderValidatorTest, HeaderValueEmpty) {
+  HeaderValidator v;
+  HeaderValidator::HeaderStatus status = v.ValidateSingleHeader("name", "");
+  EXPECT_EQ(HeaderValidator::HEADER_OK, status);
+}
+
+TEST(HeaderValidatorTest, NameHasInvalidChar) {
+  HeaderValidator v;
+  for (const bool is_pseudo_header : {true, false}) {
+    // These characters should be allowed. (Not exhaustive.)
+    for (const char* c : {"!", "3", "a", "_", "|", "~"}) {
+      const std::string name = is_pseudo_header ? absl::StrCat(":met", c, "hod")
+                                                : absl::StrCat("na", c, "me");
+      HeaderValidator::HeaderStatus status =
+          v.ValidateSingleHeader(name, "value");
+      EXPECT_EQ(HeaderValidator::HEADER_OK, status);
+    }
+    // These should not. (Not exhaustive.)
+    for (const char* c : {"\\", "<", ";", "[", "=", " ", "\r", "\n", ",", "\"",
+                          "\x1F", "\x91"}) {
+      const std::string name = is_pseudo_header ? absl::StrCat(":met", c, "hod")
+                                                : absl::StrCat("na", c, "me");
+      HeaderValidator::HeaderStatus status =
+          v.ValidateSingleHeader(name, "value");
+      EXPECT_EQ(HeaderValidator::HEADER_NAME_INVALID_CHAR, status);
+    }
+    // Uppercase characters in header names should not be allowed.
+    const std::string uc_name = is_pseudo_header ? ":Method" : "Name";
+    HeaderValidator::HeaderStatus status =
+        v.ValidateSingleHeader(uc_name, "value");
+    EXPECT_EQ(HeaderValidator::HEADER_NAME_INVALID_CHAR, status);
+  }
+}
+
+TEST(HeaderValidatorTest, ValueHasInvalidChar) {
+  HeaderValidator v;
+  // These characters should be allowed. (Not exhaustive.)
+  for (const char* c :
+       {"!", "3", "a", "_", "|", "~", "\\", "<", ";", "[", "=", "A", "\t"}) {
+    HeaderValidator::HeaderStatus status =
+        v.ValidateSingleHeader("name", absl::StrCat("val", c, "ue"));
+    EXPECT_EQ(HeaderValidator::HEADER_OK, status);
+  }
+  // These should not.
+  for (const char* c : {"\r", "\n"}) {
+    HeaderValidator::HeaderStatus status =
+        v.ValidateSingleHeader("name", absl::StrCat("val", c, "ue"));
+    EXPECT_EQ(HeaderValidator::HEADER_VALUE_INVALID_CHAR, status);
+  }
+}
+
+TEST(HeaderValidatorTest, StatusHasInvalidChar) {
+  HeaderValidator v;
+
+  for (HeaderType type : {HeaderType::RESPONSE, HeaderType::RESPONSE_100}) {
+    // When `:status` has a non-digit value, validation will fail.
+    v.StartHeaderBlock();
+    EXPECT_EQ(HeaderValidator::HEADER_VALUE_INVALID_CHAR,
+              v.ValidateSingleHeader(":status", "bar"));
+    EXPECT_FALSE(v.FinishHeaderBlock(type));
+
+    // When `:status` is too short, validation will fail.
+    v.StartHeaderBlock();
+    EXPECT_EQ(HeaderValidator::HEADER_VALUE_INVALID_CHAR,
+              v.ValidateSingleHeader(":status", "10"));
+    EXPECT_FALSE(v.FinishHeaderBlock(type));
+
+    // When `:status` is too long, validation will fail.
+    v.StartHeaderBlock();
+    EXPECT_EQ(HeaderValidator::HEADER_VALUE_INVALID_CHAR,
+              v.ValidateSingleHeader(":status", "9000"));
+    EXPECT_FALSE(v.FinishHeaderBlock(type));
+
+    // When `:status` is just right, validation will succeed.
+    v.StartHeaderBlock();
+    EXPECT_EQ(HeaderValidator::HEADER_OK,
+              v.ValidateSingleHeader(":status", "400"));
+    EXPECT_TRUE(v.FinishHeaderBlock(type));
+  }
+}
+
+TEST(HeaderValidatorTest, RequestPseudoHeaders) {
+  HeaderValidator v;
+  const absl::string_view headers[] = {":authority", ":method", ":path",
+                                       ":scheme"};
+  for (absl::string_view to_skip : headers) {
+    v.StartHeaderBlock();
+    for (absl::string_view to_add : headers) {
+      if (to_add != to_skip) {
+        EXPECT_EQ(HeaderValidator::HEADER_OK,
+                  v.ValidateSingleHeader(to_add, "foo"));
+      }
+    }
+    // When any pseudo-header is missing, final validation will fail.
+    EXPECT_FALSE(v.FinishHeaderBlock(HeaderType::REQUEST));
+  }
+
+  // When all pseudo-headers are present, final validation will succeed.
+  v.StartHeaderBlock();
+  for (absl::string_view to_add : headers) {
+    EXPECT_EQ(HeaderValidator::HEADER_OK,
+              v.ValidateSingleHeader(to_add, "foo"));
+  }
+  EXPECT_TRUE(v.FinishHeaderBlock(HeaderType::REQUEST));
+
+  // When an extra pseudo-header is present, final validation will fail.
+  v.StartHeaderBlock();
+  for (absl::string_view to_add : headers) {
+    EXPECT_EQ(HeaderValidator::HEADER_OK,
+              v.ValidateSingleHeader(to_add, "foo"));
+  }
+  EXPECT_EQ(HeaderValidator::HEADER_OK,
+            v.ValidateSingleHeader(":extra", "blah"));
+  EXPECT_FALSE(v.FinishHeaderBlock(HeaderType::REQUEST));
+
+  // When a required pseudo-header is repeated, final validation will fail.
+  for (absl::string_view to_repeat : headers) {
+    v.StartHeaderBlock();
+    for (absl::string_view to_add : headers) {
+      EXPECT_EQ(HeaderValidator::HEADER_OK,
+                v.ValidateSingleHeader(to_add, "foo"));
+      if (to_add == to_repeat) {
+        EXPECT_EQ(HeaderValidator::HEADER_OK,
+                  v.ValidateSingleHeader(to_add, "foo"));
+      }
+    }
+    EXPECT_FALSE(v.FinishHeaderBlock(HeaderType::REQUEST));
+  }
+}
+
+TEST(HeaderValidatorTest, WebsocketPseudoHeaders) {
+  HeaderValidator v;
+  const absl::string_view headers[] = {":authority", ":method", ":path",
+                                       ":scheme"};
+  v.StartHeaderBlock();
+  for (absl::string_view to_add : headers) {
+    EXPECT_EQ(HeaderValidator::HEADER_OK,
+              v.ValidateSingleHeader(to_add, "foo"));
+  }
+  EXPECT_EQ(HeaderValidator::HEADER_OK,
+            v.ValidateSingleHeader(":protocol", "websocket"));
+  // For now, `:protocol` is treated as an extra pseudo-header.
+  EXPECT_FALSE(v.FinishHeaderBlock(HeaderType::REQUEST));
+}
+
+TEST(HeaderValidatorTest, ResponsePseudoHeaders) {
+  HeaderValidator v;
+
+  for (HeaderType type : {HeaderType::RESPONSE, HeaderType::RESPONSE_100}) {
+    // When `:status` is missing, validation will fail.
+    v.StartHeaderBlock();
+    EXPECT_EQ(HeaderValidator::HEADER_OK, v.ValidateSingleHeader("foo", "bar"));
+    EXPECT_FALSE(v.FinishHeaderBlock(type));
+
+    // When all pseudo-headers are present, final validation will succeed.
+    v.StartHeaderBlock();
+    EXPECT_EQ(HeaderValidator::HEADER_OK,
+              v.ValidateSingleHeader(":status", "199"));
+    EXPECT_TRUE(v.FinishHeaderBlock(type));
+    EXPECT_EQ("199", v.status_header());
+
+    // When `:status` is repeated, validation will fail.
+    v.StartHeaderBlock();
+    EXPECT_EQ(HeaderValidator::HEADER_OK,
+              v.ValidateSingleHeader(":status", "199"));
+    EXPECT_EQ(HeaderValidator::HEADER_OK,
+              v.ValidateSingleHeader(":status", "299"));
+    EXPECT_FALSE(v.FinishHeaderBlock(type));
+
+    // When an extra pseudo-header is present, final validation will fail.
+    v.StartHeaderBlock();
+    EXPECT_EQ(HeaderValidator::HEADER_OK,
+              v.ValidateSingleHeader(":status", "199"));
+    EXPECT_EQ(HeaderValidator::HEADER_OK,
+              v.ValidateSingleHeader(":extra", "blorp"));
+    EXPECT_FALSE(v.FinishHeaderBlock(type));
+  }
+}
+
+TEST(HeaderValidatorTest, ResponseTrailerPseudoHeaders) {
+  HeaderValidator v;
+
+  // When no pseudo-headers are present, validation will succeed.
+  v.StartHeaderBlock();
+  EXPECT_EQ(HeaderValidator::HEADER_OK, v.ValidateSingleHeader("foo", "bar"));
+  EXPECT_TRUE(v.FinishHeaderBlock(HeaderType::RESPONSE_TRAILER));
+
+  // When any pseudo-header is present, final validation will fail.
+  v.StartHeaderBlock();
+  EXPECT_EQ(HeaderValidator::HEADER_OK,
+            v.ValidateSingleHeader(":status", "200"));
+  EXPECT_EQ(HeaderValidator::HEADER_OK, v.ValidateSingleHeader("foo", "bar"));
+  EXPECT_FALSE(v.FinishHeaderBlock(HeaderType::RESPONSE_TRAILER));
+}
+
+}  // namespace test
+}  // namespace adapter
+}  // namespace http2
diff --git a/http2/adapter/oghttp2_session.cc b/http2/adapter/oghttp2_session.cc
index 450a56d..e3a696b 100644
--- a/http2/adapter/oghttp2_session.cc
+++ b/http2/adapter/oghttp2_session.cc
@@ -131,24 +131,39 @@
 }  // namespace
 
 void OgHttp2Session::PassthroughHeadersHandler::OnHeaderBlockStart() {
+  result_ = Http2VisitorInterface::HEADER_OK;
   const bool status = visitor_.OnBeginHeadersForStream(stream_id_);
   if (!status) {
     result_ = Http2VisitorInterface::HEADER_CONNECTION_ERROR;
   }
+  validator_.StartHeaderBlock();
 }
 
 void OgHttp2Session::PassthroughHeadersHandler::OnHeader(
     absl::string_view key,
     absl::string_view value) {
-  if (result_ == Http2VisitorInterface::HEADER_OK) {
-    result_ = visitor_.OnHeaderForStream(stream_id_, key, value);
+  if (result_ != Http2VisitorInterface::HEADER_OK) {
+    QUICHE_VLOG(2) << "Early return; status not HEADER_OK";
+    return;
   }
+  const auto validation_result = validator_.ValidateSingleHeader(key, value);
+  if (validation_result != HeaderValidator::HEADER_OK) {
+    QUICHE_VLOG(2) << "RST_STREAM: invalid header found";
+    result_ = Http2VisitorInterface::HEADER_RST_STREAM;
+    return;
+  }
+  result_ = visitor_.OnHeaderForStream(stream_id_, key, value);
 }
 
 void OgHttp2Session::PassthroughHeadersHandler::OnHeaderBlockEnd(
     size_t /* uncompressed_header_bytes */,
     size_t /* compressed_header_bytes */) {
   if (result_ == Http2VisitorInterface::HEADER_OK) {
+    if (!validator_.FinishHeaderBlock(type_)) {
+      result_ = Http2VisitorInterface::HEADER_RST_STREAM;
+    }
+  }
+  if (result_ == Http2VisitorInterface::HEADER_OK) {
     const bool result = visitor_.OnEndHeadersForStream(stream_id_);
     if (!result) {
       session_.decoder_.StopProcessing();
@@ -670,10 +685,27 @@
 spdy::SpdyHeadersHandlerInterface* OgHttp2Session::OnHeaderFrameStart(
     spdy::SpdyStreamId stream_id) {
   headers_handler_.set_stream_id(stream_id);
+  auto it = stream_map_.find(stream_id);
+  if (it != stream_map_.end()) {
+    headers_handler_.set_header_type(
+        NextHeaderType(it->second.received_header_type));
+  }
   return &headers_handler_;
 }
 
-void OgHttp2Session::OnHeaderFrameEnd(spdy::SpdyStreamId /*stream_id*/) {
+void OgHttp2Session::OnHeaderFrameEnd(spdy::SpdyStreamId stream_id) {
+  auto it = stream_map_.find(stream_id);
+  if (it != stream_map_.end()) {
+    if (headers_handler_.header_type() == HeaderType::RESPONSE &&
+        !headers_handler_.status_header().empty() &&
+        headers_handler_.status_header()[0] == '1') {
+      // If response headers carried a 1xx response code, final response headers
+      // should still be forthcoming.
+      it->second.received_header_type = HeaderType::RESPONSE_100;
+    } else {
+      it->second.received_header_type = headers_handler_.header_type();
+    }
+  }
   headers_handler_.set_stream_id(0);
 }
 
@@ -959,6 +991,18 @@
   return stream_map_.size() < max_outbound_concurrent_streams_;
 }
 
+HeaderType OgHttp2Session::NextHeaderType(
+    absl::optional<HeaderType> current_type) {
+  if (IsServerSession()) {
+    return HeaderType::REQUEST;
+  } else if (!current_type ||
+             current_type.value() == HeaderType::RESPONSE_100) {
+    return HeaderType::RESPONSE;
+  } else {
+    return HeaderType::RESPONSE_TRAILER;
+  }
+}
+
 void OgHttp2Session::LatchErrorAndNotify() {
   latched_error_ = true;
   visitor_.OnConnectionError();
diff --git a/http2/adapter/oghttp2_session.h b/http2/adapter/oghttp2_session.h
index 558b36c..75f9c96 100644
--- a/http2/adapter/oghttp2_session.h
+++ b/http2/adapter/oghttp2_session.h
@@ -5,6 +5,7 @@
 #include <list>
 
 #include "http2/adapter/data_source.h"
+#include "http2/adapter/header_validator.h"
 #include "http2/adapter/http2_protocol.h"
 #include "http2/adapter/http2_session.h"
 #include "http2/adapter/http2_util.h"
@@ -181,6 +182,7 @@
     std::unique_ptr<spdy::SpdyHeaderBlock> trailers;
     void* user_data = nullptr;
     int32_t send_window = kInitialFlowControlWindowSize;
+    absl::optional<HeaderType> received_header_type;
     bool half_closed_local = false;
     bool half_closed_remote = false;
   };
@@ -199,14 +201,24 @@
     explicit PassthroughHeadersHandler(OgHttp2Session& session,
                                        Http2VisitorInterface& visitor)
         : session_(session), visitor_(visitor) {}
+
     void set_stream_id(Http2StreamId stream_id) {
       stream_id_ = stream_id;
       result_ = Http2VisitorInterface::HEADER_OK;
     }
+
+    void set_header_type(HeaderType type) { type_ = type; }
+    HeaderType header_type() const { return type_; }
+
     void OnHeaderBlockStart() override;
     void OnHeader(absl::string_view key, absl::string_view value) override;
     void OnHeaderBlockEnd(size_t /* uncompressed_header_bytes */,
                           size_t /* compressed_header_bytes */) override;
+    absl::string_view status_header() {
+      QUICHE_DCHECK(type_ == HeaderType::RESPONSE ||
+                    type_ == HeaderType::RESPONSE_100);
+      return validator_.status_header();
+    }
 
    private:
     OgHttp2Session& session_;
@@ -214,6 +226,9 @@
     Http2StreamId stream_id_ = 0;
     Http2VisitorInterface::OnHeaderResult result_ =
         Http2VisitorInterface::HEADER_OK;
+    // Validates header blocks according to the HTTP/2 specification.
+    HeaderValidator validator_;
+    HeaderType type_ = HeaderType::RESPONSE;
   };
 
   // Queues the connection preface, if not already done.
@@ -256,6 +271,9 @@
   // Closes the given `stream_id` with the given `error_code`.
   void CloseStream(Http2StreamId stream_id, Http2ErrorCode error_code);
 
+  // Calculates the next expected header type for a stream in a given state.
+  HeaderType NextHeaderType(absl::optional<HeaderType> current_type);
+
   // Returns true if the session can create a new stream.
   bool CanCreateStream() const;