1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "net/http/http_response_headers.h"
6
7 #include <stdint.h>
8
9 #include <iostream>
10 #include <memory>
11 #include <unordered_set>
12
13 #include "base/pickle.h"
14 #include "base/strings/stringprintf.h"
15 #include "base/time/time.h"
16 #include "base/values.h"
17 #include "net/base/cronet_buildflags.h"
18 #include "net/base/tracing.h"
19 #include "net/http/http_byte_range.h"
20 #include "net/http/http_response_headers_test_util.h"
21 #include "net/http/http_util.h"
22 #include "testing/gtest/include/gtest/gtest.h"
23
24 #if !BUILDFLAG(CRONET_BUILD)
25 #include "third_party/perfetto/include/perfetto/test/traced_value_test_support.h"
26 #endif
27
28 namespace net {
29
30 namespace {
31
32 struct TestData {
33 const char* raw_headers;
34 const char* expected_headers;
35 HttpVersion expected_version;
36 int expected_response_code;
37 const char* expected_status_text;
38 };
39
40 class HttpResponseHeadersTest : public testing::Test {
41 };
42
43 // Transform "normal"-looking headers (\n-separated) to the appropriate
44 // input format for ParseRawHeaders (\0-separated).
HeadersToRaw(std::string * headers)45 void HeadersToRaw(std::string* headers) {
46 std::replace(headers->begin(), headers->end(), '\n', '\0');
47 if (!headers->empty())
48 *headers += '\0';
49 }
50
51 class HttpResponseHeadersCacheControlTest : public HttpResponseHeadersTest {
52 protected:
53 // Make tests less verbose.
54 typedef base::TimeDelta TimeDelta;
55
56 // Initilise the headers() value with a Cache-Control header set to
57 // |cache_control|. |cache_control| is copied and so can safely be a
58 // temporary.
InitializeHeadersWithCacheControl(const char * cache_control)59 void InitializeHeadersWithCacheControl(const char* cache_control) {
60 std::string raw_headers("HTTP/1.1 200 OK\n");
61 raw_headers += "Cache-Control: ";
62 raw_headers += cache_control;
63 raw_headers += "\n";
64 HeadersToRaw(&raw_headers);
65 headers_ = base::MakeRefCounted<HttpResponseHeaders>(raw_headers);
66 }
67
headers()68 const scoped_refptr<HttpResponseHeaders>& headers() { return headers_; }
69
70 // Return a pointer to a TimeDelta object. For use when the value doesn't
71 // matter.
TimeDeltaPointer()72 TimeDelta* TimeDeltaPointer() { return &delta_; }
73
74 // Get the max-age value. This should only be used in tests where a valid
75 // max-age parameter is expected to be present.
GetMaxAgeValue()76 TimeDelta GetMaxAgeValue() {
77 DCHECK(headers_.get()) << "Call InitializeHeadersWithCacheControl() first";
78 TimeDelta max_age_value;
79 EXPECT_TRUE(headers()->GetMaxAgeValue(&max_age_value));
80 return max_age_value;
81 }
82
83 // Get the stale-while-revalidate value. This should only be used in tests
84 // where a valid max-age parameter is expected to be present.
GetStaleWhileRevalidateValue()85 TimeDelta GetStaleWhileRevalidateValue() {
86 DCHECK(headers_.get()) << "Call InitializeHeadersWithCacheControl() first";
87 TimeDelta stale_while_revalidate_value;
88 EXPECT_TRUE(
89 headers()->GetStaleWhileRevalidateValue(&stale_while_revalidate_value));
90 return stale_while_revalidate_value;
91 }
92
93 private:
94 scoped_refptr<HttpResponseHeaders> headers_;
95 TimeDelta delta_;
96 };
97
98 class CommonHttpResponseHeadersTest
99 : public HttpResponseHeadersTest,
100 public ::testing::WithParamInterface<TestData> {
101 };
102
103 constexpr auto ToSimpleString = test::HttpResponseHeadersToSimpleString;
104
105 // Transform to readable output format (so it's easier to see diffs).
EscapeForPrinting(std::string * s)106 void EscapeForPrinting(std::string* s) {
107 std::replace(s->begin(), s->end(), ' ', '_');
108 std::replace(s->begin(), s->end(), '\n', '\\');
109 }
110
TEST_P(CommonHttpResponseHeadersTest,TestCommon)111 TEST_P(CommonHttpResponseHeadersTest, TestCommon) {
112 const TestData test = GetParam();
113
114 std::string raw_headers(test.raw_headers);
115 HeadersToRaw(&raw_headers);
116 std::string expected_headers(test.expected_headers);
117
118 auto parsed = base::MakeRefCounted<HttpResponseHeaders>(raw_headers);
119 std::string headers = ToSimpleString(parsed);
120
121 EscapeForPrinting(&headers);
122 EscapeForPrinting(&expected_headers);
123
124 EXPECT_EQ(expected_headers, headers);
125
126 SCOPED_TRACE(test.raw_headers);
127
128 EXPECT_TRUE(test.expected_version == parsed->GetHttpVersion());
129 EXPECT_EQ(test.expected_response_code, parsed->response_code());
130 EXPECT_EQ(test.expected_status_text, parsed->GetStatusText());
131 }
132
133 TestData response_headers_tests[] = {
134 {// Normalize whitespace.
135 "HTTP/1.1 202 Accepted \n"
136 "Content-TYPE : text/html; charset=utf-8 \n"
137 "Set-Cookie: a \n"
138 "Set-Cookie: b \n",
139
140 "HTTP/1.1 202 Accepted\n"
141 "Content-TYPE: text/html; charset=utf-8\n"
142 "Set-Cookie: a\n"
143 "Set-Cookie: b\n",
144
145 HttpVersion(1, 1), 202, "Accepted"},
146 {// Normalize leading whitespace.
147 "HTTP/1.1 202 Accepted \n"
148 // Starts with space -- will be skipped as invalid.
149 " Content-TYPE : text/html; charset=utf-8 \n"
150 "Set-Cookie: a \n"
151 "Set-Cookie: b \n",
152
153 "HTTP/1.1 202 Accepted\n"
154 "Set-Cookie: a\n"
155 "Set-Cookie: b\n",
156
157 HttpVersion(1, 1), 202, "Accepted"},
158 {// Keep whitespace within status text.
159 "HTTP/1.0 404 Not found \n",
160
161 "HTTP/1.0 404 Not found\n",
162
163 HttpVersion(1, 0), 404, "Not found"},
164 {// Normalize blank headers.
165 "HTTP/1.1 200 OK\n"
166 "Header1 : \n"
167 "Header2: \n"
168 "Header3:\n"
169 "Header4\n"
170 "Header5 :\n",
171
172 "HTTP/1.1 200 OK\n"
173 "Header1: \n"
174 "Header2: \n"
175 "Header3: \n"
176 "Header5: \n",
177
178 HttpVersion(1, 1), 200, "OK"},
179 {// Don't believe the http/0.9 version if there are headers!
180 "hTtP/0.9 201\n"
181 "Content-TYPE: text/html; charset=utf-8\n",
182
183 "HTTP/1.0 201\n"
184 "Content-TYPE: text/html; charset=utf-8\n",
185
186 HttpVersion(1, 0), 201, ""},
187 {// Accept the HTTP/0.9 version number if there are no headers.
188 // This is how HTTP/0.9 responses get constructed from
189 // HttpNetworkTransaction.
190 "hTtP/0.9 200 OK\n",
191
192 "HTTP/0.9 200 OK\n",
193
194 HttpVersion(0, 9), 200, "OK"},
195 {// Do not add missing status text.
196 "HTTP/1.1 201\n"
197 "Content-TYPE: text/html; charset=utf-8\n",
198
199 "HTTP/1.1 201\n"
200 "Content-TYPE: text/html; charset=utf-8\n",
201
202 HttpVersion(1, 1), 201, ""},
203 {// Normalize bad status line.
204 "SCREWED_UP_STATUS_LINE\n"
205 "Content-TYPE: text/html; charset=utf-8\n",
206
207 "HTTP/1.0 200 OK\n"
208 "Content-TYPE: text/html; charset=utf-8\n",
209
210 HttpVersion(1, 0), 200, "OK"},
211 {// Normalize bad status line.
212 "Foo bar.",
213
214 "HTTP/1.0 200\n",
215
216 HttpVersion(1, 0), 200, ""},
217 {// Normalize invalid status code.
218 "HTTP/1.1 -1 Unknown\n",
219
220 "HTTP/1.1 200\n",
221
222 HttpVersion(1, 1), 200, ""},
223 {// Normalize empty header.
224 "",
225
226 "HTTP/1.0 200 OK\n",
227
228 HttpVersion(1, 0), 200, "OK"},
229 {// Normalize headers that start with a colon.
230 "HTTP/1.1 202 Accepted \n"
231 "foo: bar\n"
232 ": a \n"
233 " : b\n"
234 "baz: blat \n",
235
236 "HTTP/1.1 202 Accepted\n"
237 "foo: bar\n"
238 "baz: blat\n",
239
240 HttpVersion(1, 1), 202, "Accepted"},
241 {// Normalize headers that end with a colon.
242 "HTTP/1.1 202 Accepted \n"
243 "foo: \n"
244 "bar:\n"
245 "baz: blat \n"
246 "zip:\n",
247
248 "HTTP/1.1 202 Accepted\n"
249 "foo: \n"
250 "bar: \n"
251 "baz: blat\n"
252 "zip: \n",
253
254 HttpVersion(1, 1), 202, "Accepted"},
255 {// Normalize whitespace headers.
256 "\n \n",
257
258 "HTTP/1.0 200 OK\n",
259
260 HttpVersion(1, 0), 200, "OK"},
261 {// Has multiple Set-Cookie headers.
262 "HTTP/1.1 200 OK\n"
263 "Set-Cookie: x=1\n"
264 "Set-Cookie: y=2\n",
265
266 "HTTP/1.1 200 OK\n"
267 "Set-Cookie: x=1\n"
268 "Set-Cookie: y=2\n",
269
270 HttpVersion(1, 1), 200, "OK"},
271 {// Has multiple cache-control headers.
272 "HTTP/1.1 200 OK\n"
273 "Cache-control: private\n"
274 "cache-Control: no-store\n",
275
276 "HTTP/1.1 200 OK\n"
277 "Cache-control: private\n"
278 "cache-Control: no-store\n",
279
280 HttpVersion(1, 1), 200, "OK"},
281 {// Has multiple-value cache-control header.
282 "HTTP/1.1 200 OK\n"
283 "Cache-Control: private, no-store\n",
284
285 "HTTP/1.1 200 OK\n"
286 "Cache-Control: private, no-store\n",
287
288 HttpVersion(1, 1), 200, "OK"},
289 {// Missing HTTP.
290 " 200 Yes\n",
291
292 "HTTP/1.0 200 Yes\n",
293
294 HttpVersion(1, 0), 200, "Yes"},
295 {// Only HTTP.
296 "HTTP\n",
297
298 "HTTP/1.0 200 OK\n",
299
300 HttpVersion(1, 0), 200, "OK"},
301 {// Missing HTTP version.
302 "HTTP 404 No\n",
303
304 "HTTP/1.0 404 No\n",
305
306 HttpVersion(1, 0), 404, "No"},
307 {// Missing dot in HTTP version.
308 "HTTP/1 304 Not Friday\n",
309
310 "HTTP/1.0 304 Not Friday\n",
311
312 HttpVersion(1, 0), 304, "Not Friday"},
313 {// Multi-digit HTTP version (our error detection is bad).
314 "HTTP/234.01 204 Nothing here\n",
315
316 "HTTP/2.0 204 Nothing here\n",
317
318 HttpVersion(2, 0), 204, "Nothing here"},
319 {// HTTP minor version attached to response code (pretty bad parsing).
320 "HTTP/1 302.1 Bad parse\n",
321
322 "HTTP/1.1 302 .1 Bad parse\n",
323
324 HttpVersion(1, 1), 302, ".1 Bad parse"},
325 {// HTTP minor version inside the status text (bad parsing).
326 "HTTP/1 410 Gone in 0.1 seconds\n",
327
328 "HTTP/1.1 410 Gone in 0.1 seconds\n",
329
330 HttpVersion(1, 1), 410, "Gone in 0.1 seconds"},
331 {// Status text smushed into response code.
332 "HTTP/1.1 426Smush\n",
333
334 "HTTP/1.1 426 Smush\n",
335
336 HttpVersion(1, 1), 426, "Smush"},
337 {// Tab not recognised as separator (this is standard compliant).
338 "HTTP/1.1\t500 204 Bad\n",
339
340 "HTTP/1.1 204 Bad\n",
341
342 HttpVersion(1, 1), 204, "Bad"},
343 {// Junk after HTTP version is ignored.
344 "HTTP/1.1ignored 201 Not ignored\n",
345
346 "HTTP/1.1 201 Not ignored\n",
347
348 HttpVersion(1, 1), 201, "Not ignored"},
349 {// Tab gets included in status text.
350 "HTTP/1.1 501\tStatus\t\n",
351
352 "HTTP/1.1 501 \tStatus\t\n",
353
354 HttpVersion(1, 1), 501, "\tStatus\t"},
355 {// Zero response code.
356 "HTTP/1.1 0 Zero\n",
357
358 "HTTP/1.1 0 Zero\n",
359
360 HttpVersion(1, 1), 0, "Zero"},
361 {// Oversize response code.
362 "HTTP/1.1 20230904 Monday\n",
363
364 "HTTP/1.1 20230904 Monday\n",
365
366 HttpVersion(1, 1), 20230904, "Monday"},
367 {// Overflowing response code.
368 "HTTP/1.1 9123456789 Overflow\n",
369
370 "HTTP/1.1 9123456789 Overflow\n",
371
372 HttpVersion(1, 1), 2147483647, "Overflow"},
373 };
374
375 INSTANTIATE_TEST_SUITE_P(HttpResponseHeaders,
376 CommonHttpResponseHeadersTest,
377 testing::ValuesIn(response_headers_tests));
378
379 struct PersistData {
380 HttpResponseHeaders::PersistOptions options;
381 const char* raw_headers;
382 const char* expected_headers;
383 };
384
385 class PersistenceTest
386 : public HttpResponseHeadersTest,
387 public ::testing::WithParamInterface<PersistData> {
388 };
389
TEST_P(PersistenceTest,Persist)390 TEST_P(PersistenceTest, Persist) {
391 const PersistData test = GetParam();
392
393 std::string headers = test.raw_headers;
394 HeadersToRaw(&headers);
395 auto parsed1 = base::MakeRefCounted<HttpResponseHeaders>(headers);
396
397 base::Pickle pickle;
398 parsed1->Persist(&pickle, test.options);
399
400 base::PickleIterator iter(pickle);
401 auto parsed2 = base::MakeRefCounted<HttpResponseHeaders>(&iter);
402
403 EXPECT_EQ(std::string(test.expected_headers), ToSimpleString(parsed2));
404 }
405
406 const struct PersistData persistence_tests[] = {
407 {HttpResponseHeaders::PERSIST_ALL,
408 "HTTP/1.1 200 OK\n"
409 "Cache-control:private\n"
410 "cache-Control:no-store\n",
411
412 "HTTP/1.1 200 OK\n"
413 "Cache-control: private\n"
414 "cache-Control: no-store\n"},
415 {HttpResponseHeaders::PERSIST_SANS_HOP_BY_HOP,
416 "HTTP/1.1 200 OK\n"
417 "connection: keep-alive\n"
418 "server: blah\n",
419
420 "HTTP/1.1 200 OK\n"
421 "server: blah\n"},
422 {HttpResponseHeaders::PERSIST_SANS_NON_CACHEABLE |
423 HttpResponseHeaders::PERSIST_SANS_HOP_BY_HOP,
424 "HTTP/1.1 200 OK\n"
425 "fOo: 1\n"
426 "Foo: 2\n"
427 "Transfer-Encoding: chunked\n"
428 "CoNnection: keep-alive\n"
429 "cache-control: private, no-cache=\"foo\"\n",
430
431 "HTTP/1.1 200 OK\n"
432 "cache-control: private, no-cache=\"foo\"\n"},
433 {HttpResponseHeaders::PERSIST_SANS_NON_CACHEABLE,
434 "HTTP/1.1 200 OK\n"
435 "Foo: 2\n"
436 "Cache-Control: private,no-cache=\"foo, bar\"\n"
437 "bar",
438
439 "HTTP/1.1 200 OK\n"
440 "Cache-Control: private,no-cache=\"foo, bar\"\n"},
441 // Ignore bogus no-cache value.
442 {HttpResponseHeaders::PERSIST_SANS_NON_CACHEABLE,
443 "HTTP/1.1 200 OK\n"
444 "Foo: 2\n"
445 "Cache-Control: private,no-cache=foo\n",
446
447 "HTTP/1.1 200 OK\n"
448 "Foo: 2\n"
449 "Cache-Control: private,no-cache=foo\n"},
450 // Ignore bogus no-cache value.
451 {HttpResponseHeaders::PERSIST_SANS_NON_CACHEABLE,
452 "HTTP/1.1 200 OK\n"
453 "Foo: 2\n"
454 "Cache-Control: private, no-cache=\n",
455
456 "HTTP/1.1 200 OK\n"
457 "Foo: 2\n"
458 "Cache-Control: private, no-cache=\n"},
459 // Ignore empty no-cache value.
460 {HttpResponseHeaders::PERSIST_SANS_NON_CACHEABLE,
461 "HTTP/1.1 200 OK\n"
462 "Foo: 2\n"
463 "Cache-Control: private, no-cache=\"\"\n",
464
465 "HTTP/1.1 200 OK\n"
466 "Foo: 2\n"
467 "Cache-Control: private, no-cache=\"\"\n"},
468 // Ignore wrong quotes no-cache value.
469 {HttpResponseHeaders::PERSIST_SANS_NON_CACHEABLE,
470 "HTTP/1.1 200 OK\n"
471 "Foo: 2\n"
472 "Cache-Control: private, no-cache=\'foo\'\n",
473
474 "HTTP/1.1 200 OK\n"
475 "Foo: 2\n"
476 "Cache-Control: private, no-cache=\'foo\'\n"},
477 // Ignore unterminated quotes no-cache value.
478 {HttpResponseHeaders::PERSIST_SANS_NON_CACHEABLE,
479 "HTTP/1.1 200 OK\n"
480 "Foo: 2\n"
481 "Cache-Control: private, no-cache=\"foo\n",
482
483 "HTTP/1.1 200 OK\n"
484 "Foo: 2\n"
485 "Cache-Control: private, no-cache=\"foo\n"},
486 // Accept sloppy LWS.
487 {HttpResponseHeaders::PERSIST_SANS_NON_CACHEABLE,
488 "HTTP/1.1 200 OK\n"
489 "Foo: 2\n"
490 "Cache-Control: private, no-cache=\" foo\t, bar\"\n",
491
492 "HTTP/1.1 200 OK\n"
493 "Cache-Control: private, no-cache=\" foo\t, bar\"\n"},
494 // Header name appears twice, separated by another header.
495 {HttpResponseHeaders::PERSIST_ALL,
496 "HTTP/1.1 200 OK\n"
497 "Foo: 1\n"
498 "Bar: 2\n"
499 "Foo: 3\n",
500
501 "HTTP/1.1 200 OK\n"
502 "Foo: 1\n"
503 "Bar: 2\n"
504 "Foo: 3\n"},
505 // Header name appears twice, separated by another header (type 2).
506 {HttpResponseHeaders::PERSIST_ALL,
507 "HTTP/1.1 200 OK\n"
508 "Foo: 1, 3\n"
509 "Bar: 2\n"
510 "Foo: 4\n",
511
512 "HTTP/1.1 200 OK\n"
513 "Foo: 1, 3\n"
514 "Bar: 2\n"
515 "Foo: 4\n"},
516 // Test filtering of cookie headers.
517 {HttpResponseHeaders::PERSIST_SANS_COOKIES,
518 "HTTP/1.1 200 OK\n"
519 "Set-Cookie: foo=bar; httponly\n"
520 "Set-Cookie: bar=foo\n"
521 "Bar: 1\n"
522 "Set-Cookie2: bar2=foo2\n",
523
524 "HTTP/1.1 200 OK\n"
525 "Bar: 1\n"},
526 {HttpResponseHeaders::PERSIST_SANS_COOKIES,
527 "HTTP/1.1 200 OK\n"
528 "Set-Cookie: foo=bar\n"
529 "Foo: 2\n"
530 "Clear-Site-Data: \"cookies\"\n"
531 "Bar: 3\n",
532
533 "HTTP/1.1 200 OK\n"
534 "Foo: 2\n"
535 "Bar: 3\n"},
536 // Test LWS at the end of a header.
537 {HttpResponseHeaders::PERSIST_ALL,
538 "HTTP/1.1 200 OK\n"
539 "Content-Length: 450 \n"
540 "Content-Encoding: gzip\n",
541
542 "HTTP/1.1 200 OK\n"
543 "Content-Length: 450\n"
544 "Content-Encoding: gzip\n"},
545 // Test LWS at the end of a header.
546 {HttpResponseHeaders::PERSIST_RAW,
547 "HTTP/1.1 200 OK\n"
548 "Content-Length: 450 \n"
549 "Content-Encoding: gzip\n",
550
551 "HTTP/1.1 200 OK\n"
552 "Content-Length: 450\n"
553 "Content-Encoding: gzip\n"},
554 // Test filtering of transport security state headers.
555 {HttpResponseHeaders::PERSIST_SANS_SECURITY_STATE,
556 "HTTP/1.1 200 OK\n"
557 "Strict-Transport-Security: max-age=1576800\n"
558 "Bar: 1\n",
559
560 "HTTP/1.1 200 OK\n"
561 "Bar: 1\n"},
562 };
563
564 INSTANTIATE_TEST_SUITE_P(HttpResponseHeaders,
565 PersistenceTest,
566 testing::ValuesIn(persistence_tests));
567
TEST(HttpResponseHeadersTest,EnumerateHeader_Coalesced)568 TEST(HttpResponseHeadersTest, EnumerateHeader_Coalesced) {
569 // Ensure that commas in quoted strings are not regarded as value separators.
570 // Ensure that whitespace following a value is trimmed properly.
571 std::string headers =
572 "HTTP/1.1 200 OK\n"
573 "Cache-control:,,private , no-cache=\"set-cookie,server\",\n"
574 "cache-Control: no-store\n"
575 "cache-Control:\n";
576 HeadersToRaw(&headers);
577 auto parsed = base::MakeRefCounted<HttpResponseHeaders>(headers);
578
579 size_t iter = 0;
580 std::string value;
581 ASSERT_TRUE(parsed->EnumerateHeader(&iter, "cache-control", &value));
582 EXPECT_EQ("", value);
583 ASSERT_TRUE(parsed->EnumerateHeader(&iter, "cache-control", &value));
584 EXPECT_EQ("", value);
585 ASSERT_TRUE(parsed->EnumerateHeader(&iter, "cache-control", &value));
586 EXPECT_EQ("private", value);
587 ASSERT_TRUE(parsed->EnumerateHeader(&iter, "cache-control", &value));
588 EXPECT_EQ("no-cache=\"set-cookie,server\"", value);
589 ASSERT_TRUE(parsed->EnumerateHeader(&iter, "cache-control", &value));
590 EXPECT_EQ("", value);
591 ASSERT_TRUE(parsed->EnumerateHeader(&iter, "cache-control", &value));
592 EXPECT_EQ("no-store", value);
593 ASSERT_TRUE(parsed->EnumerateHeader(&iter, "cache-control", &value));
594 EXPECT_EQ("", value);
595 EXPECT_FALSE(parsed->EnumerateHeader(&iter, "cache-control", &value));
596 }
597
TEST(HttpResponseHeadersTest,EnumerateHeader_Challenge)598 TEST(HttpResponseHeadersTest, EnumerateHeader_Challenge) {
599 // Even though WWW-Authenticate has commas, it should not be treated as
600 // coalesced values.
601 std::string headers =
602 "HTTP/1.1 401 OK\n"
603 "WWW-Authenticate:Digest realm=foobar, nonce=x, domain=y\n"
604 "WWW-Authenticate:Basic realm=quatar\n";
605 HeadersToRaw(&headers);
606 auto parsed = base::MakeRefCounted<HttpResponseHeaders>(headers);
607
608 size_t iter = 0;
609 std::string value;
610 EXPECT_TRUE(parsed->EnumerateHeader(&iter, "WWW-Authenticate", &value));
611 EXPECT_EQ("Digest realm=foobar, nonce=x, domain=y", value);
612 EXPECT_TRUE(parsed->EnumerateHeader(&iter, "WWW-Authenticate", &value));
613 EXPECT_EQ("Basic realm=quatar", value);
614 EXPECT_FALSE(parsed->EnumerateHeader(&iter, "WWW-Authenticate", &value));
615 }
616
TEST(HttpResponseHeadersTest,EnumerateHeader_DateValued)617 TEST(HttpResponseHeadersTest, EnumerateHeader_DateValued) {
618 // The comma in a date valued header should not be treated as a
619 // field-value separator.
620 std::string headers =
621 "HTTP/1.1 200 OK\n"
622 "Date: Tue, 07 Aug 2007 23:10:55 GMT\n"
623 "Last-Modified: Wed, 01 Aug 2007 23:23:45 GMT\n";
624 HeadersToRaw(&headers);
625 auto parsed = base::MakeRefCounted<HttpResponseHeaders>(headers);
626
627 std::string value;
628 EXPECT_TRUE(parsed->EnumerateHeader(nullptr, "date", &value));
629 EXPECT_EQ("Tue, 07 Aug 2007 23:10:55 GMT", value);
630 EXPECT_TRUE(parsed->EnumerateHeader(nullptr, "last-modified", &value));
631 EXPECT_EQ("Wed, 01 Aug 2007 23:23:45 GMT", value);
632 }
633
TEST(HttpResponseHeadersTest,DefaultDateToGMT)634 TEST(HttpResponseHeadersTest, DefaultDateToGMT) {
635 // Verify we make the best interpretation when parsing dates that incorrectly
636 // do not end in "GMT" as RFC2616 requires.
637 std::string headers =
638 "HTTP/1.1 200 OK\n"
639 "Date: Tue, 07 Aug 2007 23:10:55\n"
640 "Last-Modified: Tue, 07 Aug 2007 19:10:55 EDT\n"
641 "Expires: Tue, 07 Aug 2007 23:10:55 UTC\n";
642 HeadersToRaw(&headers);
643 auto parsed = base::MakeRefCounted<HttpResponseHeaders>(headers);
644 base::Time expected_value;
645 ASSERT_TRUE(base::Time::FromString("Tue, 07 Aug 2007 23:10:55 GMT",
646 &expected_value));
647
648 base::Time value;
649 // When the timezone is missing, GMT is a good guess as its what RFC2616
650 // requires.
651 EXPECT_TRUE(parsed->GetDateValue(&value));
652 EXPECT_EQ(expected_value, value);
653 // If GMT is missing but an RFC822-conforming one is present, use that.
654 EXPECT_TRUE(parsed->GetLastModifiedValue(&value));
655 EXPECT_EQ(expected_value, value);
656 // If an unknown timezone is present, treat like a missing timezone and
657 // default to GMT. The only example of a web server not specifying "GMT"
658 // used "UTC" which is equivalent to GMT.
659 if (parsed->GetExpiresValue(&value))
660 EXPECT_EQ(expected_value, value);
661 }
662
TEST(HttpResponseHeadersTest,GetAgeValue10)663 TEST(HttpResponseHeadersTest, GetAgeValue10) {
664 std::string headers =
665 "HTTP/1.1 200 OK\n"
666 "Age: 10\n";
667 HeadersToRaw(&headers);
668 auto parsed = base::MakeRefCounted<HttpResponseHeaders>(headers);
669 base::TimeDelta age;
670 ASSERT_TRUE(parsed->GetAgeValue(&age));
671 EXPECT_EQ(10, age.InSeconds());
672 }
673
TEST(HttpResponseHeadersTest,GetAgeValue0)674 TEST(HttpResponseHeadersTest, GetAgeValue0) {
675 std::string headers =
676 "HTTP/1.1 200 OK\n"
677 "Age: 0\n";
678 HeadersToRaw(&headers);
679 auto parsed = base::MakeRefCounted<HttpResponseHeaders>(headers);
680 base::TimeDelta age;
681 ASSERT_TRUE(parsed->GetAgeValue(&age));
682 EXPECT_EQ(0, age.InSeconds());
683 }
684
TEST(HttpResponseHeadersTest,GetAgeValueBogus)685 TEST(HttpResponseHeadersTest, GetAgeValueBogus) {
686 std::string headers =
687 "HTTP/1.1 200 OK\n"
688 "Age: donkey\n";
689 HeadersToRaw(&headers);
690 auto parsed = base::MakeRefCounted<HttpResponseHeaders>(headers);
691 base::TimeDelta age;
692 ASSERT_FALSE(parsed->GetAgeValue(&age));
693 }
694
TEST(HttpResponseHeadersTest,GetAgeValueNegative)695 TEST(HttpResponseHeadersTest, GetAgeValueNegative) {
696 std::string headers =
697 "HTTP/1.1 200 OK\n"
698 "Age: -10\n";
699 HeadersToRaw(&headers);
700 auto parsed = base::MakeRefCounted<HttpResponseHeaders>(headers);
701 base::TimeDelta age;
702 ASSERT_FALSE(parsed->GetAgeValue(&age));
703 }
704
TEST(HttpResponseHeadersTest,GetAgeValueLeadingPlus)705 TEST(HttpResponseHeadersTest, GetAgeValueLeadingPlus) {
706 std::string headers =
707 "HTTP/1.1 200 OK\n"
708 "Age: +10\n";
709 HeadersToRaw(&headers);
710 auto parsed = base::MakeRefCounted<HttpResponseHeaders>(headers);
711 base::TimeDelta age;
712 ASSERT_FALSE(parsed->GetAgeValue(&age));
713 }
714
TEST(HttpResponseHeadersTest,GetAgeValueOverflow)715 TEST(HttpResponseHeadersTest, GetAgeValueOverflow) {
716 std::string headers =
717 "HTTP/1.1 200 OK\n"
718 "Age: 999999999999999999999999999999999999999999\n";
719 HeadersToRaw(&headers);
720 auto parsed = base::MakeRefCounted<HttpResponseHeaders>(headers);
721 base::TimeDelta age;
722 ASSERT_TRUE(parsed->GetAgeValue(&age));
723
724 // Should have saturated to 2^32 - 1.
725 EXPECT_EQ(static_cast<int64_t>(0xFFFFFFFFL), age.InSeconds());
726 }
727
728 struct ContentTypeTestData {
729 const std::string raw_headers;
730 const std::string mime_type;
731 const bool has_mimetype;
732 const std::string charset;
733 const bool has_charset;
734 const std::string all_content_type;
735 };
736
737 class ContentTypeTest
738 : public HttpResponseHeadersTest,
739 public ::testing::WithParamInterface<ContentTypeTestData> {
740 };
741
TEST_P(ContentTypeTest,GetMimeType)742 TEST_P(ContentTypeTest, GetMimeType) {
743 const ContentTypeTestData test = GetParam();
744
745 std::string headers(test.raw_headers);
746 HeadersToRaw(&headers);
747 auto parsed = base::MakeRefCounted<HttpResponseHeaders>(headers);
748
749 std::string value;
750 EXPECT_EQ(test.has_mimetype, parsed->GetMimeType(&value));
751 EXPECT_EQ(test.mime_type, value);
752 value.clear();
753 EXPECT_EQ(test.has_charset, parsed->GetCharset(&value));
754 EXPECT_EQ(test.charset, value);
755 EXPECT_TRUE(parsed->GetNormalizedHeader("content-type", &value));
756 EXPECT_EQ(test.all_content_type, value);
757 }
758
759 // clang-format off
760 const ContentTypeTestData mimetype_tests[] = {
761 { "HTTP/1.1 200 OK\n"
762 "Content-type: text/html\n",
763 "text/html", true,
764 "", false,
765 "text/html" },
766 // Multiple content-type headers should give us the last one.
767 { "HTTP/1.1 200 OK\n"
768 "Content-type: text/html\n"
769 "Content-type: text/html\n",
770 "text/html", true,
771 "", false,
772 "text/html, text/html" },
773 { "HTTP/1.1 200 OK\n"
774 "Content-type: text/plain\n"
775 "Content-type: text/html\n"
776 "Content-type: text/plain\n"
777 "Content-type: text/html\n",
778 "text/html", true,
779 "", false,
780 "text/plain, text/html, text/plain, text/html" },
781 // Test charset parsing.
782 { "HTTP/1.1 200 OK\n"
783 "Content-type: text/html\n"
784 "Content-type: text/html; charset=ISO-8859-1\n",
785 "text/html", true,
786 "iso-8859-1", true,
787 "text/html, text/html; charset=ISO-8859-1" },
788 // Test charset in double quotes.
789 { "HTTP/1.1 200 OK\n"
790 "Content-type: text/html\n"
791 "Content-type: text/html; charset=\"ISO-8859-1\"\n",
792 "text/html", true,
793 "iso-8859-1", true,
794 "text/html, text/html; charset=\"ISO-8859-1\"" },
795 // If there are multiple matching content-type headers, we carry
796 // over the charset value.
797 { "HTTP/1.1 200 OK\n"
798 "Content-type: text/html;charset=utf-8\n"
799 "Content-type: text/html\n",
800 "text/html", true,
801 "utf-8", true,
802 "text/html;charset=utf-8, text/html" },
803 // Regression test for https://crbug.com/772350:
804 // Single quotes are not delimiters but must be treated as part of charset.
805 { "HTTP/1.1 200 OK\n"
806 "Content-type: text/html;charset='utf-8'\n"
807 "Content-type: text/html\n",
808 "text/html", true,
809 "'utf-8'", true,
810 "text/html;charset='utf-8', text/html" },
811 // First charset wins if matching content-type.
812 { "HTTP/1.1 200 OK\n"
813 "Content-type: text/html;charset=utf-8\n"
814 "Content-type: text/html;charset=iso-8859-1\n",
815 "text/html", true,
816 "iso-8859-1", true,
817 "text/html;charset=utf-8, text/html;charset=iso-8859-1" },
818 // Charset is ignored if the content types change.
819 { "HTTP/1.1 200 OK\n"
820 "Content-type: text/plain;charset=utf-8\n"
821 "Content-type: text/html\n",
822 "text/html", true,
823 "", false,
824 "text/plain;charset=utf-8, text/html" },
825 // Empty content-type.
826 { "HTTP/1.1 200 OK\n"
827 "Content-type: \n",
828 "", false,
829 "", false,
830 "" },
831 // Emtpy charset.
832 { "HTTP/1.1 200 OK\n"
833 "Content-type: text/html;charset=\n",
834 "text/html", true,
835 "", false,
836 "text/html;charset=" },
837 // Multiple charsets, first one wins.
838 { "HTTP/1.1 200 OK\n"
839 "Content-type: text/html;charset=utf-8; charset=iso-8859-1\n",
840 "text/html", true,
841 "utf-8", true,
842 "text/html;charset=utf-8; charset=iso-8859-1" },
843 // Multiple params.
844 { "HTTP/1.1 200 OK\n"
845 "Content-type: text/html; foo=utf-8; charset=iso-8859-1\n",
846 "text/html", true,
847 "iso-8859-1", true,
848 "text/html; foo=utf-8; charset=iso-8859-1" },
849 { "HTTP/1.1 200 OK\n"
850 "Content-type: text/html ; charset=utf-8 ; bar=iso-8859-1\n",
851 "text/html", true,
852 "utf-8", true,
853 "text/html ; charset=utf-8 ; bar=iso-8859-1" },
854 // Comma embeded in quotes.
855 { "HTTP/1.1 200 OK\n"
856 "Content-type: text/html ; charset=\"utf-8,text/plain\" ;\n",
857 "text/html", true,
858 "utf-8,text/plain", true,
859 "text/html ; charset=\"utf-8,text/plain\" ;" },
860 // Charset with leading spaces.
861 { "HTTP/1.1 200 OK\n"
862 "Content-type: text/html ; charset= \"utf-8\" ;\n",
863 "text/html", true,
864 "utf-8", true,
865 "text/html ; charset= \"utf-8\" ;" },
866 // Media type comments in mime-type.
867 { "HTTP/1.1 200 OK\n"
868 "Content-type: text/html (html)\n",
869 "text/html", true,
870 "", false,
871 "text/html (html)" },
872 // Incomplete charset= param.
873 { "HTTP/1.1 200 OK\n"
874 "Content-type: text/html; char=\n",
875 "text/html", true,
876 "", false,
877 "text/html; char=" },
878 // Invalid media type: no slash.
879 { "HTTP/1.1 200 OK\n"
880 "Content-type: texthtml\n",
881 "", false,
882 "", false,
883 "texthtml" },
884 // Invalid media type: "*/*".
885 { "HTTP/1.1 200 OK\n"
886 "Content-type: */*\n",
887 "", false,
888 "", false,
889 "*/*" },
890 };
891 // clang-format on
892
893 INSTANTIATE_TEST_SUITE_P(HttpResponseHeaders,
894 ContentTypeTest,
895 testing::ValuesIn(mimetype_tests));
896
897 struct RequiresValidationTestData {
898 const char* headers;
899 ValidationType validation_type;
900 };
901
902 class RequiresValidationTest
903 : public HttpResponseHeadersTest,
904 public ::testing::WithParamInterface<RequiresValidationTestData> {
905 };
906
TEST_P(RequiresValidationTest,RequiresValidation)907 TEST_P(RequiresValidationTest, RequiresValidation) {
908 const RequiresValidationTestData test = GetParam();
909
910 base::Time request_time, response_time, current_time;
911 ASSERT_TRUE(
912 base::Time::FromString("Wed, 28 Nov 2007 00:40:09 GMT", &request_time));
913 ASSERT_TRUE(
914 base::Time::FromString("Wed, 28 Nov 2007 00:40:12 GMT", &response_time));
915 ASSERT_TRUE(
916 base::Time::FromString("Wed, 28 Nov 2007 00:45:20 GMT", ¤t_time));
917
918 std::string headers(test.headers);
919 HeadersToRaw(&headers);
920 auto parsed = base::MakeRefCounted<HttpResponseHeaders>(headers);
921
922 ValidationType validation_type =
923 parsed->RequiresValidation(request_time, response_time, current_time);
924 EXPECT_EQ(test.validation_type, validation_type);
925 }
926
927 const struct RequiresValidationTestData requires_validation_tests[] = {
928 // No expiry info: expires immediately.
929 {"HTTP/1.1 200 OK\n"
930 "\n",
931 VALIDATION_SYNCHRONOUS},
932 // No expiry info: expires immediately.
933 {"HTTP/1.1 200 OK\n"
934 "\n",
935 VALIDATION_SYNCHRONOUS},
936 // Valid for a little while.
937 {"HTTP/1.1 200 OK\n"
938 "cache-control: max-age=10000\n"
939 "\n",
940 VALIDATION_NONE},
941 // Expires in the future.
942 {"HTTP/1.1 200 OK\n"
943 "date: Wed, 28 Nov 2007 00:40:11 GMT\n"
944 "expires: Wed, 28 Nov 2007 01:00:00 GMT\n"
945 "\n",
946 VALIDATION_NONE},
947 // Already expired.
948 {"HTTP/1.1 200 OK\n"
949 "date: Wed, 28 Nov 2007 00:40:11 GMT\n"
950 "expires: Wed, 28 Nov 2007 00:00:00 GMT\n"
951 "\n",
952 VALIDATION_SYNCHRONOUS},
953 // Max-age trumps expires.
954 {"HTTP/1.1 200 OK\n"
955 "date: Wed, 28 Nov 2007 00:40:11 GMT\n"
956 "expires: Wed, 28 Nov 2007 00:00:00 GMT\n"
957 "cache-control: max-age=10000\n"
958 "\n",
959 VALIDATION_NONE},
960 // Last-modified heuristic: modified a while ago.
961 {"HTTP/1.1 200 OK\n"
962 "date: Wed, 28 Nov 2007 00:40:11 GMT\n"
963 "last-modified: Wed, 27 Nov 2007 08:00:00 GMT\n"
964 "\n",
965 VALIDATION_NONE},
966 {"HTTP/1.1 203 Non-Authoritative Information\n"
967 "date: Wed, 28 Nov 2007 00:40:11 GMT\n"
968 "last-modified: Wed, 27 Nov 2007 08:00:00 GMT\n"
969 "\n",
970 VALIDATION_NONE},
971 {"HTTP/1.1 206 Partial Content\n"
972 "date: Wed, 28 Nov 2007 00:40:11 GMT\n"
973 "last-modified: Wed, 27 Nov 2007 08:00:00 GMT\n"
974 "\n",
975 VALIDATION_NONE},
976 // Last-modified heuristic: modified a while ago and it's VALIDATION_NONE
977 // (fresh) like above but VALIDATION_SYNCHRONOUS if expires header value is
978 // "0".
979 {"HTTP/1.1 200 OK\n"
980 "date: Wed, 28 Nov 2007 00:40:11 GMT\n"
981 "last-modified: Tue, 27 Nov 2007 08:00:00 GMT\n"
982 "expires: 0\n"
983 "\n",
984 VALIDATION_SYNCHRONOUS},
985 {"HTTP/1.1 200 OK\n"
986 "date: Wed, 28 Nov 2007 00:40:11 GMT\n"
987 "last-modified: Tue, 27 Nov 2007 08:00:00 GMT\n"
988 "expires: 0 \n"
989 "\n",
990 VALIDATION_SYNCHRONOUS},
991 // The cache is fresh if the expires header value is an invalid date string
992 // except for "0"
993 {"HTTP/1.1 200 OK\n"
994 "date: Wed, 28 Nov 2007 00:40:11 GMT\n"
995 "last-modified: Tue, 27 Nov 2007 08:00:00 GMT\n"
996 "expires: banana \n"
997 "\n",
998 VALIDATION_NONE},
999 // Last-modified heuristic: modified recently.
1000 {"HTTP/1.1 200 OK\n"
1001 "date: Wed, 28 Nov 2007 00:40:11 GMT\n"
1002 "last-modified: Wed, 28 Nov 2007 00:40:10 GMT\n"
1003 "\n",
1004 VALIDATION_SYNCHRONOUS},
1005 {"HTTP/1.1 203 Non-Authoritative Information\n"
1006 "date: Wed, 28 Nov 2007 00:40:11 GMT\n"
1007 "last-modified: Wed, 28 Nov 2007 00:40:10 GMT\n"
1008 "\n",
1009 VALIDATION_SYNCHRONOUS},
1010 {"HTTP/1.1 206 Partial Content\n"
1011 "date: Wed, 28 Nov 2007 00:40:11 GMT\n"
1012 "last-modified: Wed, 28 Nov 2007 00:40:10 GMT\n"
1013 "\n",
1014 VALIDATION_SYNCHRONOUS},
1015 // Cached permanent redirect.
1016 {"HTTP/1.1 301 Moved Permanently\n"
1017 "\n",
1018 VALIDATION_NONE},
1019 // Another cached permanent redirect.
1020 {"HTTP/1.1 308 Permanent Redirect\n"
1021 "\n",
1022 VALIDATION_NONE},
1023 // Cached redirect: not reusable even though by default it would be.
1024 {"HTTP/1.1 300 Multiple Choices\n"
1025 "Cache-Control: no-cache\n"
1026 "\n",
1027 VALIDATION_SYNCHRONOUS},
1028 // Cached forever by default.
1029 {"HTTP/1.1 410 Gone\n"
1030 "\n",
1031 VALIDATION_NONE},
1032 // Cached temporary redirect: not reusable.
1033 {"HTTP/1.1 302 Found\n"
1034 "\n",
1035 VALIDATION_SYNCHRONOUS},
1036 // Cached temporary redirect: reusable.
1037 {"HTTP/1.1 302 Found\n"
1038 "cache-control: max-age=10000\n"
1039 "\n",
1040 VALIDATION_NONE},
1041 // Cache-control: max-age=N overrides expires: date in the past.
1042 {"HTTP/1.1 200 OK\n"
1043 "date: Wed, 28 Nov 2007 00:40:11 GMT\n"
1044 "expires: Wed, 28 Nov 2007 00:20:11 GMT\n"
1045 "cache-control: max-age=10000\n"
1046 "\n",
1047 VALIDATION_NONE},
1048 // Cache-control: no-store overrides expires: in the future.
1049 {"HTTP/1.1 200 OK\n"
1050 "date: Wed, 28 Nov 2007 00:40:11 GMT\n"
1051 "expires: Wed, 29 Nov 2007 00:40:11 GMT\n"
1052 "cache-control: no-store,private,no-cache=\"foo\"\n"
1053 "\n",
1054 VALIDATION_SYNCHRONOUS},
1055 // Pragma: no-cache overrides last-modified heuristic.
1056 {"HTTP/1.1 200 OK\n"
1057 "date: Wed, 28 Nov 2007 00:40:11 GMT\n"
1058 "last-modified: Wed, 27 Nov 2007 08:00:00 GMT\n"
1059 "pragma: no-cache\n"
1060 "\n",
1061 VALIDATION_SYNCHRONOUS},
1062 // max-age has expired, needs synchronous revalidation
1063 {"HTTP/1.1 200 OK\n"
1064 "date: Wed, 28 Nov 2007 00:40:11 GMT\n"
1065 "cache-control: max-age=300\n"
1066 "\n",
1067 VALIDATION_SYNCHRONOUS},
1068 // max-age has expired, stale-while-revalidate has not, eligible for
1069 // asynchronous revalidation
1070 {"HTTP/1.1 200 OK\n"
1071 "date: Wed, 28 Nov 2007 00:40:11 GMT\n"
1072 "cache-control: max-age=300, stale-while-revalidate=3600\n"
1073 "\n",
1074 VALIDATION_ASYNCHRONOUS},
1075 // max-age and stale-while-revalidate have expired, needs synchronous
1076 // revalidation
1077 {"HTTP/1.1 200 OK\n"
1078 "date: Wed, 28 Nov 2007 00:40:11 GMT\n"
1079 "cache-control: max-age=300, stale-while-revalidate=5\n"
1080 "\n",
1081 VALIDATION_SYNCHRONOUS},
1082 // max-age is 0, stale-while-revalidate is large enough to permit
1083 // asynchronous revalidation
1084 {"HTTP/1.1 200 OK\n"
1085 "date: Wed, 28 Nov 2007 00:40:11 GMT\n"
1086 "cache-control: max-age=0, stale-while-revalidate=360\n"
1087 "\n",
1088 VALIDATION_ASYNCHRONOUS},
1089 // stale-while-revalidate must not override no-cache or similar directives.
1090 {"HTTP/1.1 200 OK\n"
1091 "date: Wed, 28 Nov 2007 00:40:11 GMT\n"
1092 "cache-control: no-cache, stale-while-revalidate=360\n"
1093 "\n",
1094 VALIDATION_SYNCHRONOUS},
1095 // max-age has not expired, so no revalidation is needed.
1096 {"HTTP/1.1 200 OK\n"
1097 "date: Wed, 28 Nov 2007 00:40:11 GMT\n"
1098 "cache-control: max-age=3600, stale-while-revalidate=3600\n"
1099 "\n",
1100 VALIDATION_NONE},
1101 // must-revalidate overrides stale-while-revalidate, so synchronous
1102 // validation
1103 // is needed.
1104 {"HTTP/1.1 200 OK\n"
1105 "date: Wed, 28 Nov 2007 00:40:11 GMT\n"
1106 "cache-control: must-revalidate, max-age=300, "
1107 "stale-while-revalidate=3600\n"
1108 "\n",
1109 VALIDATION_SYNCHRONOUS},
1110
1111 // TODO(darin): Add many many more tests here.
1112 };
1113
1114 INSTANTIATE_TEST_SUITE_P(HttpResponseHeaders,
1115 RequiresValidationTest,
1116 testing::ValuesIn(requires_validation_tests));
1117
1118 struct UpdateTestData {
1119 const char* orig_headers;
1120 const char* new_headers;
1121 const char* expected_headers;
1122 };
1123
1124 class UpdateTest
1125 : public HttpResponseHeadersTest,
1126 public ::testing::WithParamInterface<UpdateTestData> {
1127 };
1128
TEST_P(UpdateTest,Update)1129 TEST_P(UpdateTest, Update) {
1130 const UpdateTestData test = GetParam();
1131
1132 std::string orig_headers(test.orig_headers);
1133 HeadersToRaw(&orig_headers);
1134 auto parsed = base::MakeRefCounted<HttpResponseHeaders>(orig_headers);
1135
1136 std::string new_headers(test.new_headers);
1137 HeadersToRaw(&new_headers);
1138 auto new_parsed = base::MakeRefCounted<HttpResponseHeaders>(new_headers);
1139
1140 parsed->Update(*new_parsed.get());
1141
1142 EXPECT_EQ(std::string(test.expected_headers), ToSimpleString(parsed));
1143 }
1144
1145 const UpdateTestData update_tests[] = {
1146 {"HTTP/1.1 200 OK\n",
1147
1148 "HTTP/1/1 304 Not Modified\n"
1149 "connection: keep-alive\n"
1150 "Cache-control: max-age=10000\n",
1151
1152 "HTTP/1.1 200 OK\n"
1153 "Cache-control: max-age=10000\n"},
1154 {"HTTP/1.1 200 OK\n"
1155 "Foo: 1\n"
1156 "Cache-control: private\n",
1157
1158 "HTTP/1/1 304 Not Modified\n"
1159 "connection: keep-alive\n"
1160 "Cache-control: max-age=10000\n",
1161
1162 "HTTP/1.1 200 OK\n"
1163 "Cache-control: max-age=10000\n"
1164 "Foo: 1\n"},
1165 {"HTTP/1.1 200 OK\n"
1166 "Foo: 1\n"
1167 "Cache-control: private\n",
1168
1169 "HTTP/1/1 304 Not Modified\n"
1170 "connection: keep-alive\n"
1171 "Cache-CONTROL: max-age=10000\n",
1172
1173 "HTTP/1.1 200 OK\n"
1174 "Cache-CONTROL: max-age=10000\n"
1175 "Foo: 1\n"},
1176 {"HTTP/1.1 200 OK\n"
1177 "Content-Length: 450\n",
1178
1179 "HTTP/1/1 304 Not Modified\n"
1180 "connection: keep-alive\n"
1181 "Cache-control: max-age=10001 \n",
1182
1183 "HTTP/1.1 200 OK\n"
1184 "Cache-control: max-age=10001\n"
1185 "Content-Length: 450\n"},
1186 {
1187 "HTTP/1.1 200 OK\n"
1188 "X-Frame-Options: DENY\n",
1189
1190 "HTTP/1/1 304 Not Modified\n"
1191 "X-Frame-Options: ALLOW\n",
1192
1193 "HTTP/1.1 200 OK\n"
1194 "X-Frame-Options: DENY\n",
1195 },
1196 {
1197 "HTTP/1.1 200 OK\n"
1198 "X-WebKit-CSP: default-src 'none'\n",
1199
1200 "HTTP/1/1 304 Not Modified\n"
1201 "X-WebKit-CSP: default-src *\n",
1202
1203 "HTTP/1.1 200 OK\n"
1204 "X-WebKit-CSP: default-src 'none'\n",
1205 },
1206 {
1207 "HTTP/1.1 200 OK\n"
1208 "X-XSS-Protection: 1\n",
1209
1210 "HTTP/1/1 304 Not Modified\n"
1211 "X-XSS-Protection: 0\n",
1212
1213 "HTTP/1.1 200 OK\n"
1214 "X-XSS-Protection: 1\n",
1215 },
1216 {"HTTP/1.1 200 OK\n",
1217
1218 "HTTP/1/1 304 Not Modified\n"
1219 "X-Content-Type-Options: nosniff\n",
1220
1221 "HTTP/1.1 200 OK\n"},
1222 {"HTTP/1.1 200 OK\n"
1223 "Content-Encoding: identity\n"
1224 "Content-Length: 100\n"
1225 "Content-Type: text/html\n"
1226 "Content-Security-Policy: default-src 'none'\n",
1227
1228 "HTTP/1/1 304 Not Modified\n"
1229 "Content-Encoding: gzip\n"
1230 "Content-Length: 200\n"
1231 "Content-Type: text/xml\n"
1232 "Content-Security-Policy: default-src 'self'\n",
1233
1234 "HTTP/1.1 200 OK\n"
1235 "Content-Security-Policy: default-src 'self'\n"
1236 "Content-Encoding: identity\n"
1237 "Content-Length: 100\n"
1238 "Content-Type: text/html\n"},
1239 {"HTTP/1.1 200 OK\n"
1240 "Content-Location: /example_page.html\n",
1241
1242 "HTTP/1/1 304 Not Modified\n"
1243 "Content-Location: /not_example_page.html\n",
1244
1245 "HTTP/1.1 200 OK\n"
1246 "Content-Location: /example_page.html\n"},
1247 };
1248
1249 INSTANTIATE_TEST_SUITE_P(HttpResponseHeaders,
1250 UpdateTest,
1251 testing::ValuesIn(update_tests));
1252
1253 struct EnumerateHeaderTestData {
1254 const char* headers;
1255 const char* expected_lines;
1256 };
1257
1258 class EnumerateHeaderLinesTest
1259 : public HttpResponseHeadersTest,
1260 public ::testing::WithParamInterface<EnumerateHeaderTestData> {
1261 };
1262
TEST_P(EnumerateHeaderLinesTest,EnumerateHeaderLines)1263 TEST_P(EnumerateHeaderLinesTest, EnumerateHeaderLines) {
1264 const EnumerateHeaderTestData test = GetParam();
1265
1266 std::string headers(test.headers);
1267 HeadersToRaw(&headers);
1268 auto parsed = base::MakeRefCounted<HttpResponseHeaders>(headers);
1269
1270 std::string name, value, lines;
1271
1272 size_t iter = 0;
1273 while (parsed->EnumerateHeaderLines(&iter, &name, &value)) {
1274 lines.append(name);
1275 lines.append(": ");
1276 lines.append(value);
1277 lines.append("\n");
1278 }
1279
1280 EXPECT_EQ(std::string(test.expected_lines), lines);
1281 }
1282
1283 const EnumerateHeaderTestData enumerate_header_tests[] = {
1284 {"HTTP/1.1 200 OK\n",
1285
1286 ""},
1287 {"HTTP/1.1 200 OK\n"
1288 "Foo: 1\n",
1289
1290 "Foo: 1\n"},
1291 {"HTTP/1.1 200 OK\n"
1292 "Foo: 1\n"
1293 "Bar: 2\n"
1294 "Foo: 3\n",
1295
1296 "Foo: 1\nBar: 2\nFoo: 3\n"},
1297 {"HTTP/1.1 200 OK\n"
1298 "Foo: 1, 2, 3\n",
1299
1300 "Foo: 1, 2, 3\n"},
1301 {"HTTP/1.1 200 OK\n"
1302 "Foo: ,, 1,, 2, 3,, \n",
1303
1304 "Foo: ,, 1,, 2, 3,,\n"},
1305 };
1306
1307 INSTANTIATE_TEST_SUITE_P(HttpResponseHeaders,
1308 EnumerateHeaderLinesTest,
1309 testing::ValuesIn(enumerate_header_tests));
1310
1311 struct IsRedirectTestData {
1312 const char* headers;
1313 const char* location;
1314 bool is_redirect;
1315 };
1316
1317 class IsRedirectTest
1318 : public HttpResponseHeadersTest,
1319 public ::testing::WithParamInterface<IsRedirectTestData> {
1320 };
1321
TEST_P(IsRedirectTest,IsRedirect)1322 TEST_P(IsRedirectTest, IsRedirect) {
1323 const IsRedirectTestData test = GetParam();
1324
1325 std::string headers(test.headers);
1326 HeadersToRaw(&headers);
1327 auto parsed = base::MakeRefCounted<HttpResponseHeaders>(headers);
1328
1329 std::string location;
1330 EXPECT_EQ(parsed->IsRedirect(&location), test.is_redirect);
1331 EXPECT_EQ(location, test.location);
1332 }
1333
1334 const IsRedirectTestData is_redirect_tests[] = {
1335 { "HTTP/1.1 200 OK\n",
1336 "",
1337 false
1338 },
1339 { "HTTP/1.1 301 Moved\n"
1340 "Location: http://foopy/\n",
1341 "http://foopy/",
1342 true
1343 },
1344 { "HTTP/1.1 301 Moved\n"
1345 "Location: \t \n",
1346 "",
1347 false
1348 },
1349 // We use the first location header as the target of the redirect.
1350 { "HTTP/1.1 301 Moved\n"
1351 "Location: http://foo/\n"
1352 "Location: http://bar/\n",
1353 "http://foo/",
1354 true
1355 },
1356 // We use the first _valid_ location header as the target of the redirect.
1357 { "HTTP/1.1 301 Moved\n"
1358 "Location: \n"
1359 "Location: http://bar/\n",
1360 "http://bar/",
1361 true
1362 },
1363 // Bug 1050541 (location header with an unescaped comma).
1364 { "HTTP/1.1 301 Moved\n"
1365 "Location: http://foo/bar,baz.html\n",
1366 "http://foo/bar,baz.html",
1367 true
1368 },
1369 // Bug 1224617 (location header with non-ASCII bytes).
1370 { "HTTP/1.1 301 Moved\n"
1371 "Location: http://foo/bar?key=\xE4\xF6\xFC\n",
1372 "http://foo/bar?key=%E4%F6%FC",
1373 true
1374 },
1375 // Shift_JIS, Big5, and GBK contain multibyte characters with the trailing
1376 // byte falling in the ASCII range.
1377 { "HTTP/1.1 301 Moved\n"
1378 "Location: http://foo/bar?key=\x81\x5E\xD8\xBF\n",
1379 "http://foo/bar?key=%81^%D8%BF",
1380 true
1381 },
1382 { "HTTP/1.1 301 Moved\n"
1383 "Location: http://foo/bar?key=\x82\x40\xBD\xC4\n",
1384 "http://foo/bar?key=%82@%BD%C4",
1385 true
1386 },
1387 { "HTTP/1.1 301 Moved\n"
1388 "Location: http://foo/bar?key=\x83\x5C\x82\x5D\xCB\xD7\n",
1389 "http://foo/bar?key=%83\\%82]%CB%D7",
1390 true
1391 },
1392 };
1393
1394 INSTANTIATE_TEST_SUITE_P(HttpResponseHeaders,
1395 IsRedirectTest,
1396 testing::ValuesIn(is_redirect_tests));
1397
1398 struct ContentLengthTestData {
1399 const char* headers;
1400 int64_t expected_len;
1401 };
1402
1403 class GetContentLengthTest
1404 : public HttpResponseHeadersTest,
1405 public ::testing::WithParamInterface<ContentLengthTestData> {
1406 };
1407
TEST_P(GetContentLengthTest,GetContentLength)1408 TEST_P(GetContentLengthTest, GetContentLength) {
1409 const ContentLengthTestData test = GetParam();
1410
1411 std::string headers(test.headers);
1412 HeadersToRaw(&headers);
1413 auto parsed = base::MakeRefCounted<HttpResponseHeaders>(headers);
1414
1415 EXPECT_EQ(test.expected_len, parsed->GetContentLength());
1416 }
1417
1418 const ContentLengthTestData content_length_tests[] = {
1419 {"HTTP/1.1 200 OK\n", -1},
1420 {"HTTP/1.1 200 OK\n"
1421 "Content-Length: 10\n",
1422 10},
1423 {"HTTP/1.1 200 OK\n"
1424 "Content-Length: \n",
1425 -1},
1426 {"HTTP/1.1 200 OK\n"
1427 "Content-Length: abc\n",
1428 -1},
1429 {"HTTP/1.1 200 OK\n"
1430 "Content-Length: -10\n",
1431 -1},
1432 {"HTTP/1.1 200 OK\n"
1433 "Content-Length: +10\n",
1434 -1},
1435 {"HTTP/1.1 200 OK\n"
1436 "Content-Length: 23xb5\n",
1437 -1},
1438 {"HTTP/1.1 200 OK\n"
1439 "Content-Length: 0xA\n",
1440 -1},
1441 {"HTTP/1.1 200 OK\n"
1442 "Content-Length: 010\n",
1443 10},
1444 // Content-Length too big, will overflow an int64_t.
1445 {"HTTP/1.1 200 OK\n"
1446 "Content-Length: 40000000000000000000\n",
1447 -1},
1448 {"HTTP/1.1 200 OK\n"
1449 "Content-Length: 10\n",
1450 10},
1451 {"HTTP/1.1 200 OK\n"
1452 "Content-Length: 10 \n",
1453 10},
1454 {"HTTP/1.1 200 OK\n"
1455 "Content-Length: \t10\n",
1456 10},
1457 {"HTTP/1.1 200 OK\n"
1458 "Content-Length: \v10\n",
1459 -1},
1460 {"HTTP/1.1 200 OK\n"
1461 "Content-Length: \f10\n",
1462 -1},
1463 {"HTTP/1.1 200 OK\n"
1464 "cOnTeNt-LENgth: 33\n",
1465 33},
1466 {"HTTP/1.1 200 OK\n"
1467 "Content-Length: 34\r\n",
1468 -1},
1469 };
1470
1471 INSTANTIATE_TEST_SUITE_P(HttpResponseHeaders,
1472 GetContentLengthTest,
1473 testing::ValuesIn(content_length_tests));
1474
1475 struct ContentRangeTestData {
1476 const char* headers;
1477 bool expected_return_value;
1478 int64_t expected_first_byte_position;
1479 int64_t expected_last_byte_position;
1480 int64_t expected_instance_size;
1481 };
1482
1483 class ContentRangeTest
1484 : public HttpResponseHeadersTest,
1485 public ::testing::WithParamInterface<ContentRangeTestData> {
1486 };
1487
TEST_P(ContentRangeTest,GetContentRangeFor206)1488 TEST_P(ContentRangeTest, GetContentRangeFor206) {
1489 const ContentRangeTestData test = GetParam();
1490
1491 std::string headers(test.headers);
1492 HeadersToRaw(&headers);
1493 auto parsed = base::MakeRefCounted<HttpResponseHeaders>(headers);
1494
1495 int64_t first_byte_position;
1496 int64_t last_byte_position;
1497 int64_t instance_size;
1498 bool return_value = parsed->GetContentRangeFor206(
1499 &first_byte_position, &last_byte_position, &instance_size);
1500 EXPECT_EQ(test.expected_return_value, return_value);
1501 EXPECT_EQ(test.expected_first_byte_position, first_byte_position);
1502 EXPECT_EQ(test.expected_last_byte_position, last_byte_position);
1503 EXPECT_EQ(test.expected_instance_size, instance_size);
1504 }
1505
1506 const ContentRangeTestData content_range_tests[] = {
1507 {"HTTP/1.1 206 Partial Content", false, -1, -1, -1},
1508 {"HTTP/1.1 206 Partial Content\n"
1509 "Content-Range:",
1510 false, -1, -1, -1},
1511 {"HTTP/1.1 206 Partial Content\n"
1512 "Content-Range: bytes 0-50/51",
1513 true, 0, 50, 51},
1514 {"HTTP/1.1 206 Partial Content\n"
1515 "Content-Range: bytes 50-0/51",
1516 false, -1, -1, -1},
1517 {"HTTP/1.1 416 Requested range not satisfiable\n"
1518 "Content-Range: bytes */*",
1519 false, -1, -1, -1},
1520 {"HTTP/1.1 206 Partial Content\n"
1521 "Content-Range: bytes 0-50/*",
1522 false, -1, -1, -1},
1523 };
1524
1525 INSTANTIATE_TEST_SUITE_P(HttpResponseHeaders,
1526 ContentRangeTest,
1527 testing::ValuesIn(content_range_tests));
1528
1529 struct KeepAliveTestData {
1530 const char* headers;
1531 bool expected_keep_alive;
1532 };
1533
1534 // Enable GTest to print KeepAliveTestData in an intelligible way if the test
1535 // fails.
PrintTo(const KeepAliveTestData & keep_alive_test_data,std::ostream * os)1536 void PrintTo(const KeepAliveTestData& keep_alive_test_data,
1537 std::ostream* os) {
1538 *os << "{\"" << keep_alive_test_data.headers << "\", " << std::boolalpha
1539 << keep_alive_test_data.expected_keep_alive << "}";
1540 }
1541
1542 class IsKeepAliveTest
1543 : public HttpResponseHeadersTest,
1544 public ::testing::WithParamInterface<KeepAliveTestData> {
1545 };
1546
TEST_P(IsKeepAliveTest,IsKeepAlive)1547 TEST_P(IsKeepAliveTest, IsKeepAlive) {
1548 const KeepAliveTestData test = GetParam();
1549
1550 std::string headers(test.headers);
1551 HeadersToRaw(&headers);
1552 auto parsed = base::MakeRefCounted<HttpResponseHeaders>(headers);
1553
1554 EXPECT_EQ(test.expected_keep_alive, parsed->IsKeepAlive());
1555 }
1556
1557 const KeepAliveTestData keepalive_tests[] = {
1558 // The status line fabricated by HttpNetworkTransaction for a 0.9 response.
1559 // Treated as 0.9.
1560 { "HTTP/0.9 200 OK",
1561 false
1562 },
1563 // This could come from a broken server. Treated as 1.0 because it has a
1564 // header.
1565 { "HTTP/0.9 200 OK\n"
1566 "connection: keep-alive\n",
1567 true
1568 },
1569 { "HTTP/1.1 200 OK\n",
1570 true
1571 },
1572 { "HTTP/1.0 200 OK\n",
1573 false
1574 },
1575 { "HTTP/1.0 200 OK\n"
1576 "connection: close\n",
1577 false
1578 },
1579 { "HTTP/1.0 200 OK\n"
1580 "connection: keep-alive\n",
1581 true
1582 },
1583 { "HTTP/1.0 200 OK\n"
1584 "connection: kEeP-AliVe\n",
1585 true
1586 },
1587 { "HTTP/1.0 200 OK\n"
1588 "connection: keep-aliveX\n",
1589 false
1590 },
1591 { "HTTP/1.1 200 OK\n"
1592 "connection: close\n",
1593 false
1594 },
1595 { "HTTP/1.1 200 OK\n"
1596 "connection: keep-alive\n",
1597 true
1598 },
1599 { "HTTP/1.0 200 OK\n"
1600 "proxy-connection: close\n",
1601 false
1602 },
1603 { "HTTP/1.0 200 OK\n"
1604 "proxy-connection: keep-alive\n",
1605 true
1606 },
1607 { "HTTP/1.1 200 OK\n"
1608 "proxy-connection: close\n",
1609 false
1610 },
1611 { "HTTP/1.1 200 OK\n"
1612 "proxy-connection: keep-alive\n",
1613 true
1614 },
1615 { "HTTP/1.1 200 OK\n"
1616 "Connection: Upgrade, close\n",
1617 false
1618 },
1619 { "HTTP/1.1 200 OK\n"
1620 "Connection: Upgrade, keep-alive\n",
1621 true
1622 },
1623 { "HTTP/1.1 200 OK\n"
1624 "Connection: Upgrade\n"
1625 "Connection: close\n",
1626 false
1627 },
1628 { "HTTP/1.1 200 OK\n"
1629 "Connection: Upgrade\n"
1630 "Connection: keep-alive\n",
1631 true
1632 },
1633 { "HTTP/1.1 200 OK\n"
1634 "Connection: close, Upgrade\n",
1635 false
1636 },
1637 { "HTTP/1.1 200 OK\n"
1638 "Connection: keep-alive, Upgrade\n",
1639 true
1640 },
1641 { "HTTP/1.1 200 OK\n"
1642 "Connection: Upgrade\n"
1643 "Proxy-Connection: close\n",
1644 false
1645 },
1646 { "HTTP/1.1 200 OK\n"
1647 "Connection: Upgrade\n"
1648 "Proxy-Connection: keep-alive\n",
1649 true
1650 },
1651 // In situations where the response headers conflict with themselves, use the
1652 // first one for backwards-compatibility.
1653 { "HTTP/1.1 200 OK\n"
1654 "Connection: close\n"
1655 "Connection: keep-alive\n",
1656 false
1657 },
1658 { "HTTP/1.1 200 OK\n"
1659 "Connection: keep-alive\n"
1660 "Connection: close\n",
1661 true
1662 },
1663 { "HTTP/1.0 200 OK\n"
1664 "Connection: close\n"
1665 "Connection: keep-alive\n",
1666 false
1667 },
1668 { "HTTP/1.0 200 OK\n"
1669 "Connection: keep-alive\n"
1670 "Connection: close\n",
1671 true
1672 },
1673 // Ignore the Proxy-Connection header if at all possible.
1674 { "HTTP/1.0 200 OK\n"
1675 "Proxy-Connection: keep-alive\n"
1676 "Connection: close\n",
1677 false
1678 },
1679 { "HTTP/1.1 200 OK\n"
1680 "Proxy-Connection: close\n"
1681 "Connection: keep-alive\n",
1682 true
1683 },
1684 // Older versions of Chrome would have ignored Proxy-Connection in this case,
1685 // but it doesn't seem safe.
1686 { "HTTP/1.1 200 OK\n"
1687 "Proxy-Connection: close\n"
1688 "Connection: Transfer-Encoding\n",
1689 false
1690 },
1691 };
1692
1693 INSTANTIATE_TEST_SUITE_P(HttpResponseHeaders,
1694 IsKeepAliveTest,
1695 testing::ValuesIn(keepalive_tests));
1696
1697 struct HasStrongValidatorsTestData {
1698 const char* headers;
1699 bool expected_result;
1700 };
1701
1702 class HasStrongValidatorsTest
1703 : public HttpResponseHeadersTest,
1704 public ::testing::WithParamInterface<HasStrongValidatorsTestData> {
1705 };
1706
TEST_P(HasStrongValidatorsTest,HasStrongValidators)1707 TEST_P(HasStrongValidatorsTest, HasStrongValidators) {
1708 const HasStrongValidatorsTestData test = GetParam();
1709
1710 std::string headers(test.headers);
1711 HeadersToRaw(&headers);
1712 auto parsed = base::MakeRefCounted<HttpResponseHeaders>(headers);
1713
1714 EXPECT_EQ(test.expected_result, parsed->HasStrongValidators());
1715 }
1716
1717 const HasStrongValidatorsTestData strong_validators_tests[] = {
1718 { "HTTP/0.9 200 OK",
1719 false
1720 },
1721 { "HTTP/1.0 200 OK\n"
1722 "Date: Wed, 28 Nov 2007 01:40:10 GMT\n"
1723 "Last-Modified: Wed, 28 Nov 2007 00:40:10 GMT\n"
1724 "ETag: \"foo\"\n",
1725 false
1726 },
1727 { "HTTP/1.1 200 OK\n"
1728 "Date: Wed, 28 Nov 2007 01:40:10 GMT\n"
1729 "Last-Modified: Wed, 28 Nov 2007 00:40:10 GMT\n"
1730 "ETag: \"foo\"\n",
1731 true
1732 },
1733 { "HTTP/1.1 200 OK\n"
1734 "Date: Wed, 28 Nov 2007 00:41:10 GMT\n"
1735 "Last-Modified: Wed, 28 Nov 2007 00:40:10 GMT\n",
1736 true
1737 },
1738 { "HTTP/1.1 200 OK\n"
1739 "Date: Wed, 28 Nov 2007 00:41:09 GMT\n"
1740 "Last-Modified: Wed, 28 Nov 2007 00:40:10 GMT\n",
1741 false
1742 },
1743 { "HTTP/1.1 200 OK\n"
1744 "ETag: \"foo\"\n",
1745 true
1746 },
1747 // This is not really a weak etag:
1748 { "HTTP/1.1 200 OK\n"
1749 "etag: \"w/foo\"\n",
1750 true
1751 },
1752 // This is a weak etag:
1753 { "HTTP/1.1 200 OK\n"
1754 "etag: w/\"foo\"\n",
1755 false
1756 },
1757 { "HTTP/1.1 200 OK\n"
1758 "etag: W / \"foo\"\n",
1759 false
1760 }
1761 };
1762
1763 INSTANTIATE_TEST_SUITE_P(HttpResponseHeaders,
1764 HasStrongValidatorsTest,
1765 testing::ValuesIn(strong_validators_tests));
1766
TEST(HttpResponseHeadersTest,HasValidatorsNone)1767 TEST(HttpResponseHeadersTest, HasValidatorsNone) {
1768 std::string headers("HTTP/1.1 200 OK");
1769 HeadersToRaw(&headers);
1770 auto parsed = base::MakeRefCounted<HttpResponseHeaders>(headers);
1771 EXPECT_FALSE(parsed->HasValidators());
1772 }
1773
TEST(HttpResponseHeadersTest,HasValidatorsEtag)1774 TEST(HttpResponseHeadersTest, HasValidatorsEtag) {
1775 std::string headers(
1776 "HTTP/1.1 200 OK\n"
1777 "etag: \"anything\"");
1778 HeadersToRaw(&headers);
1779 auto parsed = base::MakeRefCounted<HttpResponseHeaders>(headers);
1780 EXPECT_TRUE(parsed->HasValidators());
1781 }
1782
TEST(HttpResponseHeadersTest,HasValidatorsLastModified)1783 TEST(HttpResponseHeadersTest, HasValidatorsLastModified) {
1784 std::string headers(
1785 "HTTP/1.1 200 OK\n"
1786 "Last-Modified: Wed, 28 Nov 2007 00:40:10 GMT");
1787 HeadersToRaw(&headers);
1788 auto parsed = base::MakeRefCounted<HttpResponseHeaders>(headers);
1789 EXPECT_TRUE(parsed->HasValidators());
1790 }
1791
TEST(HttpResponseHeadersTest,HasValidatorsWeakEtag)1792 TEST(HttpResponseHeadersTest, HasValidatorsWeakEtag) {
1793 std::string headers(
1794 "HTTP/1.1 200 OK\n"
1795 "etag: W/\"anything\"");
1796 HeadersToRaw(&headers);
1797 auto parsed = base::MakeRefCounted<HttpResponseHeaders>(headers);
1798 EXPECT_TRUE(parsed->HasValidators());
1799 }
1800
TEST(HttpResponseHeadersTest,GetNormalizedHeaderWithEmptyValues)1801 TEST(HttpResponseHeadersTest, GetNormalizedHeaderWithEmptyValues) {
1802 std::string headers(
1803 "HTTP/1.1 200 OK\n"
1804 "a:\n"
1805 "b: \n"
1806 "c:*\n"
1807 "d: *\n"
1808 "e: \n"
1809 "a: \n"
1810 "b:*\n"
1811 "c:\n"
1812 "d:*\n"
1813 "a:\n");
1814 HeadersToRaw(&headers);
1815 auto parsed = base::MakeRefCounted<HttpResponseHeaders>(headers);
1816 std::string value;
1817
1818 EXPECT_TRUE(parsed->GetNormalizedHeader("a", &value));
1819 EXPECT_EQ(value, ", , ");
1820 EXPECT_TRUE(parsed->GetNormalizedHeader("b", &value));
1821 EXPECT_EQ(value, ", *");
1822 EXPECT_TRUE(parsed->GetNormalizedHeader("c", &value));
1823 EXPECT_EQ(value, "*, ");
1824 EXPECT_TRUE(parsed->GetNormalizedHeader("d", &value));
1825 EXPECT_EQ(value, "*, *");
1826 EXPECT_TRUE(parsed->GetNormalizedHeader("e", &value));
1827 EXPECT_EQ(value, "");
1828 EXPECT_FALSE(parsed->GetNormalizedHeader("f", &value));
1829 }
1830
TEST(HttpResponseHeadersTest,GetNormalizedHeaderWithCommas)1831 TEST(HttpResponseHeadersTest, GetNormalizedHeaderWithCommas) {
1832 std::string headers(
1833 "HTTP/1.1 200 OK\n"
1834 "a: foo, bar\n"
1835 "b: , foo, bar,\n"
1836 "c: ,,,\n"
1837 "d: , , , \n"
1838 "e:\t,\t,\t,\t\n"
1839 "a: ,");
1840 HeadersToRaw(&headers);
1841 auto parsed = base::MakeRefCounted<HttpResponseHeaders>(headers);
1842 std::string value;
1843
1844 // TODO(mmenke): "Normalized" headers probably should preserve the
1845 // leading/trailing whitespace from the original headers.
1846 ASSERT_TRUE(parsed->GetNormalizedHeader("a", &value));
1847 EXPECT_EQ("foo, bar, ,", value);
1848 ASSERT_TRUE(parsed->GetNormalizedHeader("b", &value));
1849 EXPECT_EQ(", foo, bar,", value);
1850 ASSERT_TRUE(parsed->GetNormalizedHeader("c", &value));
1851 EXPECT_EQ(",,,", value);
1852 ASSERT_TRUE(parsed->GetNormalizedHeader("d", &value));
1853 EXPECT_EQ(", , ,", value);
1854 ASSERT_TRUE(parsed->GetNormalizedHeader("e", &value));
1855 EXPECT_EQ(",\t,\t,", value);
1856 EXPECT_FALSE(parsed->GetNormalizedHeader("f", &value));
1857 }
1858
TEST(HttpResponseHeadersTest,AddHeader)1859 TEST(HttpResponseHeadersTest, AddHeader) {
1860 scoped_refptr<HttpResponseHeaders> headers = HttpResponseHeaders::TryToCreate(
1861 "HTTP/1.1 200 OK\n"
1862 "connection: keep-alive\n"
1863 "Cache-control: max-age=10000\n");
1864 ASSERT_TRUE(headers);
1865
1866 headers->AddHeader("Content-Length", "450");
1867 EXPECT_EQ(
1868 "HTTP/1.1 200 OK\n"
1869 "connection: keep-alive\n"
1870 "Cache-control: max-age=10000\n"
1871 "Content-Length: 450\n",
1872 ToSimpleString(headers));
1873
1874 // Add a second Content-Length header with extra spaces in the value. It
1875 // should be added to the end, and the extra spaces removed.
1876 headers->AddHeader("Content-Length", " 42 ");
1877 EXPECT_EQ(
1878 "HTTP/1.1 200 OK\n"
1879 "connection: keep-alive\n"
1880 "Cache-control: max-age=10000\n"
1881 "Content-Length: 450\n"
1882 "Content-Length: 42\n",
1883 ToSimpleString(headers));
1884 }
1885
TEST(HttpResponseHeadersTest,SetHeader)1886 TEST(HttpResponseHeadersTest, SetHeader) {
1887 scoped_refptr<HttpResponseHeaders> headers = HttpResponseHeaders::TryToCreate(
1888 "HTTP/1.1 200 OK\n"
1889 "connection: keep-alive\n"
1890 "Cache-control: max-age=10000\n");
1891 ASSERT_TRUE(headers);
1892
1893 headers->SetHeader("Content-Length", "450");
1894 EXPECT_EQ(
1895 "HTTP/1.1 200 OK\n"
1896 "connection: keep-alive\n"
1897 "Cache-control: max-age=10000\n"
1898 "Content-Length: 450\n",
1899 ToSimpleString(headers));
1900
1901 headers->SetHeader("Content-Length", " 42 ");
1902 EXPECT_EQ(
1903 "HTTP/1.1 200 OK\n"
1904 "connection: keep-alive\n"
1905 "Cache-control: max-age=10000\n"
1906 "Content-Length: 42\n",
1907 ToSimpleString(headers));
1908
1909 headers->SetHeader("connection", "close");
1910 EXPECT_EQ(
1911 "HTTP/1.1 200 OK\n"
1912 "Cache-control: max-age=10000\n"
1913 "Content-Length: 42\n"
1914 "connection: close\n",
1915 ToSimpleString(headers));
1916 }
1917
TEST(HttpResponseHeadersTest,TryToCreateWithNul)1918 TEST(HttpResponseHeadersTest, TryToCreateWithNul) {
1919 static constexpr char kHeadersWithNuls[] = {
1920 "HTTP/1.1 200 OK\0"
1921 "Content-Type: application/octet-stream\0"};
1922 // The size must be specified explicitly to include the nul characters.
1923 static constexpr base::StringPiece kHeadersWithNulsAsStringPiece(
1924 kHeadersWithNuls, sizeof(kHeadersWithNuls));
1925 scoped_refptr<HttpResponseHeaders> headers =
1926 HttpResponseHeaders::TryToCreate(kHeadersWithNulsAsStringPiece);
1927 EXPECT_EQ(headers, nullptr);
1928 }
1929
1930 #if !BUILDFLAG(CRONET_BUILD)
1931 // Cronet disables tracing so this test would fail.
TEST(HttpResponseHeadersTest,TracingSupport)1932 TEST(HttpResponseHeadersTest, TracingSupport) {
1933 scoped_refptr<HttpResponseHeaders> headers = HttpResponseHeaders::TryToCreate(
1934 "HTTP/1.1 200 OK\n"
1935 "connection: keep-alive\n");
1936 ASSERT_TRUE(headers);
1937
1938 EXPECT_EQ(perfetto::TracedValueToString(headers),
1939 "{response_code:200,headers:[{name:connection,value:keep-alive}]}");
1940 }
1941 #endif
1942
1943 struct RemoveHeaderTestData {
1944 const char* orig_headers;
1945 const char* to_remove;
1946 const char* expected_headers;
1947 };
1948
1949 class RemoveHeaderTest
1950 : public HttpResponseHeadersTest,
1951 public ::testing::WithParamInterface<RemoveHeaderTestData> {
1952 };
1953
TEST_P(RemoveHeaderTest,RemoveHeader)1954 TEST_P(RemoveHeaderTest, RemoveHeader) {
1955 const RemoveHeaderTestData test = GetParam();
1956
1957 std::string orig_headers(test.orig_headers);
1958 HeadersToRaw(&orig_headers);
1959 auto parsed = base::MakeRefCounted<HttpResponseHeaders>(orig_headers);
1960
1961 std::string name(test.to_remove);
1962 parsed->RemoveHeader(name);
1963
1964 EXPECT_EQ(std::string(test.expected_headers), ToSimpleString(parsed));
1965 }
1966
1967 const RemoveHeaderTestData remove_header_tests[] = {
1968 { "HTTP/1.1 200 OK\n"
1969 "connection: keep-alive\n"
1970 "Cache-control: max-age=10000\n"
1971 "Content-Length: 450\n",
1972
1973 "Content-Length",
1974
1975 "HTTP/1.1 200 OK\n"
1976 "connection: keep-alive\n"
1977 "Cache-control: max-age=10000\n"
1978 },
1979 { "HTTP/1.1 200 OK\n"
1980 "connection: keep-alive \n"
1981 "Content-Length : 450 \n"
1982 "Cache-control: max-age=10000\n",
1983
1984 "Content-Length",
1985
1986 "HTTP/1.1 200 OK\n"
1987 "connection: keep-alive\n"
1988 "Cache-control: max-age=10000\n"
1989 },
1990 };
1991
1992 INSTANTIATE_TEST_SUITE_P(HttpResponseHeaders,
1993 RemoveHeaderTest,
1994 testing::ValuesIn(remove_header_tests));
1995
1996 struct RemoveHeadersTestData {
1997 const char* orig_headers;
1998 const char* to_remove[2];
1999 const char* expected_headers;
2000 };
2001
2002 class RemoveHeadersTest
2003 : public HttpResponseHeadersTest,
2004 public ::testing::WithParamInterface<RemoveHeadersTestData> {};
2005
TEST_P(RemoveHeadersTest,RemoveHeaders)2006 TEST_P(RemoveHeadersTest, RemoveHeaders) {
2007 const RemoveHeadersTestData test = GetParam();
2008
2009 std::string orig_headers(test.orig_headers);
2010 HeadersToRaw(&orig_headers);
2011 auto parsed = base::MakeRefCounted<HttpResponseHeaders>(orig_headers);
2012
2013 std::unordered_set<std::string> to_remove;
2014 for (auto* header : test.to_remove) {
2015 if (header)
2016 to_remove.insert(header);
2017 }
2018 parsed->RemoveHeaders(to_remove);
2019
2020 EXPECT_EQ(std::string(test.expected_headers), ToSimpleString(parsed));
2021 }
2022
2023 const RemoveHeadersTestData remove_headers_tests[] = {
2024 {"HTTP/1.1 200 OK\n"
2025 "connection: keep-alive\n"
2026 "Cache-control: max-age=10000\n"
2027 "Content-Length: 450\n",
2028
2029 {"Content-Length", "CACHE-control"},
2030
2031 "HTTP/1.1 200 OK\n"
2032 "connection: keep-alive\n"},
2033
2034 {"HTTP/1.1 200 OK\n"
2035 "connection: keep-alive\n"
2036 "Content-Length: 450\n",
2037
2038 {"foo", "bar"},
2039
2040 "HTTP/1.1 200 OK\n"
2041 "connection: keep-alive\n"
2042 "Content-Length: 450\n"},
2043
2044 {"HTTP/1.1 404 Kinda not OK\n"
2045 "connection: keep-alive \n",
2046
2047 {},
2048
2049 "HTTP/1.1 404 Kinda not OK\n"
2050 "connection: keep-alive\n"},
2051 };
2052
2053 INSTANTIATE_TEST_SUITE_P(HttpResponseHeaders,
2054 RemoveHeadersTest,
2055 testing::ValuesIn(remove_headers_tests));
2056
2057 struct RemoveIndividualHeaderTestData {
2058 const char* orig_headers;
2059 const char* to_remove_name;
2060 const char* to_remove_value;
2061 const char* expected_headers;
2062 };
2063
2064 class RemoveIndividualHeaderTest
2065 : public HttpResponseHeadersTest,
2066 public ::testing::WithParamInterface<RemoveIndividualHeaderTestData> {
2067 };
2068
TEST_P(RemoveIndividualHeaderTest,RemoveIndividualHeader)2069 TEST_P(RemoveIndividualHeaderTest, RemoveIndividualHeader) {
2070 const RemoveIndividualHeaderTestData test = GetParam();
2071
2072 std::string orig_headers(test.orig_headers);
2073 HeadersToRaw(&orig_headers);
2074 auto parsed = base::MakeRefCounted<HttpResponseHeaders>(orig_headers);
2075
2076 std::string name(test.to_remove_name);
2077 std::string value(test.to_remove_value);
2078 parsed->RemoveHeaderLine(name, value);
2079
2080 EXPECT_EQ(std::string(test.expected_headers), ToSimpleString(parsed));
2081 }
2082
2083 const RemoveIndividualHeaderTestData remove_individual_header_tests[] = {
2084 { "HTTP/1.1 200 OK\n"
2085 "connection: keep-alive\n"
2086 "Cache-control: max-age=10000\n"
2087 "Content-Length: 450\n",
2088
2089 "Content-Length",
2090
2091 "450",
2092
2093 "HTTP/1.1 200 OK\n"
2094 "connection: keep-alive\n"
2095 "Cache-control: max-age=10000\n"
2096 },
2097 { "HTTP/1.1 200 OK\n"
2098 "connection: keep-alive \n"
2099 "Content-Length : 450 \n"
2100 "Cache-control: max-age=10000\n",
2101
2102 "Content-Length",
2103
2104 "450",
2105
2106 "HTTP/1.1 200 OK\n"
2107 "connection: keep-alive\n"
2108 "Cache-control: max-age=10000\n"
2109 },
2110 { "HTTP/1.1 200 OK\n"
2111 "connection: keep-alive \n"
2112 "Content-Length: 450\n"
2113 "Cache-control: max-age=10000\n",
2114
2115 "Content-Length", // Matching name.
2116
2117 "999", // Mismatching value.
2118
2119 "HTTP/1.1 200 OK\n"
2120 "connection: keep-alive\n"
2121 "Content-Length: 450\n"
2122 "Cache-control: max-age=10000\n"
2123 },
2124 { "HTTP/1.1 200 OK\n"
2125 "connection: keep-alive \n"
2126 "Foo: bar, baz\n"
2127 "Foo: bar\n"
2128 "Cache-control: max-age=10000\n",
2129
2130 "Foo",
2131
2132 "bar, baz", // Space in value.
2133
2134 "HTTP/1.1 200 OK\n"
2135 "connection: keep-alive\n"
2136 "Foo: bar\n"
2137 "Cache-control: max-age=10000\n"
2138 },
2139 { "HTTP/1.1 200 OK\n"
2140 "connection: keep-alive \n"
2141 "Foo: bar, baz\n"
2142 "Cache-control: max-age=10000\n",
2143
2144 "Foo",
2145
2146 "baz", // Only partial match -> ignored.
2147
2148 "HTTP/1.1 200 OK\n"
2149 "connection: keep-alive\n"
2150 "Foo: bar, baz\n"
2151 "Cache-control: max-age=10000\n"
2152 },
2153 };
2154
2155 INSTANTIATE_TEST_SUITE_P(HttpResponseHeaders,
2156 RemoveIndividualHeaderTest,
2157 testing::ValuesIn(remove_individual_header_tests));
2158
2159 struct ReplaceStatusTestData {
2160 const char* orig_headers;
2161 const char* new_status;
2162 const char* expected_headers;
2163 };
2164
2165 class ReplaceStatusTest
2166 : public HttpResponseHeadersTest,
2167 public ::testing::WithParamInterface<ReplaceStatusTestData> {
2168 };
2169
TEST_P(ReplaceStatusTest,ReplaceStatus)2170 TEST_P(ReplaceStatusTest, ReplaceStatus) {
2171 const ReplaceStatusTestData test = GetParam();
2172
2173 std::string orig_headers(test.orig_headers);
2174 HeadersToRaw(&orig_headers);
2175 auto parsed = base::MakeRefCounted<HttpResponseHeaders>(orig_headers);
2176
2177 std::string name(test.new_status);
2178 parsed->ReplaceStatusLine(name);
2179
2180 EXPECT_EQ(std::string(test.expected_headers), ToSimpleString(parsed));
2181 }
2182
2183 const ReplaceStatusTestData replace_status_tests[] = {
2184 { "HTTP/1.1 206 Partial Content\n"
2185 "connection: keep-alive\n"
2186 "Cache-control: max-age=10000\n"
2187 "Content-Length: 450\n",
2188
2189 "HTTP/1.1 200 OK",
2190
2191 "HTTP/1.1 200 OK\n"
2192 "connection: keep-alive\n"
2193 "Cache-control: max-age=10000\n"
2194 "Content-Length: 450\n"
2195 },
2196 { "HTTP/1.1 200 OK\n"
2197 "connection: keep-alive\n",
2198
2199 "HTTP/1.1 304 Not Modified",
2200
2201 "HTTP/1.1 304 Not Modified\n"
2202 "connection: keep-alive\n"
2203 },
2204 { "HTTP/1.1 200 OK\n"
2205 "connection: keep-alive \n"
2206 "Content-Length : 450 \n"
2207 "Cache-control: max-age=10000\n",
2208
2209 "HTTP/1//1 304 Not Modified",
2210
2211 "HTTP/1.0 304 Not Modified\n"
2212 "connection: keep-alive\n"
2213 "Content-Length: 450\n"
2214 "Cache-control: max-age=10000\n"
2215 },
2216 };
2217
2218 INSTANTIATE_TEST_SUITE_P(HttpResponseHeaders,
2219 ReplaceStatusTest,
2220 testing::ValuesIn(replace_status_tests));
2221
2222 struct UpdateWithNewRangeTestData {
2223 const char* orig_headers;
2224 const char* expected_headers;
2225 const char* expected_headers_with_replaced_status;
2226 };
2227
2228 class UpdateWithNewRangeTest
2229 : public HttpResponseHeadersTest,
2230 public ::testing::WithParamInterface<UpdateWithNewRangeTestData> {
2231 };
2232
TEST_P(UpdateWithNewRangeTest,UpdateWithNewRange)2233 TEST_P(UpdateWithNewRangeTest, UpdateWithNewRange) {
2234 const UpdateWithNewRangeTestData test = GetParam();
2235
2236 const HttpByteRange range = HttpByteRange::Bounded(3, 5);
2237
2238 std::string orig_headers(test.orig_headers);
2239 std::replace(orig_headers.begin(), orig_headers.end(), '\n', '\0');
2240 auto parsed = base::MakeRefCounted<HttpResponseHeaders>(orig_headers + '\0');
2241 int64_t content_size = parsed->GetContentLength();
2242
2243 // Update headers without replacing status line.
2244 parsed->UpdateWithNewRange(range, content_size, false);
2245 EXPECT_EQ(std::string(test.expected_headers), ToSimpleString(parsed));
2246
2247 // Replace status line too.
2248 parsed->UpdateWithNewRange(range, content_size, true);
2249 EXPECT_EQ(std::string(test.expected_headers_with_replaced_status),
2250 ToSimpleString(parsed));
2251 }
2252
2253 const UpdateWithNewRangeTestData update_range_tests[] = {
2254 { "HTTP/1.1 200 OK\n"
2255 "Content-Length: 450\n",
2256
2257 "HTTP/1.1 200 OK\n"
2258 "Content-Range: bytes 3-5/450\n"
2259 "Content-Length: 3\n",
2260
2261 "HTTP/1.1 206 Partial Content\n"
2262 "Content-Range: bytes 3-5/450\n"
2263 "Content-Length: 3\n",
2264 },
2265 { "HTTP/1.1 200 OK\n"
2266 "Content-Length: 5\n",
2267
2268 "HTTP/1.1 200 OK\n"
2269 "Content-Range: bytes 3-5/5\n"
2270 "Content-Length: 3\n",
2271
2272 "HTTP/1.1 206 Partial Content\n"
2273 "Content-Range: bytes 3-5/5\n"
2274 "Content-Length: 3\n",
2275 },
2276 };
2277
2278 INSTANTIATE_TEST_SUITE_P(HttpResponseHeaders,
2279 UpdateWithNewRangeTest,
2280 testing::ValuesIn(update_range_tests));
2281
TEST_F(HttpResponseHeadersCacheControlTest,AbsentMaxAgeReturnsFalse)2282 TEST_F(HttpResponseHeadersCacheControlTest, AbsentMaxAgeReturnsFalse) {
2283 InitializeHeadersWithCacheControl("nocache");
2284 EXPECT_FALSE(headers()->GetMaxAgeValue(TimeDeltaPointer()));
2285 }
2286
TEST_F(HttpResponseHeadersCacheControlTest,MaxAgeWithNoParameterRejected)2287 TEST_F(HttpResponseHeadersCacheControlTest, MaxAgeWithNoParameterRejected) {
2288 InitializeHeadersWithCacheControl("max-age=,private");
2289 EXPECT_FALSE(headers()->GetMaxAgeValue(TimeDeltaPointer()));
2290 }
2291
TEST_F(HttpResponseHeadersCacheControlTest,MaxAgeWithSpaceParameterRejected)2292 TEST_F(HttpResponseHeadersCacheControlTest, MaxAgeWithSpaceParameterRejected) {
2293 InitializeHeadersWithCacheControl("max-age= ,private");
2294 EXPECT_FALSE(headers()->GetMaxAgeValue(TimeDeltaPointer()));
2295 }
2296
TEST_F(HttpResponseHeadersCacheControlTest,MaxAgeWithInterimSpaceIsRejected)2297 TEST_F(HttpResponseHeadersCacheControlTest, MaxAgeWithInterimSpaceIsRejected) {
2298 InitializeHeadersWithCacheControl("max-age=1 2");
2299 EXPECT_FALSE(headers()->GetMaxAgeValue(TimeDeltaPointer()));
2300 }
2301
TEST_F(HttpResponseHeadersCacheControlTest,MaxAgeWithMinusSignIsRejected)2302 TEST_F(HttpResponseHeadersCacheControlTest, MaxAgeWithMinusSignIsRejected) {
2303 InitializeHeadersWithCacheControl("max-age=-7");
2304 EXPECT_FALSE(headers()->GetMaxAgeValue(TimeDeltaPointer()));
2305 }
2306
TEST_F(HttpResponseHeadersCacheControlTest,MaxAgeWithSpaceBeforeEqualsIsRejected)2307 TEST_F(HttpResponseHeadersCacheControlTest,
2308 MaxAgeWithSpaceBeforeEqualsIsRejected) {
2309 InitializeHeadersWithCacheControl("max-age = 7");
2310 EXPECT_FALSE(headers()->GetMaxAgeValue(TimeDeltaPointer()));
2311 }
2312
TEST_F(HttpResponseHeadersCacheControlTest,MaxAgeWithLeadingandTrailingSpaces)2313 TEST_F(HttpResponseHeadersCacheControlTest,
2314 MaxAgeWithLeadingandTrailingSpaces) {
2315 InitializeHeadersWithCacheControl("max-age= 7 ");
2316 EXPECT_EQ(base::Seconds(7), GetMaxAgeValue());
2317 }
2318
TEST_F(HttpResponseHeadersCacheControlTest,MaxAgeFirstMatchUsed)2319 TEST_F(HttpResponseHeadersCacheControlTest, MaxAgeFirstMatchUsed) {
2320 InitializeHeadersWithCacheControl("max-age=10, max-age=20");
2321 EXPECT_EQ(base::Seconds(10), GetMaxAgeValue());
2322 }
2323
TEST_F(HttpResponseHeadersCacheControlTest,MaxAgeBogusFirstMatchUsed)2324 TEST_F(HttpResponseHeadersCacheControlTest, MaxAgeBogusFirstMatchUsed) {
2325 // "max-age10" isn't parsed as "max-age"; "max-age=now" is bogus and
2326 // ignored and so "max-age=20" is used.
2327 InitializeHeadersWithCacheControl(
2328 "max-age10, max-age=now, max-age=20, max-age=30");
2329 EXPECT_EQ(base::Seconds(20), GetMaxAgeValue());
2330 }
2331
TEST_F(HttpResponseHeadersCacheControlTest,MaxAgeCaseInsensitive)2332 TEST_F(HttpResponseHeadersCacheControlTest, MaxAgeCaseInsensitive) {
2333 InitializeHeadersWithCacheControl("Max-aGe=15");
2334 EXPECT_EQ(base::Seconds(15), GetMaxAgeValue());
2335 }
2336
TEST_F(HttpResponseHeadersCacheControlTest,MaxAgeOverflow)2337 TEST_F(HttpResponseHeadersCacheControlTest, MaxAgeOverflow) {
2338 InitializeHeadersWithCacheControl("max-age=99999999999999999999");
2339 EXPECT_EQ(base::TimeDelta::FiniteMax().InSeconds(),
2340 GetMaxAgeValue().InSeconds());
2341 }
2342
2343 struct MaxAgeTestData {
2344 const char* max_age_string;
2345 const absl::optional<int64_t> expected_seconds;
2346 };
2347
2348 class MaxAgeEdgeCasesTest
2349 : public HttpResponseHeadersCacheControlTest,
2350 public ::testing::WithParamInterface<MaxAgeTestData> {
2351 };
2352
TEST_P(MaxAgeEdgeCasesTest,MaxAgeEdgeCases)2353 TEST_P(MaxAgeEdgeCasesTest, MaxAgeEdgeCases) {
2354 const MaxAgeTestData test = GetParam();
2355
2356 std::string max_age = "max-age=";
2357 InitializeHeadersWithCacheControl(
2358 (max_age + test.max_age_string).c_str());
2359 if (test.expected_seconds.has_value()) {
2360 EXPECT_EQ(test.expected_seconds.value(), GetMaxAgeValue().InSeconds())
2361 << " for max-age=" << test.max_age_string;
2362 } else {
2363 EXPECT_FALSE(headers()->GetMaxAgeValue(TimeDeltaPointer()));
2364 }
2365 }
2366
2367 const MaxAgeTestData max_age_tests[] = {
2368 {" 1 ", 1}, // Spaces are ignored.
2369 {"-1", absl::nullopt},
2370 {"--1", absl::nullopt},
2371 {"2s", absl::nullopt},
2372 {"3 days", absl::nullopt},
2373 {"'4'", absl::nullopt},
2374 {"\"5\"", absl::nullopt},
2375 {"0x6", absl::nullopt}, // Hex not parsed as hex.
2376 {"7F", absl::nullopt}, // Hex without 0x still not parsed as hex.
2377 {"010", 10}, // Octal not parsed as octal.
2378 {"9223372036853", 9223372036853},
2379 {"9223372036854", 9223372036854},
2380 {"9223372036855", 9223372036854},
2381 {"9223372036854775806", 9223372036854},
2382 {"9223372036854775807", 9223372036854},
2383 {"20000000000000000000", 9223372036854}, // Overflow int64_t.
2384 };
2385
2386 INSTANTIATE_TEST_SUITE_P(HttpResponseHeadersCacheControl,
2387 MaxAgeEdgeCasesTest,
2388 testing::ValuesIn(max_age_tests));
2389
TEST_F(HttpResponseHeadersCacheControlTest,AbsentStaleWhileRevalidateReturnsFalse)2390 TEST_F(HttpResponseHeadersCacheControlTest,
2391 AbsentStaleWhileRevalidateReturnsFalse) {
2392 InitializeHeadersWithCacheControl("max-age=3600");
2393 EXPECT_FALSE(headers()->GetStaleWhileRevalidateValue(TimeDeltaPointer()));
2394 }
2395
TEST_F(HttpResponseHeadersCacheControlTest,StaleWhileRevalidateWithoutValueRejected)2396 TEST_F(HttpResponseHeadersCacheControlTest,
2397 StaleWhileRevalidateWithoutValueRejected) {
2398 InitializeHeadersWithCacheControl("max-age=3600,stale-while-revalidate=");
2399 EXPECT_FALSE(headers()->GetStaleWhileRevalidateValue(TimeDeltaPointer()));
2400 }
2401
TEST_F(HttpResponseHeadersCacheControlTest,StaleWhileRevalidateWithInvalidValueIgnored)2402 TEST_F(HttpResponseHeadersCacheControlTest,
2403 StaleWhileRevalidateWithInvalidValueIgnored) {
2404 InitializeHeadersWithCacheControl("max-age=3600,stale-while-revalidate=true");
2405 EXPECT_FALSE(headers()->GetStaleWhileRevalidateValue(TimeDeltaPointer()));
2406 }
2407
TEST_F(HttpResponseHeadersCacheControlTest,StaleWhileRevalidateValueReturned)2408 TEST_F(HttpResponseHeadersCacheControlTest, StaleWhileRevalidateValueReturned) {
2409 InitializeHeadersWithCacheControl("max-age=3600,stale-while-revalidate=7200");
2410 EXPECT_EQ(base::Seconds(7200), GetStaleWhileRevalidateValue());
2411 }
2412
TEST_F(HttpResponseHeadersCacheControlTest,FirstStaleWhileRevalidateValueUsed)2413 TEST_F(HttpResponseHeadersCacheControlTest,
2414 FirstStaleWhileRevalidateValueUsed) {
2415 InitializeHeadersWithCacheControl(
2416 "stale-while-revalidate=1,stale-while-revalidate=7200");
2417 EXPECT_EQ(base::Seconds(1), GetStaleWhileRevalidateValue());
2418 }
2419
2420 struct GetCurrentAgeTestData {
2421 const char* headers;
2422 const char* request_time;
2423 const char* response_time;
2424 const char* current_time;
2425 const int expected_age;
2426 };
2427
2428 class GetCurrentAgeTest
2429 : public HttpResponseHeadersTest,
2430 public ::testing::WithParamInterface<GetCurrentAgeTestData> {
2431 };
2432
TEST_P(GetCurrentAgeTest,GetCurrentAge)2433 TEST_P(GetCurrentAgeTest, GetCurrentAge) {
2434 const GetCurrentAgeTestData test = GetParam();
2435
2436 base::Time request_time, response_time, current_time;
2437 ASSERT_TRUE(base::Time::FromString(test.request_time, &request_time));
2438 ASSERT_TRUE(base::Time::FromString(test.response_time, &response_time));
2439 ASSERT_TRUE(base::Time::FromString(test.current_time, ¤t_time));
2440
2441 std::string headers(test.headers);
2442 HeadersToRaw(&headers);
2443 auto parsed = base::MakeRefCounted<HttpResponseHeaders>(headers);
2444
2445 base::TimeDelta age =
2446 parsed->GetCurrentAge(request_time, response_time, current_time);
2447 EXPECT_EQ(test.expected_age, age.InSeconds());
2448 }
2449
2450 const struct GetCurrentAgeTestData get_current_age_tests[] = {
2451 // Without Date header.
2452 {"HTTP/1.1 200 OK\n"
2453 "Age: 2",
2454 "Fri, 20 Jan 2011 10:40:08 GMT", "Fri, 20 Jan 2011 10:40:12 GMT",
2455 "Fri, 20 Jan 2011 10:40:14 GMT", 8},
2456 // Without Age header.
2457 {"HTTP/1.1 200 OK\n"
2458 "Date: Fri, 20 Jan 2011 10:40:10 GMT\n",
2459 "Fri, 20 Jan 2011 10:40:08 GMT", "Fri, 20 Jan 2011 10:40:12 GMT",
2460 "Fri, 20 Jan 2011 10:40:14 GMT", 6},
2461 // date_value > response_time with Age header.
2462 {"HTTP/1.1 200 OK\n"
2463 "Date: Fri, 20 Jan 2011 10:40:14 GMT\n"
2464 "Age: 2\n",
2465 "Fri, 20 Jan 2011 10:40:08 GMT", "Fri, 20 Jan 2011 10:40:12 GMT",
2466 "Fri, 20 Jan 2011 10:40:14 GMT", 8},
2467 // date_value > response_time without Age header.
2468 {"HTTP/1.1 200 OK\n"
2469 "Date: Fri, 20 Jan 2011 10:40:14 GMT\n",
2470 "Fri, 20 Jan 2011 10:40:08 GMT", "Fri, 20 Jan 2011 10:40:12 GMT",
2471 "Fri, 20 Jan 2011 10:40:14 GMT", 6},
2472 // apparent_age > corrected_age_value
2473 {"HTTP/1.1 200 OK\n"
2474 "Date: Fri, 20 Jan 2011 10:40:07 GMT\n"
2475 "Age: 0\n",
2476 "Fri, 20 Jan 2011 10:40:08 GMT", "Fri, 20 Jan 2011 10:40:12 GMT",
2477 "Fri, 20 Jan 2011 10:40:14 GMT", 7}};
2478
2479 INSTANTIATE_TEST_SUITE_P(HttpResponseHeaders,
2480 GetCurrentAgeTest,
2481 testing::ValuesIn(get_current_age_tests));
2482
TEST(HttpResponseHeadersBuilderTest,Version)2483 TEST(HttpResponseHeadersBuilderTest, Version) {
2484 for (HttpVersion version :
2485 {HttpVersion(1, 0), HttpVersion(1, 1), HttpVersion(2, 0)}) {
2486 auto headers = HttpResponseHeaders::Builder(version, "200").Build();
2487 EXPECT_EQ(base::StringPrintf("HTTP/%d.%d 200", version.major_value(),
2488 version.minor_value()),
2489 headers->GetStatusLine());
2490 EXPECT_EQ(version, headers->GetHttpVersion());
2491 }
2492 }
2493
2494 struct BuilderStatusLineTestData {
2495 const base::StringPiece status;
2496 const base::StringPiece expected_status_line;
2497 const int expected_response_code;
2498 const base::StringPiece expected_status_text;
2499 };
2500
2501 // Provide GTest with a method to print the BuilderStatusLineTestData, for ease
2502 // of debugging.
PrintTo(const BuilderStatusLineTestData & data,std::ostream * os)2503 void PrintTo(const BuilderStatusLineTestData& data, std::ostream* os) {
2504 *os << "\"" << data.status << "\", \"" << data.expected_status_line << "\", "
2505 << data.expected_response_code << ", \"" << data.expected_status_text
2506 << "\"}";
2507 }
2508
2509 class BuilderStatusLineTest
2510 : public HttpResponseHeadersTest,
2511 public ::testing::WithParamInterface<BuilderStatusLineTestData> {};
2512
TEST_P(BuilderStatusLineTest,Common)2513 TEST_P(BuilderStatusLineTest, Common) {
2514 const auto& [status, expected_status_line, expected_response_code,
2515 expected_status_text] = GetParam();
2516
2517 auto http_response_headers =
2518 HttpResponseHeaders::Builder({1, 1}, status).Build();
2519
2520 EXPECT_EQ(expected_status_line, http_response_headers->GetStatusLine());
2521 EXPECT_EQ(expected_response_code, http_response_headers->response_code());
2522 EXPECT_EQ(expected_status_text, http_response_headers->GetStatusText());
2523 }
2524
2525 constexpr BuilderStatusLineTestData kBuilderStatusLineTests[] = {
2526 {// Simple case.
2527 "200 OK",
2528
2529 "HTTP/1.1 200 OK", 200, "OK"},
2530 {// No status text.
2531 "200",
2532
2533 "HTTP/1.1 200", 200, ""},
2534 {// Empty status.
2535 "",
2536
2537 "HTTP/1.1 200", 200, ""},
2538 {// Space status.
2539 " ",
2540
2541 "HTTP/1.1 200", 200, ""},
2542 {// Spaces removed from status.
2543 " 204 No content ",
2544
2545 "HTTP/1.1 204 No content", 204, "No content"},
2546 {// Tabs treated as terminating whitespace.
2547 "204 \t No content \t ",
2548
2549 "HTTP/1.1 204 \t No content \t", 204, "\t No content \t"},
2550 {// Status text smushed into response code.
2551 "426Smush",
2552
2553 "HTTP/1.1 426 Smush", 426, "Smush"},
2554 {// Tab gets included in status text.
2555 "501\tStatus\t",
2556
2557 "HTTP/1.1 501 \tStatus\t", 501, "\tStatus\t"},
2558 {// Zero response code.
2559 "0 Zero",
2560
2561 "HTTP/1.1 0 Zero", 0, "Zero"},
2562 {// Oversize response code.
2563 "20230904 Monday",
2564
2565 "HTTP/1.1 20230904 Monday", 20230904, "Monday"},
2566 {// Overflowing response code.
2567 "9123456789 Overflow",
2568
2569 "HTTP/1.1 9123456789 Overflow", 2147483647, "Overflow"},
2570 };
2571
2572 INSTANTIATE_TEST_SUITE_P(HttpResponseHeaders,
2573 BuilderStatusLineTest,
2574 testing::ValuesIn(kBuilderStatusLineTests));
2575
2576 struct BuilderHeadersTestData {
2577 const std::vector<std::pair<base::StringPiece, base::StringPiece>> headers;
2578 const base::StringPiece expected_headers;
2579 };
2580
2581 // Provide GTest with a method to print the BuilderHeadersTestData, for ease of
2582 // debugging.
PrintTo(const BuilderHeadersTestData & data,std::ostream * os)2583 void PrintTo(const BuilderHeadersTestData& data, std::ostream* os) {
2584 *os << "{";
2585 for (const auto& header : data.headers) {
2586 *os << "{\"" << header.first << "\", \"" << header.second << "\"},";
2587 }
2588 std::string expected_headers(data.expected_headers);
2589 EscapeForPrinting(&expected_headers);
2590 *os << "}, \"" << expected_headers << "\"}";
2591 }
2592
2593 class BuilderHeadersTest
2594 : public HttpResponseHeadersTest,
2595 public ::testing::WithParamInterface<BuilderHeadersTestData> {};
2596
TEST_P(BuilderHeadersTest,Common)2597 TEST_P(BuilderHeadersTest, Common) {
2598 const auto& [headers, expected_headers_const] = GetParam();
2599 HttpResponseHeaders::Builder builder({1, 1}, "200");
2600 for (const auto& [key, value] : headers) {
2601 builder.AddHeader(key, value);
2602 }
2603 auto http_response_headers = builder.Build();
2604
2605 std::string output_headers = ToSimpleString(http_response_headers);
2606 std::string expected_headers(expected_headers_const);
2607
2608 EscapeForPrinting(&output_headers);
2609 EscapeForPrinting(&expected_headers);
2610
2611 EXPECT_EQ(expected_headers, output_headers);
2612 }
2613
2614 const BuilderHeadersTestData builder_headers_tests[] = {
2615 {// Single header.
2616 {{"Content-Type", "text/html"}},
2617
2618 "HTTP/1.1 200\n"
2619 "Content-Type: text/html\n"},
2620 {// Multiple headers.
2621 {
2622 {"Content-Type", "text/html"},
2623 {"Content-Length", "6"},
2624 {"Set-Cookie", "a=1"},
2625 },
2626
2627 "HTTP/1.1 200\n"
2628 "Content-Type: text/html\n"
2629 "Content-Length: 6\n"
2630 "Set-Cookie: a=1\n"},
2631 {// Empty header value.
2632 {{"Pragma", ""}},
2633
2634 "HTTP/1.1 200\n"
2635 "Pragma: \n"},
2636 {// Multiple header value.
2637 {{"Cache-Control", "no-cache, no-store"}},
2638
2639 "HTTP/1.1 200\n"
2640 "Cache-Control: no-cache, no-store\n"},
2641 {// Spaces are removed around values, but when EnumerateHeaderLines()
2642 // rejoins continuations, it keeps interior spaces. .
2643 {{"X-Commas", " , , "}},
2644
2645 "HTTP/1.1 200\n"
2646 "X-Commas: , ,\n"},
2647 {// Single value is trimmed.
2648 {{"Pragma", " no-cache "}},
2649
2650 "HTTP/1.1 200\n"
2651 "Pragma: no-cache\n"},
2652 {// Location header is trimmed.
2653 {{"Location", " http://example.com/ "}},
2654
2655 "HTTP/1.1 200\n"
2656 "Location: http://example.com/\n"},
2657 };
2658
2659 INSTANTIATE_TEST_SUITE_P(HttpResponseHeaders,
2660 BuilderHeadersTest,
2661 testing::ValuesIn(builder_headers_tests));
2662
TEST(HttpResponseHeadersTest,StrictlyEqualsSuccess)2663 TEST(HttpResponseHeadersTest, StrictlyEqualsSuccess) {
2664 constexpr char kRawHeaders[] =
2665 "HTTP/1.1 200\n"
2666 "Content-Type:application/octet-stream\n"
2667 "Cache-Control:no-cache, no-store\n";
2668 std::string raw_headers = kRawHeaders;
2669 HeadersToRaw(&raw_headers);
2670 const auto parsed = base::MakeRefCounted<HttpResponseHeaders>(raw_headers);
2671 const auto built = HttpResponseHeaders::Builder({1, 1}, "200")
2672 .AddHeader("Content-Type", "application/octet-stream")
2673 .AddHeader("Cache-Control", "no-cache, no-store")
2674 .Build();
2675 EXPECT_TRUE(parsed->StrictlyEquals(*built));
2676 EXPECT_TRUE(built->StrictlyEquals(*parsed));
2677 }
2678
TEST(HttpResponseHeadersTest,StrictlyEqualsVersionMismatch)2679 TEST(HttpResponseHeadersTest, StrictlyEqualsVersionMismatch) {
2680 const auto http10 = HttpResponseHeaders::Builder({1, 0}, "200").Build();
2681 const auto http11 = HttpResponseHeaders::Builder({1, 1}, "200").Build();
2682 EXPECT_FALSE(http10->StrictlyEquals(*http11));
2683 EXPECT_FALSE(http11->StrictlyEquals(*http10));
2684 }
2685
TEST(HttpResponseHeadersTest,StrictlyEqualsResponseCodeMismatch)2686 TEST(HttpResponseHeadersTest, StrictlyEqualsResponseCodeMismatch) {
2687 const auto response200 = HttpResponseHeaders::Builder({1, 1}, "200").Build();
2688 const auto response404 = HttpResponseHeaders::Builder({1, 1}, "404").Build();
2689 EXPECT_FALSE(response200->StrictlyEquals(*response404));
2690 EXPECT_FALSE(response404->StrictlyEquals(*response200));
2691 }
2692
TEST(HttpResponseHeadersTest,StrictlyEqualsStatusTextMismatch)2693 TEST(HttpResponseHeadersTest, StrictlyEqualsStatusTextMismatch) {
2694 const auto ok = HttpResponseHeaders::Builder({1, 1}, "200 OK").Build();
2695 const auto ng = HttpResponseHeaders::Builder({1, 1}, "200 NG").Build();
2696 EXPECT_FALSE(ok->StrictlyEquals(*ng));
2697 EXPECT_FALSE(ng->StrictlyEquals(*ok));
2698 }
2699
TEST(HttpResponseHeadersTest,StrictlyEqualsRawMismatch)2700 TEST(HttpResponseHeadersTest, StrictlyEqualsRawMismatch) {
2701 // These are designed so that the offsets of names and values will be the
2702 // same.
2703 std::string raw1 =
2704 "HTTP/1.1 200\n"
2705 "Pragma :None\n";
2706 std::string raw2 =
2707 "HTTP/1.1 200\n"
2708 "Pragma: None\n";
2709 HeadersToRaw(&raw1);
2710 HeadersToRaw(&raw2);
2711 const auto parsed1 = base::MakeRefCounted<HttpResponseHeaders>(raw1);
2712 const auto parsed2 = base::MakeRefCounted<HttpResponseHeaders>(raw2);
2713 EXPECT_FALSE(parsed1->StrictlyEquals(*parsed2));
2714 EXPECT_FALSE(parsed2->StrictlyEquals(*parsed1));
2715 }
2716
2717 // There's no known way to produce an HttpResponseHeaders object with the same
2718 // `raw_headers_` but different `parsed_` structures, so there's no test for
2719 // that.
2720
2721 } // namespace
2722
2723 } // namespace net
2724