| // Copyright 2014 The Chromium Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "net/third_party/quiche/src/spdy/core/hpack/hpack_encoder.h" | |
| #include <cstdint> | |
| #include <map> | |
| #include "net/third_party/quiche/src/http2/test_tools/http2_random.h" | |
| #include "net/third_party/quiche/src/spdy/core/hpack/hpack_huffman_table.h" | |
| #include "net/third_party/quiche/src/spdy/core/spdy_simple_arena.h" | |
| #include "net/third_party/quiche/src/spdy/platform/api/spdy_test.h" | |
| namespace spdy { | |
| namespace test { | |
| class HpackHeaderTablePeer { | |
| public: | |
| explicit HpackHeaderTablePeer(HpackHeaderTable* table) : table_(table) {} | |
| HpackHeaderTable::EntryTable* dynamic_entries() { | |
| return &table_->dynamic_entries_; | |
| } | |
| private: | |
| HpackHeaderTable* table_; | |
| }; | |
| class HpackEncoderPeer { | |
| public: | |
| typedef HpackEncoder::Representation Representation; | |
| typedef HpackEncoder::Representations Representations; | |
| explicit HpackEncoderPeer(HpackEncoder* encoder) : encoder_(encoder) {} | |
| bool compression_enabled() const { return encoder_->enable_compression_; } | |
| HpackHeaderTable* table() { return &encoder_->header_table_; } | |
| HpackHeaderTablePeer table_peer() { return HpackHeaderTablePeer(table()); } | |
| const HpackHuffmanTable& huffman_table() const { | |
| return encoder_->huffman_table_; | |
| } | |
| void EmitString(quiche::QuicheStringPiece str) { encoder_->EmitString(str); } | |
| void TakeString(std::string* out) { | |
| encoder_->output_stream_.TakeString(out); | |
| } | |
| static void CookieToCrumbs(quiche::QuicheStringPiece cookie, | |
| std::vector<quiche::QuicheStringPiece>* out) { | |
| Representations tmp; | |
| HpackEncoder::CookieToCrumbs(std::make_pair("", cookie), &tmp); | |
| out->clear(); | |
| for (size_t i = 0; i != tmp.size(); ++i) { | |
| out->push_back(tmp[i].second); | |
| } | |
| } | |
| static void DecomposeRepresentation( | |
| quiche::QuicheStringPiece value, | |
| std::vector<quiche::QuicheStringPiece>* out) { | |
| Representations tmp; | |
| HpackEncoder::DecomposeRepresentation(std::make_pair("foobar", value), | |
| &tmp); | |
| out->clear(); | |
| for (size_t i = 0; i != tmp.size(); ++i) { | |
| out->push_back(tmp[i].second); | |
| } | |
| } | |
| // TODO(dahollings): Remove or clean up these methods when deprecating | |
| // non-incremental encoding path. | |
| static bool EncodeHeaderSet(HpackEncoder* encoder, | |
| const SpdyHeaderBlock& header_set, | |
| std::string* output) { | |
| return encoder->EncodeHeaderSet(header_set, output); | |
| } | |
| static bool EncodeIncremental(HpackEncoder* encoder, | |
| const SpdyHeaderBlock& header_set, | |
| std::string* output) { | |
| std::unique_ptr<HpackEncoder::ProgressiveEncoder> encoderator = | |
| encoder->EncodeHeaderSet(header_set); | |
| std::string output_buffer; | |
| http2::test::Http2Random random; | |
| encoderator->Next(random.UniformInRange(0, 16), &output_buffer); | |
| while (encoderator->HasNext()) { | |
| std::string second_buffer; | |
| encoderator->Next(random.UniformInRange(0, 16), &second_buffer); | |
| output_buffer.append(second_buffer); | |
| } | |
| *output = std::move(output_buffer); | |
| return true; | |
| } | |
| static bool EncodeRepresentations(HpackEncoder* encoder, | |
| const Representations& representations, | |
| std::string* output) { | |
| std::unique_ptr<HpackEncoder::ProgressiveEncoder> encoderator = | |
| encoder->EncodeRepresentations(representations); | |
| std::string output_buffer; | |
| http2::test::Http2Random random; | |
| encoderator->Next(random.UniformInRange(0, 16), &output_buffer); | |
| while (encoderator->HasNext()) { | |
| std::string second_buffer; | |
| encoderator->Next(random.UniformInRange(0, 16), &second_buffer); | |
| output_buffer.append(second_buffer); | |
| } | |
| *output = std::move(output_buffer); | |
| return true; | |
| } | |
| private: | |
| HpackEncoder* encoder_; | |
| }; | |
| } // namespace test | |
| namespace { | |
| using testing::ElementsAre; | |
| using testing::Pair; | |
| enum EncodeStrategy { | |
| kDefault, | |
| kIncremental, | |
| kRepresentations, | |
| }; | |
| class HpackEncoderTestBase : public ::testing::Test { | |
| protected: | |
| typedef test::HpackEncoderPeer::Representations Representations; | |
| HpackEncoderTestBase() | |
| : encoder_(ObtainHpackHuffmanTable()), | |
| peer_(&encoder_), | |
| static_(peer_.table()->GetByIndex(1)), | |
| headers_storage_(1024 /* block size */) {} | |
| void SetUp() override { | |
| // Populate dynamic entries into the table fixture. For simplicity each | |
| // entry has name.size() + value.size() == 10. | |
| key_1_ = peer_.table()->TryAddEntry("key1", "value1"); | |
| key_2_ = peer_.table()->TryAddEntry("key2", "value2"); | |
| cookie_a_ = peer_.table()->TryAddEntry("cookie", "a=bb"); | |
| cookie_c_ = peer_.table()->TryAddEntry("cookie", "c=dd"); | |
| // No further insertions may occur without evictions. | |
| peer_.table()->SetMaxSize(peer_.table()->size()); | |
| } | |
| void SaveHeaders(quiche::QuicheStringPiece name, | |
| quiche::QuicheStringPiece value) { | |
| quiche::QuicheStringPiece n( | |
| headers_storage_.Memdup(name.data(), name.size()), name.size()); | |
| quiche::QuicheStringPiece v( | |
| headers_storage_.Memdup(value.data(), value.size()), value.size()); | |
| headers_observed_.push_back(std::make_pair(n, v)); | |
| } | |
| void ExpectIndex(size_t index) { | |
| expected_.AppendPrefix(kIndexedOpcode); | |
| expected_.AppendUint32(index); | |
| } | |
| void ExpectIndexedLiteral(const HpackEntry* key_entry, | |
| quiche::QuicheStringPiece value) { | |
| expected_.AppendPrefix(kLiteralIncrementalIndexOpcode); | |
| expected_.AppendUint32(IndexOf(key_entry)); | |
| ExpectString(&expected_, value); | |
| } | |
| void ExpectIndexedLiteral(quiche::QuicheStringPiece name, | |
| quiche::QuicheStringPiece value) { | |
| expected_.AppendPrefix(kLiteralIncrementalIndexOpcode); | |
| expected_.AppendUint32(0); | |
| ExpectString(&expected_, name); | |
| ExpectString(&expected_, value); | |
| } | |
| void ExpectNonIndexedLiteral(quiche::QuicheStringPiece name, | |
| quiche::QuicheStringPiece value) { | |
| expected_.AppendPrefix(kLiteralNoIndexOpcode); | |
| expected_.AppendUint32(0); | |
| ExpectString(&expected_, name); | |
| ExpectString(&expected_, value); | |
| } | |
| void ExpectString(HpackOutputStream* stream, quiche::QuicheStringPiece str) { | |
| const HpackHuffmanTable& huffman_table = peer_.huffman_table(); | |
| size_t encoded_size = peer_.compression_enabled() | |
| ? huffman_table.EncodedSize(str) | |
| : str.size(); | |
| if (encoded_size < str.size()) { | |
| expected_.AppendPrefix(kStringLiteralHuffmanEncoded); | |
| expected_.AppendUint32(encoded_size); | |
| huffman_table.EncodeString(str, stream); | |
| } else { | |
| expected_.AppendPrefix(kStringLiteralIdentityEncoded); | |
| expected_.AppendUint32(str.size()); | |
| expected_.AppendBytes(str); | |
| } | |
| } | |
| void ExpectHeaderTableSizeUpdate(uint32_t size) { | |
| expected_.AppendPrefix(kHeaderTableSizeUpdateOpcode); | |
| expected_.AppendUint32(size); | |
| } | |
| Representations MakeRepresentations(const SpdyHeaderBlock& header_set) { | |
| Representations r; | |
| for (const auto& header : header_set) { | |
| r.push_back(header); | |
| } | |
| return r; | |
| } | |
| void CompareWithExpectedEncoding(const SpdyHeaderBlock& header_set) { | |
| std::string expected_out, actual_out; | |
| expected_.TakeString(&expected_out); | |
| switch (strategy_) { | |
| case kDefault: | |
| EXPECT_TRUE(test::HpackEncoderPeer::EncodeHeaderSet( | |
| &encoder_, header_set, &actual_out)); | |
| break; | |
| case kIncremental: | |
| EXPECT_TRUE(test::HpackEncoderPeer::EncodeIncremental( | |
| &encoder_, header_set, &actual_out)); | |
| break; | |
| case kRepresentations: | |
| EXPECT_TRUE(test::HpackEncoderPeer::EncodeRepresentations( | |
| &encoder_, MakeRepresentations(header_set), &actual_out)); | |
| break; | |
| } | |
| EXPECT_EQ(expected_out, actual_out); | |
| } | |
| void CompareWithExpectedEncoding(const Representations& representations) { | |
| std::string expected_out, actual_out; | |
| expected_.TakeString(&expected_out); | |
| EXPECT_TRUE(test::HpackEncoderPeer::EncodeRepresentations( | |
| &encoder_, representations, &actual_out)); | |
| EXPECT_EQ(expected_out, actual_out); | |
| } | |
| size_t IndexOf(const HpackEntry* entry) { | |
| return peer_.table()->IndexOf(entry); | |
| } | |
| HpackEncoder encoder_; | |
| test::HpackEncoderPeer peer_; | |
| const HpackEntry* static_; | |
| const HpackEntry* key_1_; | |
| const HpackEntry* key_2_; | |
| const HpackEntry* cookie_a_; | |
| const HpackEntry* cookie_c_; | |
| SpdySimpleArena headers_storage_; | |
| std::vector<std::pair<quiche::QuicheStringPiece, quiche::QuicheStringPiece>> | |
| headers_observed_; | |
| HpackOutputStream expected_; | |
| EncodeStrategy strategy_ = kDefault; | |
| }; | |
| TEST_F(HpackEncoderTestBase, EncodeRepresentations) { | |
| encoder_.SetHeaderListener( | |
| [this](quiche::QuicheStringPiece name, quiche::QuicheStringPiece value) { | |
| this->SaveHeaders(name, value); | |
| }); | |
| const std::vector< | |
| std::pair<quiche::QuicheStringPiece, quiche::QuicheStringPiece>> | |
| header_list = {{"cookie", "val1; val2;val3"}, | |
| {":path", "/home"}, | |
| {"accept", "text/html, text/plain,application/xml"}, | |
| {"cookie", "val4"}, | |
| {"withnul", quiche::QuicheStringPiece("one\0two", 7)}}; | |
| ExpectNonIndexedLiteral(":path", "/home"); | |
| ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "val1"); | |
| ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "val2"); | |
| ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "val3"); | |
| ExpectIndexedLiteral(peer_.table()->GetByName("accept"), | |
| "text/html, text/plain,application/xml"); | |
| ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "val4"); | |
| ExpectIndexedLiteral("withnul", quiche::QuicheStringPiece("one\0two", 7)); | |
| CompareWithExpectedEncoding(header_list); | |
| EXPECT_THAT( | |
| headers_observed_, | |
| ElementsAre(Pair(":path", "/home"), Pair("cookie", "val1"), | |
| Pair("cookie", "val2"), Pair("cookie", "val3"), | |
| Pair("accept", "text/html, text/plain,application/xml"), | |
| Pair("cookie", "val4"), | |
| Pair("withnul", quiche::QuicheStringPiece("one\0two", 7)))); | |
| } | |
| class HpackEncoderTest : public HpackEncoderTestBase, | |
| public ::testing::WithParamInterface<EncodeStrategy> { | |
| protected: | |
| void SetUp() override { | |
| strategy_ = GetParam(); | |
| HpackEncoderTestBase::SetUp(); | |
| } | |
| }; | |
| INSTANTIATE_TEST_SUITE_P(HpackEncoderTests, | |
| HpackEncoderTest, | |
| ::testing::Values(kDefault, | |
| kIncremental, | |
| kRepresentations)); | |
| TEST_P(HpackEncoderTest, SingleDynamicIndex) { | |
| encoder_.SetHeaderListener( | |
| [this](quiche::QuicheStringPiece name, quiche::QuicheStringPiece value) { | |
| this->SaveHeaders(name, value); | |
| }); | |
| ExpectIndex(IndexOf(key_2_)); | |
| SpdyHeaderBlock headers; | |
| headers[key_2_->name()] = key_2_->value(); | |
| CompareWithExpectedEncoding(headers); | |
| EXPECT_THAT(headers_observed_, | |
| ElementsAre(Pair(key_2_->name(), key_2_->value()))); | |
| } | |
| TEST_P(HpackEncoderTest, SingleStaticIndex) { | |
| ExpectIndex(IndexOf(static_)); | |
| SpdyHeaderBlock headers; | |
| headers[static_->name()] = static_->value(); | |
| CompareWithExpectedEncoding(headers); | |
| } | |
| TEST_P(HpackEncoderTest, SingleStaticIndexTooLarge) { | |
| peer_.table()->SetMaxSize(1); // Also evicts all fixtures. | |
| ExpectIndex(IndexOf(static_)); | |
| SpdyHeaderBlock headers; | |
| headers[static_->name()] = static_->value(); | |
| CompareWithExpectedEncoding(headers); | |
| EXPECT_EQ(0u, peer_.table_peer().dynamic_entries()->size()); | |
| } | |
| TEST_P(HpackEncoderTest, SingleLiteralWithIndexName) { | |
| ExpectIndexedLiteral(key_2_, "value3"); | |
| SpdyHeaderBlock headers; | |
| headers[key_2_->name()] = "value3"; | |
| CompareWithExpectedEncoding(headers); | |
| // A new entry was inserted and added to the reference set. | |
| HpackEntry* new_entry = &peer_.table_peer().dynamic_entries()->front(); | |
| EXPECT_EQ(new_entry->name(), key_2_->name()); | |
| EXPECT_EQ(new_entry->value(), "value3"); | |
| } | |
| TEST_P(HpackEncoderTest, SingleLiteralWithLiteralName) { | |
| ExpectIndexedLiteral("key3", "value3"); | |
| SpdyHeaderBlock headers; | |
| headers["key3"] = "value3"; | |
| CompareWithExpectedEncoding(headers); | |
| HpackEntry* new_entry = &peer_.table_peer().dynamic_entries()->front(); | |
| EXPECT_EQ(new_entry->name(), "key3"); | |
| EXPECT_EQ(new_entry->value(), "value3"); | |
| } | |
| TEST_P(HpackEncoderTest, SingleLiteralTooLarge) { | |
| peer_.table()->SetMaxSize(1); // Also evicts all fixtures. | |
| ExpectIndexedLiteral("key3", "value3"); | |
| // A header overflowing the header table is still emitted. | |
| // The header table is empty. | |
| SpdyHeaderBlock headers; | |
| headers["key3"] = "value3"; | |
| CompareWithExpectedEncoding(headers); | |
| EXPECT_EQ(0u, peer_.table_peer().dynamic_entries()->size()); | |
| } | |
| TEST_P(HpackEncoderTest, EmitThanEvict) { | |
| // |key_1_| is toggled and placed into the reference set, | |
| // and then immediately evicted by "key3". | |
| ExpectIndex(IndexOf(key_1_)); | |
| ExpectIndexedLiteral("key3", "value3"); | |
| SpdyHeaderBlock headers; | |
| headers[key_1_->name()] = key_1_->value(); | |
| headers["key3"] = "value3"; | |
| CompareWithExpectedEncoding(headers); | |
| } | |
| TEST_P(HpackEncoderTest, CookieHeaderIsCrumbled) { | |
| ExpectIndex(IndexOf(cookie_a_)); | |
| ExpectIndex(IndexOf(cookie_c_)); | |
| ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "e=ff"); | |
| SpdyHeaderBlock headers; | |
| headers["cookie"] = "a=bb; c=dd; e=ff"; | |
| CompareWithExpectedEncoding(headers); | |
| } | |
| TEST_P(HpackEncoderTest, MultiValuedHeadersNotCrumbled) { | |
| ExpectIndexedLiteral("foo", "bar, baz"); | |
| SpdyHeaderBlock headers; | |
| headers["foo"] = "bar, baz"; | |
| CompareWithExpectedEncoding(headers); | |
| } | |
| TEST_P(HpackEncoderTest, StringsDynamicallySelectHuffmanCoding) { | |
| // Compactable string. Uses Huffman coding. | |
| peer_.EmitString("feedbeef"); | |
| expected_.AppendPrefix(kStringLiteralHuffmanEncoded); | |
| expected_.AppendUint32(6); | |
| expected_.AppendBytes("\x94\xA5\x92\x32\x96_"); | |
| // Non-compactable. Uses identity coding. | |
| peer_.EmitString("@@@@@@"); | |
| expected_.AppendPrefix(kStringLiteralIdentityEncoded); | |
| expected_.AppendUint32(6); | |
| expected_.AppendBytes("@@@@@@"); | |
| std::string expected_out, actual_out; | |
| expected_.TakeString(&expected_out); | |
| peer_.TakeString(&actual_out); | |
| EXPECT_EQ(expected_out, actual_out); | |
| } | |
| TEST_P(HpackEncoderTest, EncodingWithoutCompression) { | |
| encoder_.SetHeaderListener( | |
| [this](quiche::QuicheStringPiece name, quiche::QuicheStringPiece value) { | |
| this->SaveHeaders(name, value); | |
| }); | |
| encoder_.DisableCompression(); | |
| ExpectNonIndexedLiteral(":path", "/index.html"); | |
| ExpectNonIndexedLiteral("cookie", "foo=bar"); | |
| ExpectNonIndexedLiteral("cookie", "baz=bing"); | |
| if (strategy_ == kRepresentations) { | |
| ExpectNonIndexedLiteral("hello", std::string("goodbye\0aloha", 13)); | |
| } else { | |
| ExpectNonIndexedLiteral("hello", "goodbye"); | |
| ExpectNonIndexedLiteral("hello", "aloha"); | |
| } | |
| ExpectNonIndexedLiteral("multivalue", "value1, value2"); | |
| SpdyHeaderBlock headers; | |
| headers[":path"] = "/index.html"; | |
| headers["cookie"] = "foo=bar; baz=bing"; | |
| headers["hello"] = "goodbye"; | |
| headers.AppendValueOrAddHeader("hello", "aloha"); | |
| headers["multivalue"] = "value1, value2"; | |
| CompareWithExpectedEncoding(headers); | |
| if (strategy_ == kRepresentations) { | |
| EXPECT_THAT( | |
| headers_observed_, | |
| ElementsAre( | |
| Pair(":path", "/index.html"), Pair("cookie", "foo=bar"), | |
| Pair("cookie", "baz=bing"), | |
| Pair("hello", quiche::QuicheStringPiece("goodbye\0aloha", 13)), | |
| Pair("multivalue", "value1, value2"))); | |
| } else { | |
| EXPECT_THAT( | |
| headers_observed_, | |
| ElementsAre(Pair(":path", "/index.html"), Pair("cookie", "foo=bar"), | |
| Pair("cookie", "baz=bing"), Pair("hello", "goodbye"), | |
| Pair("hello", "aloha"), | |
| Pair("multivalue", "value1, value2"))); | |
| } | |
| } | |
| TEST_P(HpackEncoderTest, MultipleEncodingPasses) { | |
| encoder_.SetHeaderListener( | |
| [this](quiche::QuicheStringPiece name, quiche::QuicheStringPiece value) { | |
| this->SaveHeaders(name, value); | |
| }); | |
| // Pass 1. | |
| { | |
| SpdyHeaderBlock headers; | |
| headers["key1"] = "value1"; | |
| headers["cookie"] = "a=bb"; | |
| ExpectIndex(IndexOf(key_1_)); | |
| ExpectIndex(IndexOf(cookie_a_)); | |
| CompareWithExpectedEncoding(headers); | |
| } | |
| // Header table is: | |
| // 65: key1: value1 | |
| // 64: key2: value2 | |
| // 63: cookie: a=bb | |
| // 62: cookie: c=dd | |
| // Pass 2. | |
| { | |
| SpdyHeaderBlock headers; | |
| headers["key2"] = "value2"; | |
| headers["cookie"] = "c=dd; e=ff"; | |
| // "key2: value2" | |
| ExpectIndex(64); | |
| // "cookie: c=dd" | |
| ExpectIndex(62); | |
| // This cookie evicts |key1| from the dynamic table. | |
| ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "e=ff"); | |
| CompareWithExpectedEncoding(headers); | |
| } | |
| // Header table is: | |
| // 65: key2: value2 | |
| // 64: cookie: a=bb | |
| // 63: cookie: c=dd | |
| // 62: cookie: e=ff | |
| // Pass 3. | |
| { | |
| SpdyHeaderBlock headers; | |
| headers["key2"] = "value2"; | |
| headers["cookie"] = "a=bb; b=cc; c=dd"; | |
| // "key2: value2" | |
| ExpectIndex(65); | |
| // "cookie: a=bb" | |
| ExpectIndex(64); | |
| // This cookie evicts |key2| from the dynamic table. | |
| ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "b=cc"); | |
| // "cookie: c=dd" | |
| ExpectIndex(64); | |
| CompareWithExpectedEncoding(headers); | |
| } | |
| // clang-format off | |
| EXPECT_THAT(headers_observed_, | |
| ElementsAre(Pair("key1", "value1"), | |
| Pair("cookie", "a=bb"), | |
| Pair("key2", "value2"), | |
| Pair("cookie", "c=dd"), | |
| Pair("cookie", "e=ff"), | |
| Pair("key2", "value2"), | |
| Pair("cookie", "a=bb"), | |
| Pair("cookie", "b=cc"), | |
| Pair("cookie", "c=dd"))); | |
| // clang-format on | |
| } | |
| TEST_P(HpackEncoderTest, PseudoHeadersFirst) { | |
| SpdyHeaderBlock headers; | |
| // A pseudo-header that should not be indexed. | |
| headers[":path"] = "/spam/eggs.html"; | |
| // A pseudo-header to be indexed. | |
| headers[":authority"] = "www.example.com"; | |
| // A regular header which precedes ":" alphabetically, should still be encoded | |
| // after pseudo-headers. | |
| headers["-foo"] = "bar"; | |
| headers["foo"] = "bar"; | |
| headers["cookie"] = "c=dd"; | |
| // Headers are indexed in the order in which they were added. | |
| // This entry pushes "cookie: a=bb" back to 63. | |
| ExpectNonIndexedLiteral(":path", "/spam/eggs.html"); | |
| ExpectIndexedLiteral(peer_.table()->GetByName(":authority"), | |
| "www.example.com"); | |
| ExpectIndexedLiteral("-foo", "bar"); | |
| ExpectIndexedLiteral("foo", "bar"); | |
| ExpectIndexedLiteral(peer_.table()->GetByName("cookie"), "c=dd"); | |
| CompareWithExpectedEncoding(headers); | |
| } | |
| TEST_P(HpackEncoderTest, CookieToCrumbs) { | |
| test::HpackEncoderPeer peer(nullptr); | |
| std::vector<quiche::QuicheStringPiece> out; | |
| // Leading and trailing whitespace is consumed. A space after ';' is consumed. | |
| // All other spaces remain. ';' at beginning and end of string produce empty | |
| // crumbs. | |
| // See section 8.1.3.4 "Compressing the Cookie Header Field" in the HTTP/2 | |
| // specification at http://tools.ietf.org/html/draft-ietf-httpbis-http2-11 | |
| peer.CookieToCrumbs(" foo=1;bar=2 ; bar=3; bing=4; ", &out); | |
| EXPECT_THAT(out, ElementsAre("foo=1", "bar=2 ", "bar=3", " bing=4", "")); | |
| peer.CookieToCrumbs(";;foo = bar ;; ;baz =bing", &out); | |
| EXPECT_THAT(out, ElementsAre("", "", "foo = bar ", "", "", "baz =bing")); | |
| peer.CookieToCrumbs("baz=bing; foo=bar; baz=bing", &out); | |
| EXPECT_THAT(out, ElementsAre("baz=bing", "foo=bar", "baz=bing")); | |
| peer.CookieToCrumbs("baz=bing", &out); | |
| EXPECT_THAT(out, ElementsAre("baz=bing")); | |
| peer.CookieToCrumbs("", &out); | |
| EXPECT_THAT(out, ElementsAre("")); | |
| peer.CookieToCrumbs("foo;bar; baz;baz;bing;", &out); | |
| EXPECT_THAT(out, ElementsAre("foo", "bar", "baz", "baz", "bing", "")); | |
| peer.CookieToCrumbs(" \t foo=1;bar=2 ; bar=3;\t ", &out); | |
| EXPECT_THAT(out, ElementsAre("foo=1", "bar=2 ", "bar=3", "")); | |
| peer.CookieToCrumbs(" \t foo=1;bar=2 ; bar=3 \t ", &out); | |
| EXPECT_THAT(out, ElementsAre("foo=1", "bar=2 ", "bar=3")); | |
| } | |
| TEST_P(HpackEncoderTest, DecomposeRepresentation) { | |
| test::HpackEncoderPeer peer(nullptr); | |
| std::vector<quiche::QuicheStringPiece> out; | |
| peer.DecomposeRepresentation("", &out); | |
| EXPECT_THAT(out, ElementsAre("")); | |
| peer.DecomposeRepresentation("foobar", &out); | |
| EXPECT_THAT(out, ElementsAre("foobar")); | |
| peer.DecomposeRepresentation(quiche::QuicheStringPiece("foo\0bar", 7), &out); | |
| EXPECT_THAT(out, ElementsAre("foo", "bar")); | |
| peer.DecomposeRepresentation(quiche::QuicheStringPiece("\0foo\0bar", 8), | |
| &out); | |
| EXPECT_THAT(out, ElementsAre("", "foo", "bar")); | |
| peer.DecomposeRepresentation(quiche::QuicheStringPiece("foo\0bar\0", 8), | |
| &out); | |
| EXPECT_THAT(out, ElementsAre("foo", "bar", "")); | |
| peer.DecomposeRepresentation(quiche::QuicheStringPiece("\0foo\0bar\0", 9), | |
| &out); | |
| EXPECT_THAT(out, ElementsAre("", "foo", "bar", "")); | |
| } | |
| // Test that encoded headers do not have \0-delimited multiple values, as this | |
| // became disallowed in HTTP/2 draft-14. | |
| TEST_P(HpackEncoderTest, CrumbleNullByteDelimitedValue) { | |
| if (strategy_ == kRepresentations) { | |
| // When HpackEncoder is asked to encode a list of Representations, the | |
| // caller must crumble null-delimited values. | |
| return; | |
| } | |
| SpdyHeaderBlock headers; | |
| // A header field to be crumbled: "spam: foo\0bar". | |
| headers["spam"] = std::string("foo\0bar", 7); | |
| ExpectIndexedLiteral("spam", "foo"); | |
| expected_.AppendPrefix(kLiteralIncrementalIndexOpcode); | |
| expected_.AppendUint32(62); | |
| expected_.AppendPrefix(kStringLiteralIdentityEncoded); | |
| expected_.AppendUint32(3); | |
| expected_.AppendBytes("bar"); | |
| CompareWithExpectedEncoding(headers); | |
| } | |
| TEST_P(HpackEncoderTest, HeaderTableSizeUpdate) { | |
| encoder_.ApplyHeaderTableSizeSetting(1024); | |
| ExpectHeaderTableSizeUpdate(1024); | |
| ExpectIndexedLiteral("key3", "value3"); | |
| SpdyHeaderBlock headers; | |
| headers["key3"] = "value3"; | |
| CompareWithExpectedEncoding(headers); | |
| HpackEntry* new_entry = &peer_.table_peer().dynamic_entries()->front(); | |
| EXPECT_EQ(new_entry->name(), "key3"); | |
| EXPECT_EQ(new_entry->value(), "value3"); | |
| } | |
| TEST_P(HpackEncoderTest, HeaderTableSizeUpdateWithMin) { | |
| const size_t starting_size = peer_.table()->settings_size_bound(); | |
| encoder_.ApplyHeaderTableSizeSetting(starting_size - 2); | |
| encoder_.ApplyHeaderTableSizeSetting(starting_size - 1); | |
| // We must encode the low watermark, so the peer knows to evict entries | |
| // if necessary. | |
| ExpectHeaderTableSizeUpdate(starting_size - 2); | |
| ExpectHeaderTableSizeUpdate(starting_size - 1); | |
| ExpectIndexedLiteral("key3", "value3"); | |
| SpdyHeaderBlock headers; | |
| headers["key3"] = "value3"; | |
| CompareWithExpectedEncoding(headers); | |
| HpackEntry* new_entry = &peer_.table_peer().dynamic_entries()->front(); | |
| EXPECT_EQ(new_entry->name(), "key3"); | |
| EXPECT_EQ(new_entry->value(), "value3"); | |
| } | |
| TEST_P(HpackEncoderTest, HeaderTableSizeUpdateWithExistingSize) { | |
| encoder_.ApplyHeaderTableSizeSetting(peer_.table()->settings_size_bound()); | |
| // No encoded size update. | |
| ExpectIndexedLiteral("key3", "value3"); | |
| SpdyHeaderBlock headers; | |
| headers["key3"] = "value3"; | |
| CompareWithExpectedEncoding(headers); | |
| HpackEntry* new_entry = &peer_.table_peer().dynamic_entries()->front(); | |
| EXPECT_EQ(new_entry->name(), "key3"); | |
| EXPECT_EQ(new_entry->value(), "value3"); | |
| } | |
| TEST_P(HpackEncoderTest, HeaderTableSizeUpdatesWithGreaterSize) { | |
| const size_t starting_size = peer_.table()->settings_size_bound(); | |
| encoder_.ApplyHeaderTableSizeSetting(starting_size + 1); | |
| encoder_.ApplyHeaderTableSizeSetting(starting_size + 2); | |
| // Only a single size update to the final size. | |
| ExpectHeaderTableSizeUpdate(starting_size + 2); | |
| ExpectIndexedLiteral("key3", "value3"); | |
| SpdyHeaderBlock headers; | |
| headers["key3"] = "value3"; | |
| CompareWithExpectedEncoding(headers); | |
| HpackEntry* new_entry = &peer_.table_peer().dynamic_entries()->front(); | |
| EXPECT_EQ(new_entry->name(), "key3"); | |
| EXPECT_EQ(new_entry->value(), "value3"); | |
| } | |
| } // namespace | |
| } // namespace spdy |