Add helper functions to validate subprotocol names without serializing them.

PiperOrigin-RevId: 758302644
diff --git a/quiche/web_transport/web_transport_headers.cc b/quiche/web_transport/web_transport_headers.cc
index fc46536..2ae7bf1 100644
--- a/quiche/web_transport/web_transport_headers.cc
+++ b/quiche/web_transport/web_transport_headers.cc
@@ -12,6 +12,7 @@
 #include <vector>
 
 #include "absl/base/attributes.h"
+#include "absl/container/flat_hash_set.h"
 #include "absl/status/status.h"
 #include "absl/status/statusor.h"
 #include "absl/strings/str_cat.h"
@@ -106,6 +107,31 @@
   return std::string(subprotocol);
 }
 
+bool ValidateSubprotocolName(absl::string_view name) {
+  return !name.empty() && quiche::structured_headers::IsValidToken(name);
+}
+
+template <typename S>
+bool ValidateSubprotocolListBase(absl::Span<const S> list) {
+  absl::flat_hash_set<absl::string_view> examined;
+  for (const absl::string_view name : list) {
+    if (!ValidateSubprotocolName(name)) {
+      return false;
+    }
+    auto [it, added] = examined.insert(name);
+    if (!added) {
+      return false;
+    }
+  }
+  return true;
+}
+bool ValidateSubprotocolList(absl::Span<const absl::string_view> list) {
+  return ValidateSubprotocolListBase(list);
+}
+bool ValidateSubprotocolList(absl::Span<const std::string> list) {
+  return ValidateSubprotocolListBase(list);
+}
+
 absl::StatusOr<WebTransportInitHeader> ParseInitHeader(
     absl::string_view header) {
   std::optional<Dictionary> parsed =
diff --git a/quiche/web_transport/web_transport_headers.h b/quiche/web_transport/web_transport_headers.h
index 9ed1674..897741e 100644
--- a/quiche/web_transport/web_transport_headers.h
+++ b/quiche/web_transport/web_transport_headers.h
@@ -28,6 +28,10 @@
     absl::string_view value);
 QUICHE_EXPORT absl::StatusOr<std::string> SerializeSubprotocolResponseHeader(
     absl::string_view subprotocol);
+QUICHE_EXPORT bool ValidateSubprotocolName(absl::string_view name);
+QUICHE_EXPORT bool ValidateSubprotocolList(
+    absl::Span<const absl::string_view> list);
+QUICHE_EXPORT bool ValidateSubprotocolList(absl::Span<const std::string> list);
 
 inline constexpr absl::string_view kInitHeader = "WebTransport-Init";
 
diff --git a/quiche/web_transport/web_transport_headers_test.cc b/quiche/web_transport/web_transport_headers_test.cc
index c41dc8d..d85e1f8 100644
--- a/quiche/web_transport/web_transport_headers_test.cc
+++ b/quiche/web_transport/web_transport_headers_test.cc
@@ -4,7 +4,12 @@
 
 #include "quiche/web_transport/web_transport_headers.h"
 
+#include <initializer_list>
+#include <string>
+#include <vector>
+
 #include "absl/status/status.h"
+#include "absl/strings/string_view.h"
 #include "quiche/common/platform/api/quiche_test.h"
 #include "quiche/common/test_tools/quiche_test_utils.h"
 
@@ -119,5 +124,28 @@
               IsOkAndHolds("u=100, bl=200, br=400"));
 }
 
+// Helper to sidestep the fact that calling ValidateSubprotocolList directly
+// with an initializer list is ambiguous.
+bool ValidateSubprotocolListHelper(
+    std::initializer_list<absl::string_view> list) {
+  return ValidateSubprotocolList(list);
+}
+
+TEST(WebTransportHeaders, ValidateSubprotocolName) {
+  EXPECT_TRUE(ValidateSubprotocolName("test"));
+  EXPECT_FALSE(ValidateSubprotocolName("123"));
+  EXPECT_FALSE(ValidateSubprotocolName(""));
+
+  EXPECT_TRUE(ValidateSubprotocolListHelper({}));
+  EXPECT_TRUE(ValidateSubprotocolListHelper({"a", "b", "c"}));
+  EXPECT_FALSE(ValidateSubprotocolListHelper({"a", "1", "c"}));
+  EXPECT_FALSE(ValidateSubprotocolListHelper({"a", "b", "a"}));
+
+  std::vector<std::string> vec = {"a", "b"};
+  EXPECT_TRUE(ValidateSubprotocolList(vec));
+  vec.push_back("b");
+  EXPECT_FALSE(ValidateSubprotocolList(vec));
+}
+
 }  // namespace
 }  // namespace webtransport