Split cookies along ';' instead of '\0' in ValueSplittingHeaderList.

This is described at https://httpwg.org/specs/rfc7540.html#CompressCookie.

gfe-relnote: n/a.  Change in QPACK code used in QUIC v99 only.
PiperOrigin-RevId: 252041422
Change-Id: Id4e99993d8c25cccdedd334488fd6b5cae3058d0
diff --git a/quic/core/qpack/fuzzer/qpack_round_trip_fuzzer.cc b/quic/core/qpack/fuzzer/qpack_round_trip_fuzzer.cc
index 5b7f8d3..5f752ea 100644
--- a/quic/core/qpack/fuzzer/qpack_round_trip_fuzzer.cc
+++ b/quic/core/qpack/fuzzer/qpack_round_trip_fuzzer.cc
@@ -143,8 +143,8 @@
   CHECK(handler.decoding_completed());
   CHECK(!handler.decoding_error_detected());
 
-  // Encoder splits |header_list| header keys along '\0' characters.  Do the
-  // same so that we get matching results.
+  // Encoder splits |header_list| header values along '\0' or ';' separators.
+  // Do the same here so that we get matching results.
   ValueSplittingHeaderList splitting_header_list(&header_list);
   spdy::SpdyHeaderBlock expected_header_list;
   for (const auto& header : splitting_header_list) {
diff --git a/quic/core/qpack/value_splitting_header_list.cc b/quic/core/qpack/value_splitting_header_list.cc
index d8a0ee8..c1387b9 100644
--- a/quic/core/qpack/value_splitting_header_list.cc
+++ b/quic/core/qpack/value_splitting_header_list.cc
@@ -5,6 +5,14 @@
 #include "net/third_party/quiche/src/quic/core/qpack/value_splitting_header_list.h"
 
 namespace quic {
+namespace {
+
+const char kCookieKey[] = "cookie";
+const char kCookieSeparator = ';';
+const char kOptionalSpaceAfterCookieSeparator = ' ';
+const char kNonCookieSeparator = '\0';
+
+}  // namespace
 
 ValueSplittingHeaderList::const_iterator::const_iterator(
     const spdy::SpdyHeaderBlock* header_list,
@@ -59,15 +67,24 @@
   }
 
   const QuicStringPiece name = header_list_iterator_->first;
+  const QuicStringPiece original_value = header_list_iterator_->second;
 
-  value_end_ = header_list_iterator_->second.find('\0', value_start_);
-  const QuicStringPiece::size_type value_length =
-      value_end_ == QuicStringPiece::npos ? QuicStringPiece::npos
-                                          : value_end_ - value_start_;
+  if (name == kCookieKey) {
+    value_end_ = original_value.find(kCookieSeparator, value_start_);
+  } else {
+    value_end_ = original_value.find(kNonCookieSeparator, value_start_);
+  }
+
   const QuicStringPiece value =
-      header_list_iterator_->second.substr(value_start_, value_length);
-
+      original_value.substr(value_start_, value_end_ - value_start_);
   header_field_ = std::make_pair(name, value);
+
+  // Skip character after ';' separator if it is a space.
+  if (name == kCookieKey && value_end_ != QuicStringPiece::npos &&
+      value_end_ + 1 < original_value.size() &&
+      original_value[value_end_ + 1] == kOptionalSpaceAfterCookieSeparator) {
+    ++value_end_;
+  }
 }
 
 ValueSplittingHeaderList::ValueSplittingHeaderList(
diff --git a/quic/core/qpack/value_splitting_header_list.h b/quic/core/qpack/value_splitting_header_list.h
index 8d994ae..fee3043 100644
--- a/quic/core/qpack/value_splitting_header_list.h
+++ b/quic/core/qpack/value_splitting_header_list.h
@@ -11,8 +11,9 @@
 
 namespace quic {
 
-// A wrapper class around SpdyHeaderBlock that splits header values along '\0'
-// characters.
+// A wrapper class around SpdyHeaderBlock that splits header values along ';'
+// separators (while also removing optional space following separator) for
+// cookies and along '\0' separators for other header fields.
 class QUIC_EXPORT_PRIVATE ValueSplittingHeaderList {
  public:
   using value_type = spdy::SpdyHeaderBlock::value_type;
@@ -34,7 +35,7 @@
     const value_type* operator->() const;
 
    private:
-    // Find next '\0' character; update |value_end_| and |header_field_|.
+    // Find next separator; update |value_end_| and |header_field_|.
     void UpdateHeaderField();
 
     const spdy::SpdyHeaderBlock* const header_list_;
diff --git a/quic/core/qpack/value_splitting_header_list_test.cc b/quic/core/qpack/value_splitting_header_list_test.cc
index 9ece65d..03a5eb0 100644
--- a/quic/core/qpack/value_splitting_header_list_test.cc
+++ b/quic/core/qpack/value_splitting_header_list_test.cc
@@ -4,6 +4,7 @@
 
 #include "net/third_party/quiche/src/quic/core/qpack/value_splitting_header_list.h"
 
+#include "net/third_party/quiche/src/quic/platform/api/quic_arraysize.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
 
 namespace quic {
@@ -17,10 +18,11 @@
   spdy::SpdyHeaderBlock block;
   block["foo"] = QuicStringPiece("bar\0baz", 7);
   block["baz"] = "qux";
+  block["cookie"] = "foo; bar";
 
   ValueSplittingHeaderList headers(&block);
   ValueSplittingHeaderList::const_iterator it1 = headers.begin();
-  const int kEnd = 4;
+  const int kEnd = 6;
   for (int i = 0; i < kEnd; ++i) {
     // Compare to begin().
     if (i == 0) {
@@ -73,47 +75,75 @@
   EXPECT_EQ(headers.begin(), headers.end());
 }
 
-TEST(ValueSplittingHeaderListTest, Simple) {
-  spdy::SpdyHeaderBlock block;
-  block["foo"] = "bar";
-  block["baz"] = "qux";
+struct {
+  const char* name;
+  QuicStringPiece value;
+  std::vector<const char*> expected_values;
+} kTestData[]{
+    // Empty value.
+    {"foo", "", {""}},
+    // Trivial case.
+    {"foo", "bar", {"bar"}},
+    // Simple split.
+    {"foo", {"bar\0baz", 7}, {"bar", "baz"}},
+    {"cookie", "foo;bar", {"foo", "bar"}},
+    {"cookie", "foo; bar", {"foo", "bar"}},
+    // Empty fragments with \0 separator.
+    {"foo", {"\0", 1}, {"", ""}},
+    {"bar", {"foo\0", 4}, {"foo", ""}},
+    {"baz", {"\0bar", 4}, {"", "bar"}},
+    {"qux", {"\0foobar\0", 8}, {"", "foobar", ""}},
+    // Empty fragments with ";" separator.
+    {"cookie", ";", {"", ""}},
+    {"cookie", "foo;", {"foo", ""}},
+    {"cookie", ";bar", {"", "bar"}},
+    {"cookie", ";foobar;", {"", "foobar", ""}},
+    // Empty fragments with "; " separator.
+    {"cookie", "; ", {"", ""}},
+    {"cookie", "foo; ", {"foo", ""}},
+    {"cookie", "; bar", {"", "bar"}},
+    {"cookie", "; foobar; ", {"", "foobar", ""}},
+};
 
-  ValueSplittingHeaderList headers(&block);
-  EXPECT_THAT(headers, ElementsAre(Pair("foo", "bar"), Pair("baz", "qux")));
-  EXPECT_NE(headers.begin(), headers.end());
+TEST(ValueSplittingHeaderListTest, Split) {
+  for (size_t i = 0; i < QUIC_ARRAYSIZE(kTestData); ++i) {
+    spdy::SpdyHeaderBlock block;
+    block[kTestData[i].name] = kTestData[i].value;
+
+    ValueSplittingHeaderList headers(&block);
+    auto it = headers.begin();
+    for (const char* expected_value : kTestData[i].expected_values) {
+      ASSERT_NE(it, headers.end());
+      EXPECT_EQ(it->first, kTestData[i].name);
+      EXPECT_EQ(it->second, expected_value);
+      ++it;
+    }
+    EXPECT_EQ(it, headers.end());
+  }
 }
 
-TEST(ValueSplittingHeaderListTest, EmptyValue) {
+TEST(ValueSplittingHeaderListTest, MultipleFields) {
   spdy::SpdyHeaderBlock block;
-  block["foo"] = "";
-
-  ValueSplittingHeaderList headers(&block);
-  EXPECT_THAT(headers, ElementsAre(Pair("foo", "")));
-}
-
-TEST(ValueSplittingHeaderListTest, SimpleSplit) {
-  spdy::SpdyHeaderBlock block;
-  block["foo"] = QuicStringPiece("bar\0baz", 7);
-  block["baz"] = QuicStringPiece("foo\0foo", 7);
+  block["foo"] = QuicStringPiece("bar\0baz\0", 8);
+  block["cookie"] = "foo; bar";
+  block["bar"] = QuicStringPiece("qux\0foo", 7);
 
   ValueSplittingHeaderList headers(&block);
   EXPECT_THAT(headers, ElementsAre(Pair("foo", "bar"), Pair("foo", "baz"),
-                                   Pair("baz", "foo"), Pair("baz", "foo")));
+                                   Pair("foo", ""), Pair("cookie", "foo"),
+                                   Pair("cookie", "bar"), Pair("bar", "qux"),
+                                   Pair("bar", "foo")));
 }
 
-TEST(ValueSplittingHeaderListTest, EmptyFragments) {
+TEST(ValueSplittingHeaderListTest, CookieStartsWithSpace) {
   spdy::SpdyHeaderBlock block;
-  block["foo"] = QuicStringPiece("\0", 1);
-  block["bar"] = QuicStringPiece("foo\0", 4);
-  block["baz"] = QuicStringPiece("\0bar", 4);
-  block["qux"] = QuicStringPiece("\0foobar\0", 8);
+  block["foo"] = "bar";
+  block["cookie"] = " foo";
+  block["bar"] = "baz";
 
   ValueSplittingHeaderList headers(&block);
-  EXPECT_THAT(
-      headers,
-      ElementsAre(Pair("foo", ""), Pair("foo", ""), Pair("bar", "foo"),
-                  Pair("bar", ""), Pair("baz", ""), Pair("baz", "bar"),
-                  Pair("qux", ""), Pair("qux", "foobar"), Pair("qux", "")));
+  EXPECT_THAT(headers, ElementsAre(Pair("foo", "bar"), Pair("cookie", " foo"),
+                                   Pair("bar", "baz")));
 }
 
 }  // namespace