No public description

PiperOrigin-RevId: 866544937
diff --git a/quiche/balsa/header_properties.cc b/quiche/balsa/header_properties.cc
index 03ebe56..57ffbc1 100644
--- a/quiche/balsa/header_properties.cc
+++ b/quiche/balsa/header_properties.cc
@@ -146,6 +146,26 @@
   return validTokenCharTable;
 }
 
+constexpr std::array<bool, 256> buildValidChunkExtensionValCharLookupTable() {
+  std::array<bool, 256> validChunkExtValCharTable = kAllTrueArray;
+  constexpr uint8_t US_ASCII_CONTROL_START = 0;
+  constexpr uint8_t US_ASCII_CONTROL_END = 32;  // exclusive
+  // Valid chunk-ext-val characters are the entire range of visible US-ASCII
+  // characters and all extended ASCII characters. Of the control characters,
+  // only HTAB is allowed. There are some more precise rules for when DQUOTE
+  // and backslash can appear in a quoted-string which we skip here.
+  for (uint8_t c = US_ASCII_CONTROL_START; c < US_ASCII_CONTROL_END; ++c) {
+    if (c == '\t') {
+      continue;
+    }
+    validChunkExtValCharTable[c] = false;
+  }
+  // Also exclude the control character DEL.
+  validChunkExtValCharTable[127] = false;
+
+  return validChunkExtValCharTable;
+}
+
 }  // anonymous namespace
 
 bool IsMultivaluedHeader(absl::string_view header) {
@@ -192,6 +212,23 @@
   }
   return true;
 }
+
+bool IsValidChunkExtension(absl::string_view value) {
+  for (const char c : value) {
+    if (!IsValidChunkExtensionValChar(static_cast<uint8_t>(c))) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool IsValidChunkExtensionValChar(uint8_t c) {
+  static constexpr std::array<bool, 256> validChunkExtValCharTable =
+      buildValidChunkExtensionValCharLookupTable();
+
+  return validChunkExtValCharTable[c];
+}
+
 bool HasInvalidHeaderChars(absl::string_view value) {
   for (const char c : value) {
     if (IsInvalidHeaderChar(c)) {
diff --git a/quiche/balsa/header_properties.h b/quiche/balsa/header_properties.h
index a540e79..6d72b1b 100644
--- a/quiche/balsa/header_properties.h
+++ b/quiche/balsa/header_properties.h
@@ -82,6 +82,12 @@
 QUICHE_EXPORT bool IsValidTokenChar(uint8_t c);
 QUICHE_EXPORT bool IsValidToken(absl::string_view value);
 
+// Returns true if the given `char` is in the set of ASCII characters valid for
+// `chunk-ext-val` specified in RFC 9112 Section 7.1.1. Note that Section 7.1.1
+// defines specific ordering of certain characters, which is not checked here.
+QUICHE_EXPORT bool IsValidChunkExtensionValChar(uint8_t c);
+QUICHE_EXPORT bool IsValidChunkExtension(absl::string_view value);
+
 // Returns true if `value` contains a character not allowed in the path
 // component of a URI.
 QUICHE_EXPORT bool HasInvalidPathChar(absl::string_view value);
diff --git a/quiche/balsa/header_properties_test.cc b/quiche/balsa/header_properties_test.cc
index ad983f1..f4e749a 100644
--- a/quiche/balsa/header_properties_test.cc
+++ b/quiche/balsa/header_properties_test.cc
@@ -3,6 +3,7 @@
 #include <string>
 
 #include "absl/container/flat_hash_set.h"
+#include "absl/strings/ascii.h"
 #include "absl/strings/string_view.h"
 #include "quiche/common/platform/api/quiche_test.h"
 
@@ -185,6 +186,7 @@
   EXPECT_FALSE(IsValidToken("GET@"));
   EXPECT_FALSE(IsValidToken("GET["));
   EXPECT_FALSE(IsValidToken("GET\\"));
+  EXPECT_FALSE(IsValidToken("GET\""));
   EXPECT_FALSE(IsValidToken("GET]"));
   EXPECT_FALSE(IsValidToken("GET:"));
   EXPECT_FALSE(IsValidToken("GET;"));
@@ -199,5 +201,57 @@
   EXPECT_FALSE(IsValidToken(""));
 }
 
+TEST(HeaderPropertiesTest, IsValidChunkExtensionValChar) {
+  EXPECT_TRUE(IsValidChunkExtension(""));
+  EXPECT_TRUE(IsValidChunkExtension(";"));
+  EXPECT_TRUE(IsValidChunkExtension(";a"));
+  EXPECT_TRUE(IsValidChunkExtension("; a"));
+  EXPECT_TRUE(IsValidChunkExtension("\""));
+  EXPECT_TRUE(IsValidChunkExtension(";\t a"));
+  EXPECT_TRUE(IsValidChunkExtension(";a="));
+  EXPECT_TRUE(IsValidChunkExtension(";a=b"));
+  EXPECT_TRUE(IsValidChunkExtension(";a=\"\""));
+  EXPECT_TRUE(IsValidChunkExtension(";a=\"ba\"z\""));
+  EXPECT_TRUE(IsValidChunkExtension(";a=foo-quote'"));
+  EXPECT_TRUE(IsValidChunkExtension(";foo=bar;baz=qux"));
+  EXPECT_TRUE(IsValidChunkExtension(";foo=bar;baz=\"qu\";x"));
+  EXPECT_TRUE(IsValidChunkExtension(";foo=bar;baz=\"q \tu\";x"));
+  EXPECT_TRUE(IsValidChunkExtension("!#$%&'*+-.^_`|~"));
+  EXPECT_TRUE(IsValidChunkExtension("abcefghijklmnopqrstuvwxyz0123456789"));
+  EXPECT_TRUE(
+      IsValidChunkExtension("ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+                            "abcdefghijklmnopqrstuvwxyz"
+                            "0123456789"
+                            "!#$%&'*+-.^_`|~"));
+  EXPECT_TRUE(IsValidChunkExtension("GET\xFF"));
+  EXPECT_TRUE(IsValidChunkExtension("GET\x80"));
+
+  EXPECT_FALSE(
+      IsValidChunkExtension(absl::string_view(";chunky-nulls=\0", 15)));
+  EXPECT_FALSE(IsValidChunkExtension(";\x1"));
+  EXPECT_FALSE(IsValidChunkExtension(";\n"));
+  EXPECT_FALSE(IsValidChunkExtension(";\r"));
+}
+
+TEST(HeaderPropertiesTest, IsValidChunkExtension) {
+  for (int i = 0; i < 256; ++i) {
+    unsigned char c = static_cast<unsigned char>(i);
+    if (c == ' ' || c == '\t') {
+      continue;
+    }
+    if (absl::ascii_iscntrl(c)) {
+      EXPECT_FALSE(IsValidChunkExtensionValChar(c));
+    } else if (absl::ascii_isprint(c)) {
+      EXPECT_TRUE(IsValidChunkExtensionValChar(c));
+    } else if (absl::ascii_isspace(c)) {
+      EXPECT_TRUE(IsValidChunkExtensionValChar(c));
+    } else if (i >= 128) {
+      EXPECT_TRUE(IsValidChunkExtensionValChar(c));
+    } else {
+      FAIL() << "Unexpected character: [" << c << "], int = [" << i << "]";
+    }
+  }
+}
+
 }  // namespace
 }  // namespace quiche::header_properties::test