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