1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "net/spdy/hpack_decoder.h"
10 #include "base/basictypes.h"
11 #include "base/logging.h"
12 #include "base/strings/string_piece.h"
13 #include "net/spdy/hpack_encoder.h"
14 #include "net/spdy/hpack_input_stream.h"
15 #include "net/spdy/hpack_output_stream.h"
16 #include "testing/gmock/include/gmock/gmock.h"
17 #include "testing/gtest/include/gtest/gtest.h"
23 using base::StringPiece;
26 class HpackDecoderPeer {
28 explicit HpackDecoderPeer(HpackDecoder* decoder)
29 : decoder_(decoder) {}
31 void HandleHeaderRepresentation(StringPiece name, StringPiece value) {
32 decoder_->HandleHeaderRepresentation(name, value);
34 bool DecodeNextName(HpackInputStream* in, StringPiece* out) {
35 return decoder_->DecodeNextName(in, out);
37 const HpackHeaderTable& header_table() {
38 return decoder_->header_table_;
40 void set_cookie_name(string name) {
41 decoder_->cookie_name_ = name;
43 string cookie_name() {
44 return decoder_->cookie_name_;
46 void set_cookie_value(string value) {
47 decoder_->cookie_value_ = value;
49 string cookie_value() {
50 return decoder_->cookie_value_;
52 const std::map<string, string>& decoded_block() const {
53 return decoder_->decoded_block_;
55 const string& headers_block_buffer() const {
56 return decoder_->headers_block_buffer_;
60 HpackDecoder* decoder_;
67 using base::StringPiece;
70 using testing::ElementsAre;
73 const size_t kLiteralBound = 1024;
75 class HpackDecoderTest : public ::testing::Test {
78 : decoder_(ObtainHpackHuffmanTable()),
79 decoder_peer_(&decoder_) {}
81 bool DecodeHeaderBlock(StringPiece str) {
82 return decoder_.HandleControlFrameHeadersData(0, str.data(), str.size()) &&
83 decoder_.HandleControlFrameHeadersComplete(0);
85 const std::map<string, string>& decoded_block() const {
86 // TODO(jgraettinger): HpackDecoderTest should implement
87 // SpdyHeadersHandlerInterface, and collect headers for examination.
88 return decoder_peer_.decoded_block();
90 // TODO(jgraettinger): Eliminate uses of this in tests below. Prefer
91 // DecodeHeaderBlock().
92 const std::map<string, string>& DecodeUniqueHeaderSet(StringPiece str) {
93 EXPECT_TRUE(DecodeHeaderBlock(str));
94 return decoded_block();
97 HpackDecoder decoder_;
98 test::HpackDecoderPeer decoder_peer_;
101 TEST_F(HpackDecoderTest, HandleControlFrameHeadersData) {
102 // Strings under threshold are concatenated in the buffer.
103 EXPECT_TRUE(decoder_.HandleControlFrameHeadersData(
104 0, "small string one", 16));
105 EXPECT_TRUE(decoder_.HandleControlFrameHeadersData(
106 0, "small string two", 16));
107 // A string which would push the buffer over the threshold is refused.
108 EXPECT_FALSE(decoder_.HandleControlFrameHeadersData(
109 0, "fails", kMaxDecodeBufferSize - 32 + 1));
111 EXPECT_EQ(decoder_peer_.headers_block_buffer(),
112 "small string onesmall string two");
115 TEST_F(HpackDecoderTest, HandleControlFrameHeadersComplete) {
116 // Decode a block which toggles two static headers into the reference set.
117 EXPECT_TRUE(DecodeHeaderBlock("\x82\x86"));
119 decoder_peer_.set_cookie_name("CooKie");
120 decoder_peer_.set_cookie_value("foobar=baz");
122 // Headers in the reference set should be emitted.
123 // Incremental cookie buffer should be emitted and cleared.
124 decoder_.HandleControlFrameHeadersData(0, NULL, 0);
125 decoder_.HandleControlFrameHeadersComplete(0);
127 EXPECT_THAT(decoded_block(), ElementsAre(
128 Pair(":method", "GET"),
129 Pair(":path", "/index.html"),
130 Pair("CooKie", "foobar=baz")));
132 EXPECT_EQ(decoder_peer_.cookie_name(), "");
133 EXPECT_EQ(decoder_peer_.cookie_value(), "");
136 TEST_F(HpackDecoderTest, HandleHeaderRepresentation) {
137 // Casing of first Cookie is retained, but all instances are joined.
138 decoder_peer_.HandleHeaderRepresentation("cOOkie", " part 1");
139 decoder_peer_.HandleHeaderRepresentation("cookie", "part 2 ");
140 decoder_peer_.HandleHeaderRepresentation("cookie", "part3");
142 // Already-delimited headers are passed through.
143 decoder_peer_.HandleHeaderRepresentation("passed-through",
144 string("foo\0baz", 7));
146 // Other headers are joined on \0. Case matters.
147 decoder_peer_.HandleHeaderRepresentation("joined", "not joined");
148 decoder_peer_.HandleHeaderRepresentation("joineD", "value 1");
149 decoder_peer_.HandleHeaderRepresentation("joineD", "value 2");
151 // Empty headers remain empty.
152 decoder_peer_.HandleHeaderRepresentation("empty", "");
154 // Joined empty headers work as expected.
155 decoder_peer_.HandleHeaderRepresentation("empty-joined", "");
156 decoder_peer_.HandleHeaderRepresentation("empty-joined", "foo");
157 decoder_peer_.HandleHeaderRepresentation("empty-joined", "");
158 decoder_peer_.HandleHeaderRepresentation("empty-joined", "");
160 // Non-contiguous cookie crumb.
161 decoder_peer_.HandleHeaderRepresentation("Cookie", " fin!");
163 // Finish and emit all headers.
164 decoder_.HandleControlFrameHeadersComplete(0);
166 EXPECT_THAT(decoded_block(), ElementsAre(
167 Pair("cOOkie", " part 1; part 2 ; part3; fin!"),
169 Pair("empty-joined", string("\0foo\0\0", 6)),
170 Pair("joineD", string("value 1\0value 2", 15)),
171 Pair("joined", "not joined"),
172 Pair("passed-through", string("foo\0baz", 7))));
175 // Decoding an encoded name with a valid string literal should work.
176 TEST_F(HpackDecoderTest, DecodeNextNameLiteral) {
177 HpackInputStream input_stream(kLiteralBound, StringPiece("\x00\x04name", 6));
179 StringPiece string_piece;
180 EXPECT_TRUE(decoder_peer_.DecodeNextName(&input_stream, &string_piece));
181 EXPECT_EQ("name", string_piece);
182 EXPECT_FALSE(input_stream.HasMoreData());
185 TEST_F(HpackDecoderTest, DecodeNextNameLiteralWithHuffmanEncoding) {
186 char input[] = "\x00\x88\x4e\xb0\x8b\x74\x97\x90\xfa\x7f";
187 StringPiece foo(input, arraysize(input) - 1);
188 HpackInputStream input_stream(kLiteralBound, foo);
190 StringPiece string_piece;
191 EXPECT_TRUE(decoder_peer_.DecodeNextName(&input_stream, &string_piece));
192 EXPECT_EQ("custom-key", string_piece);
193 EXPECT_FALSE(input_stream.HasMoreData());
196 // Decoding an encoded name with a valid index should work.
197 TEST_F(HpackDecoderTest, DecodeNextNameIndexed) {
198 HpackInputStream input_stream(kLiteralBound, "\x01");
200 StringPiece string_piece;
201 EXPECT_TRUE(decoder_peer_.DecodeNextName(&input_stream, &string_piece));
202 EXPECT_EQ(":authority", string_piece);
203 EXPECT_FALSE(input_stream.HasMoreData());
206 // Decoding an encoded name with an invalid index should fail.
207 TEST_F(HpackDecoderTest, DecodeNextNameInvalidIndex) {
208 // One more than the number of static table entries.
209 HpackInputStream input_stream(kLiteralBound, "\x3d");
211 StringPiece string_piece;
212 EXPECT_FALSE(decoder_peer_.DecodeNextName(&input_stream, &string_piece));
215 // Decoding an indexed header should toggle the index's presence in
216 // the reference set, making a copy of static table entries if
217 // necessary. It should also emit the header if toggled on (and only
218 // as many times as it was toggled on).
219 TEST_F(HpackDecoderTest, IndexedHeaderBasic) {
220 // Toggle on static table entry #2 (and make a copy at index #1),
221 // then toggle on static table entry #5 (which is now #6 because of
223 std::map<string, string> header_set1 =
224 DecodeUniqueHeaderSet("\x82\x86");
225 std::map<string, string> expected_header_set1;
226 expected_header_set1[":method"] = "GET";
227 expected_header_set1[":path"] = "/index.html";
228 EXPECT_EQ(expected_header_set1, header_set1);
230 std::map<string, string> expected_header_set2;
231 expected_header_set2[":path"] = "/index.html";
232 // Toggle off the copy of static table entry #5.
233 std::map<string, string> header_set2 =
234 DecodeUniqueHeaderSet("\x82");
235 EXPECT_EQ(expected_header_set2, header_set2);
238 // Test a too-large indexed header.
239 TEST_F(HpackDecoderTest, InvalidIndexedHeader) {
240 // High-bit set, and a prefix of one more than the number of static entries.
241 EXPECT_FALSE(DecodeHeaderBlock(StringPiece("\xbd", 1)));
244 TEST_F(HpackDecoderTest, ContextUpdateMaximumSize) {
245 EXPECT_EQ(kDefaultHeaderTableSizeSetting,
246 decoder_peer_.header_table().max_size());
248 // Maximum-size update with size 126. Succeeds.
249 EXPECT_TRUE(DecodeHeaderBlock(StringPiece("\x80\x7e", 2)));
250 EXPECT_EQ(126u, decoder_peer_.header_table().max_size());
254 // Maximum-size update with kDefaultHeaderTableSizeSetting. Succeeds.
255 HpackOutputStream output_stream;
256 output_stream.AppendBits(0x80, 8); // Context update.
257 output_stream.AppendBits(0x00, 1); // Size update.
258 output_stream.AppendUint32(kDefaultHeaderTableSizeSetting);
260 output_stream.TakeString(&input);
261 EXPECT_TRUE(DecodeHeaderBlock(StringPiece(input)));
262 EXPECT_EQ(kDefaultHeaderTableSizeSetting,
263 decoder_peer_.header_table().max_size());
266 // Maximum-size update with kDefaultHeaderTableSizeSetting + 1. Fails.
267 HpackOutputStream output_stream;
268 output_stream.AppendBits(0x80, 8); // Context update.
269 output_stream.AppendBits(0x00, 1); // Size update.
270 output_stream.AppendUint32(kDefaultHeaderTableSizeSetting + 1);
272 output_stream.TakeString(&input);
273 EXPECT_FALSE(DecodeHeaderBlock(StringPiece(input)));
274 EXPECT_EQ(kDefaultHeaderTableSizeSetting,
275 decoder_peer_.header_table().max_size());
279 TEST_F(HpackDecoderTest, ContextUpdateClearReferenceSet) {
280 // Toggle on a couple of headers.
281 std::map<string, string> header_set1 =
282 DecodeUniqueHeaderSet("\x82\x86");
283 std::map<string, string> expected_header_set1;
284 expected_header_set1[":method"] = "GET";
285 expected_header_set1[":path"] = "/index.html";
286 EXPECT_EQ(expected_header_set1, header_set1);
288 // Send a context update to clear the reference set.
289 std::map<string, string> header_set2 =
290 DecodeUniqueHeaderSet("\x80\x80");
291 std::map<string, string> expected_header_set2;
292 EXPECT_EQ(expected_header_set2, header_set2);
295 // Decoding two valid encoded literal headers with no indexing should
297 TEST_F(HpackDecoderTest, LiteralHeaderNoIndexing) {
298 // First header with indexed name, second header with string literal
300 std::map<string, string> header_set =
301 DecodeUniqueHeaderSet(
302 "\x44\x0c/sample/path\x40\x06:path2\x0e/sample/path/2");
304 std::map<string, string> expected_header_set;
305 expected_header_set[":path"] = "/sample/path";
306 expected_header_set[":path2"] = "/sample/path/2";
307 EXPECT_EQ(expected_header_set, header_set);
310 // Decoding two valid encoded literal headers with incremental
311 // indexing and string literal names should work and add the headers
312 // to the reference set.
313 TEST_F(HpackDecoderTest, LiteralHeaderIncrementalIndexing) {
314 std::map<string, string> header_set = DecodeUniqueHeaderSet(
315 StringPiece("\x04\x0c/sample/path\x00\x06:path2\x0e/sample/path/2", 37));
317 std::map<string, string> expected_header_set;
318 expected_header_set[":path"] = "/sample/path";
319 expected_header_set[":path2"] = "/sample/path/2";
320 EXPECT_EQ(expected_header_set, header_set);
322 // Decoding an empty string should just return the reference set.
323 std::map<string, string> header_set2 = DecodeUniqueHeaderSet("");
324 EXPECT_EQ(expected_header_set, header_set2);
327 // Decoding literal headers with invalid indices should fail
329 TEST_F(HpackDecoderTest, LiteralHeaderInvalidIndices) {
332 // One more than the number of static table entries.
333 EXPECT_FALSE(DecodeHeaderBlock(StringPiece("\x7d", 1)));
334 EXPECT_FALSE(DecodeHeaderBlock(StringPiece("\x40", 1)));
336 // Incremental indexing.
338 // One more than the number of static table entries.
339 EXPECT_FALSE(DecodeHeaderBlock(StringPiece("\x3d", 1)));
340 EXPECT_FALSE(DecodeHeaderBlock(StringPiece("\x00", 1)));
343 // Round-tripping the header set from E.2.1 should work.
344 TEST_F(HpackDecoderTest, BasicE21) {
345 HpackEncoder encoder(ObtainHpackHuffmanTable());
347 std::map<string, string> expected_header_set;
348 expected_header_set[":method"] = "GET";
349 expected_header_set[":scheme"] = "http";
350 expected_header_set[":path"] = "/";
351 expected_header_set[":authority"] = "www.example.com";
353 string encoded_header_set;
354 EXPECT_TRUE(encoder.EncodeHeaderSet(
355 expected_header_set, &encoded_header_set));
357 EXPECT_TRUE(DecodeHeaderBlock(encoded_header_set));
358 EXPECT_EQ(expected_header_set, decoded_block());
361 TEST_F(HpackDecoderTest, SectionD3RequestHuffmanExamples) {
362 std::map<string, string> header_set;
364 // 82 | == Indexed - Add ==
367 // 87 | == Indexed - Add ==
369 // | -> :scheme: http
370 // 86 | == Indexed - Add ==
373 // 04 | == Literal indexed ==
374 // | Indexed name (idx = 4)
376 // 8b | Literal value (len = 15)
377 // | Huffman encoded:
378 // db6d 883e 68d1 cb12 25ba 7f | .m..h...%..
381 // | -> :authority: www.example.com
383 "\x82\x87\x86\x04\x8b\xdb\x6d\x88\x3e\x68\xd1\xcb\x12\x25\xba\x7f";
384 header_set = DecodeUniqueHeaderSet(StringPiece(first, arraysize(first)-1));
386 // TODO(jgraettinger): Create HpackEncodingContext and
387 // HpackDecoder peers, and inspect the header table here.
388 EXPECT_THAT(header_set, ElementsAre(
389 Pair(":authority", "www.example.com"),
390 Pair(":method", "GET"),
392 Pair(":scheme", "http")));
394 // 1b | == Literal indexed ==
395 // | Indexed name (idx = 27)
397 // 86 | Literal value (len = 8)
398 // | Huffman encoded:
399 // 6365 4a13 98ff | ceJ...
402 // | -> cache-control: no-cache
403 char second[] = "\x1b\x86\x63\x65\x4a\x13\x98\xff";
404 header_set = DecodeUniqueHeaderSet(StringPiece(second, arraysize(second)-1));
406 EXPECT_THAT(header_set, ElementsAre(
407 Pair(":authority", "www.example.com"),
408 Pair(":method", "GET"),
410 Pair(":scheme", "http"),
411 Pair("cache-control", "no-cache")));
413 // 8080 | == Empty reference set ==
416 // 85 | == Indexed - Add ==
419 // 8c | == Indexed - Add ==
421 // | -> :scheme: https
422 // 8b | == Indexed - Add ==
424 // | -> :path: /index.html
425 // 84 | == Indexed - Add ==
427 // | -> :authority: www.example.com
428 // 00 | == Literal indexed ==
429 // 88 | Literal name (len = 10)
430 // | Huffman encoded:
431 // 4eb0 8b74 9790 fa7f | N..t....
434 // 89 | Literal value (len = 12)
435 // | Huffman encoded:
436 // 4eb0 8b74 979a 17a8 ff | N..t.....
439 // | -> custom-key: custom-value
441 "\x80\x80\x85\x8c\x8b\x84\x00\x88\x4e\xb0\x8b\x74\x97\x90\xfa\x7f\x89"
442 "\x4e\xb0\x8b\x74\x97\x9a\x17\xa8\xff";
443 header_set = DecodeUniqueHeaderSet(StringPiece(third, arraysize(third)-1));
445 EXPECT_THAT(header_set, ElementsAre(
446 Pair(":authority", "www.example.com"),
447 Pair(":method", "GET"),
448 Pair(":path", "/index.html"),
449 Pair(":scheme", "https"),
450 Pair("custom-key", "custom-value")));
453 TEST_F(HpackDecoderTest, SectionD5ResponseHuffmanExamples) {
454 std::map<string, string> header_set;
455 decoder_.ApplyHeaderTableSizeSetting(256);
457 // 08 | == Literal indexed ==
458 // | Indexed name (idx = 8)
460 // 82 | Literal value (len = 3)
461 // | Huffman encoded:
466 // 18 | == Literal indexed ==
467 // | Indexed name (idx = 24)
469 // 85 | Literal value (len = 7)
470 // | Huffman encoded:
471 // 73d5 cd11 1f | s....
474 // | -> cache-control: private
475 // 22 | == Literal indexed ==
476 // | Indexed name (idx = 34)
478 // 98 | Literal value (len = 29)
479 // | Huffman encoded:
480 // ef6b 3a7a 0e6e 8fa2 63d0 729a 6e83 97d8 | .k:z.n..c.r.n...
481 // 69bd 8737 47bb bfc7 | i..7G...
483 // | Mon, 21 Oct 2013 20:13:21
485 // | -> date: Mon, 21 Oct 2013
487 // 30 | == Literal indexed ==
488 // | Indexed name (idx = 48)
490 // 90 | Literal value (len = 23)
491 // | Huffman encoded:
492 // ce31 743d 801b 6db1 07cd 1a39 6244 b74f | .1t=..m....9bD.O
494 // | https://www.example.com
495 // | -> location: https://www.e
498 "\x08\x82\x98\xa7\x18\x85\x73\xd5\xcd\x11\x1f\x22\x98\xef\x6b"
499 "\x3a\x7a\x0e\x6e\x8f\xa2\x63\xd0\x72\x9a\x6e\x83\x97\xd8\x69\xbd\x87"
500 "\x37\x47\xbb\xbf\xc7\x30\x90\xce\x31\x74\x3d\x80\x1b\x6d\xb1\x07\xcd"
501 "\x1a\x39\x62\x44\xb7\x4f";
502 header_set = DecodeUniqueHeaderSet(StringPiece(first, arraysize(first)-1));
504 EXPECT_THAT(header_set, ElementsAre(
505 Pair(":status", "302"),
506 Pair("cache-control", "private"),
507 Pair("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
508 Pair("location", "https://www.example.com")));
510 // 8c | == Indexed - Add ==
512 // | - evict: :status: 302
514 char second[] = "\x8c";
515 header_set = DecodeUniqueHeaderSet(StringPiece(second, arraysize(second)-1));
517 EXPECT_THAT(header_set, ElementsAre(
518 Pair(":status", "200"),
519 Pair("cache-control", "private"),
520 Pair("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
521 Pair("location", "https://www.example.com")));
523 // 84 | == Indexed - Remove ==
525 // | -> cache-control: private
526 // 84 | == Indexed - Add ==
528 // | -> cache-control: private
529 // 03 | == Literal indexed ==
530 // | Indexed name (idx = 3)
532 // 98 | Literal value (len = 29)
533 // | Huffman encoded:
534 // ef6b 3a7a 0e6e 8fa2 63d0 729a 6e83 97d8 | .k:z.n..c.r.n...
535 // 69bd 873f 47bb bfc7 | i..?G...
537 // | Mon, 21 Oct 2013 20:13:22
539 // | - evict: cache-control: pr
541 // | -> date: Mon, 21 Oct 2013
543 // 1d | == Literal indexed ==
544 // | Indexed name (idx = 29)
545 // | content-encoding
546 // 83 | Literal value (len = 4)
547 // | Huffman encoded:
551 // | - evict: date: Mon, 21 Oct
552 // | 2013 20:13:21 GMT
553 // | -> content-encoding: gzip
554 // 84 | == Indexed - Remove ==
556 // | -> location: https://www.e
558 // 84 | == Indexed - Add ==
560 // | -> location: https://www.e
562 // 83 | == Indexed - Remove ==
565 // 83 | == Indexed - Add ==
568 // 3a | == Literal indexed ==
569 // | Indexed name (idx = 58)
571 // b3 | Literal value (len = 56)
572 // | Huffman encoded:
573 // c5ad b77f 876f c7fb f7fd bfbe bff3 f7f4 | .....o..........
574 // fb7e bbbe 9f5f 87e3 7fef edfa eefa 7c3f | ....._........|?
575 // 1d5d 1a23 ce54 6436 cd49 4bd5 d1cc 5f05 | .].#.Td6.IK..._.
578 // | foo=ASDJKHQKBZXOQWEOPIUAXQ
579 // | WEOIU; max-age=3600; versi
581 // | - evict: location: https:/
582 // | /www.example.com
583 // | - evict: :status: 200
584 // | -> set-cookie: foo=ASDJKHQ
585 // | KBZXOQWEOPIUAXQWEOIU; ma
586 // | x-age=3600; version=1
588 "\x84\x84\x03\x98\xef\x6b\x3a\x7a\x0e\x6e\x8f\xa2\x63\xd0\x72"
589 "\x9a\x6e\x83\x97\xd8\x69\xbd\x87\x3f\x47\xbb\xbf\xc7\x1d\x83\xcb\xd5"
590 "\x4e\x84\x84\x83\x83\x3a\xb3\xc5\xad\xb7\x7f\x87\x6f\xc7\xfb\xf7\xfd"
591 "\xbf\xbe\xbf\xf3\xf7\xf4\xfb\x7e\xbb\xbe\x9f\x5f\x87\xe3\x7f\xef\xed"
592 "\xfa\xee\xfa\x7c\x3f\x1d\x5d\x1a\x23\xce\x54\x64\x36\xcd\x49\x4b\xd5"
593 "\xd1\xcc\x5f\x05\x35\x96\x9b";
594 header_set = DecodeUniqueHeaderSet(StringPiece(third, arraysize(third)-1));
596 EXPECT_THAT(header_set, ElementsAre(
597 Pair(":status", "200"),
598 Pair("cache-control", "private"),
599 Pair("content-encoding", "gzip"),
600 Pair("date", "Mon, 21 Oct 2013 20:13:22 GMT"),
601 Pair("location", "https://www.example.com"),
602 Pair("set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU;"
603 " max-age=3600; version=1")));