|  | // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "net/http/http_response_headers.h" | 
|  |  | 
|  | #include <algorithm> | 
|  | #include <iostream> | 
|  | #include <limits> | 
|  | #include <memory> | 
|  | #include <unordered_set> | 
|  |  | 
|  | #include "base/pickle.h" | 
|  | #include "base/time/time.h" | 
|  | #include "base/values.h" | 
|  | #include "net/http/http_byte_range.h" | 
|  | #include "net/http/http_util.h" | 
|  | #include "net/log/net_log_capture_mode.h" | 
|  | #include "starboard/types.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  |  | 
|  | namespace net { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | struct TestData { | 
|  | const char* raw_headers; | 
|  | const char* expected_headers; | 
|  | HttpVersion expected_version; | 
|  | int expected_response_code; | 
|  | const char* expected_status_text; | 
|  | }; | 
|  |  | 
|  | class HttpResponseHeadersTest : public testing::Test { | 
|  | }; | 
|  |  | 
|  | // Transform "normal"-looking headers (\n-separated) to the appropriate | 
|  | // input format for ParseRawHeaders (\0-separated). | 
|  | void HeadersToRaw(std::string* headers) { | 
|  | std::replace(headers->begin(), headers->end(), '\n', '\0'); | 
|  | if (!headers->empty()) | 
|  | *headers += '\0'; | 
|  | } | 
|  |  | 
|  | class HttpResponseHeadersCacheControlTest : public HttpResponseHeadersTest { | 
|  | protected: | 
|  | // Make tests less verbose. | 
|  | typedef base::TimeDelta TimeDelta; | 
|  |  | 
|  | // Initilise the headers() value with a Cache-Control header set to | 
|  | // |cache_control|. |cache_control| is copied and so can safely be a | 
|  | // temporary. | 
|  | void InitializeHeadersWithCacheControl(const char* cache_control) { | 
|  | std::string raw_headers("HTTP/1.1 200 OK\n"); | 
|  | raw_headers += "Cache-Control: "; | 
|  | raw_headers += cache_control; | 
|  | raw_headers += "\n"; | 
|  | HeadersToRaw(&raw_headers); | 
|  | headers_ = new HttpResponseHeaders(raw_headers); | 
|  | } | 
|  |  | 
|  | const scoped_refptr<HttpResponseHeaders>& headers() { return headers_; } | 
|  |  | 
|  | // Return a pointer to a TimeDelta object. For use when the value doesn't | 
|  | // matter. | 
|  | TimeDelta* TimeDeltaPointer() { return &delta_; } | 
|  |  | 
|  | // Get the max-age value. This should only be used in tests where a valid | 
|  | // max-age parameter is expected to be present. | 
|  | TimeDelta GetMaxAgeValue() { | 
|  | DCHECK(headers_.get()) << "Call InitializeHeadersWithCacheControl() first"; | 
|  | TimeDelta max_age_value; | 
|  | EXPECT_TRUE(headers()->GetMaxAgeValue(&max_age_value)); | 
|  | return max_age_value; | 
|  | } | 
|  |  | 
|  | // Get the stale-while-revalidate value. This should only be used in tests | 
|  | // where a valid max-age parameter is expected to be present. | 
|  | TimeDelta GetStaleWhileRevalidateValue() { | 
|  | DCHECK(headers_.get()) << "Call InitializeHeadersWithCacheControl() first"; | 
|  | TimeDelta stale_while_revalidate_value; | 
|  | EXPECT_TRUE( | 
|  | headers()->GetStaleWhileRevalidateValue(&stale_while_revalidate_value)); | 
|  | return stale_while_revalidate_value; | 
|  | } | 
|  |  | 
|  | private: | 
|  | scoped_refptr<HttpResponseHeaders> headers_; | 
|  | TimeDelta delta_; | 
|  | }; | 
|  |  | 
|  | class CommonHttpResponseHeadersTest | 
|  | : public HttpResponseHeadersTest, | 
|  | public ::testing::WithParamInterface<TestData> { | 
|  | }; | 
|  |  | 
|  | // Returns a simple text serialization of the given | 
|  | // |HttpResponseHeaders|. This is used by tests to verify that an | 
|  | // |HttpResponseHeaders| matches an expectation string. | 
|  | // | 
|  | //  * One line per header, written as: | 
|  | //        HEADER_NAME: HEADER_VALUE\n | 
|  | //  * The original case of header names is preserved. | 
|  | //  * Whitespace around head names/values is stripped. | 
|  | //  * Repeated headers are not aggregated. | 
|  | //  * Headers are listed in their original order. | 
|  | std::string ToSimpleString(const scoped_refptr<HttpResponseHeaders>& parsed) { | 
|  | std::string result = parsed->GetStatusLine() + "\n"; | 
|  |  | 
|  | size_t iter = 0; | 
|  | std::string name; | 
|  | std::string value; | 
|  | while (parsed->EnumerateHeaderLines(&iter, &name, &value)) { | 
|  | std::string new_line = name + ": " + value + "\n"; | 
|  |  | 
|  | // Verify that |name| and |value| do not contain ':' or '\n' (if they did | 
|  | // it would make this serialized format ambiguous). | 
|  | if (std::count(new_line.begin(), new_line.end(), '\n') != 1 || | 
|  | std::count(new_line.begin(), new_line.end(), ':') != 1) { | 
|  | ADD_FAILURE() << "Unexpected characters in the header name or value: " | 
|  | << new_line; | 
|  | return result; | 
|  | } | 
|  |  | 
|  | result += new_line; | 
|  | } | 
|  |  | 
|  | return result; | 
|  | } | 
|  |  | 
|  | TEST_P(CommonHttpResponseHeadersTest, TestCommon) { | 
|  | const TestData test = GetParam(); | 
|  |  | 
|  | std::string raw_headers(test.raw_headers); | 
|  | HeadersToRaw(&raw_headers); | 
|  | std::string expected_headers(test.expected_headers); | 
|  |  | 
|  | scoped_refptr<HttpResponseHeaders> parsed( | 
|  | new HttpResponseHeaders(raw_headers)); | 
|  | std::string headers = ToSimpleString(parsed); | 
|  |  | 
|  | // Transform to readable output format (so it's easier to see diffs). | 
|  | std::replace(headers.begin(), headers.end(), ' ', '_'); | 
|  | std::replace(headers.begin(), headers.end(), '\n', '\\'); | 
|  | std::replace(expected_headers.begin(), expected_headers.end(), ' ', '_'); | 
|  | std::replace(expected_headers.begin(), expected_headers.end(), '\n', '\\'); | 
|  |  | 
|  | EXPECT_EQ(expected_headers, headers); | 
|  |  | 
|  | EXPECT_TRUE(test.expected_version == parsed->GetHttpVersion()); | 
|  | EXPECT_EQ(test.expected_response_code, parsed->response_code()); | 
|  | EXPECT_EQ(test.expected_status_text, parsed->GetStatusText()); | 
|  | } | 
|  |  | 
|  | TestData response_headers_tests[] = { | 
|  | {// Normalize whitespace. | 
|  | "HTTP/1.1    202   Accepted  \n" | 
|  | "Content-TYPE  : text/html; charset=utf-8  \n" | 
|  | "Set-Cookie: a \n" | 
|  | "Set-Cookie:   b \n", | 
|  |  | 
|  | "HTTP/1.1 202 Accepted\n" | 
|  | "Content-TYPE: text/html; charset=utf-8\n" | 
|  | "Set-Cookie: a\n" | 
|  | "Set-Cookie: b\n", | 
|  |  | 
|  | HttpVersion(1, 1), 202, "Accepted"}, | 
|  | {// Normalize leading whitespace. | 
|  | "HTTP/1.1    202   Accepted  \n" | 
|  | // Starts with space -- will be skipped as invalid. | 
|  | "  Content-TYPE  : text/html; charset=utf-8  \n" | 
|  | "Set-Cookie: a \n" | 
|  | "Set-Cookie:   b \n", | 
|  |  | 
|  | "HTTP/1.1 202 Accepted\n" | 
|  | "Set-Cookie: a\n" | 
|  | "Set-Cookie: b\n", | 
|  |  | 
|  | HttpVersion(1, 1), 202, "Accepted"}, | 
|  | {// Keep whitespace within status text. | 
|  | "HTTP/1.0 404 Not   found  \n", | 
|  |  | 
|  | "HTTP/1.0 404 Not   found\n", | 
|  |  | 
|  | HttpVersion(1, 0), 404, "Not   found"}, | 
|  | {// Normalize blank headers. | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Header1 :          \n" | 
|  | "Header2: \n" | 
|  | "Header3:\n" | 
|  | "Header4\n" | 
|  | "Header5    :\n", | 
|  |  | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Header1: \n" | 
|  | "Header2: \n" | 
|  | "Header3: \n" | 
|  | "Header5: \n", | 
|  |  | 
|  | HttpVersion(1, 1), 200, "OK"}, | 
|  | {// Don't believe the http/0.9 version if there are headers! | 
|  | "hTtP/0.9 201\n" | 
|  | "Content-TYPE: text/html; charset=utf-8\n", | 
|  |  | 
|  | "HTTP/1.0 201\n" | 
|  | "Content-TYPE: text/html; charset=utf-8\n", | 
|  |  | 
|  | HttpVersion(1, 0), 201, ""}, | 
|  | {// Accept the HTTP/0.9 version number if there are no headers. | 
|  | // This is how HTTP/0.9 responses get constructed from | 
|  | // HttpNetworkTransaction. | 
|  | "hTtP/0.9 200 OK\n", | 
|  |  | 
|  | "HTTP/0.9 200 OK\n", | 
|  |  | 
|  | HttpVersion(0, 9), 200, "OK"}, | 
|  | {// Do not add missing status text. | 
|  | "HTTP/1.1 201\n" | 
|  | "Content-TYPE: text/html; charset=utf-8\n", | 
|  |  | 
|  | "HTTP/1.1 201\n" | 
|  | "Content-TYPE: text/html; charset=utf-8\n", | 
|  |  | 
|  | HttpVersion(1, 1), 201, ""}, | 
|  | {// Normalize bad status line. | 
|  | "SCREWED_UP_STATUS_LINE\n" | 
|  | "Content-TYPE: text/html; charset=utf-8\n", | 
|  |  | 
|  | "HTTP/1.0 200 OK\n" | 
|  | "Content-TYPE: text/html; charset=utf-8\n", | 
|  |  | 
|  | HttpVersion(1, 0), 200, "OK"}, | 
|  | {// Normalize bad status line. | 
|  | "Foo bar.", | 
|  |  | 
|  | "HTTP/1.0 200\n", | 
|  |  | 
|  | HttpVersion(1, 0), 200, ""}, | 
|  | {// Normalize invalid status code. | 
|  | "HTTP/1.1 -1  Unknown\n", | 
|  |  | 
|  | "HTTP/1.1 200\n", | 
|  |  | 
|  | HttpVersion(1, 1), 200, ""}, | 
|  | {// Normalize empty header. | 
|  | "", | 
|  |  | 
|  | "HTTP/1.0 200 OK\n", | 
|  |  | 
|  | HttpVersion(1, 0), 200, "OK"}, | 
|  | {// Normalize headers that start with a colon. | 
|  | "HTTP/1.1    202   Accepted  \n" | 
|  | "foo: bar\n" | 
|  | ": a \n" | 
|  | " : b\n" | 
|  | "baz: blat \n", | 
|  |  | 
|  | "HTTP/1.1 202 Accepted\n" | 
|  | "foo: bar\n" | 
|  | "baz: blat\n", | 
|  |  | 
|  | HttpVersion(1, 1), 202, "Accepted"}, | 
|  | {// Normalize headers that end with a colon. | 
|  | "HTTP/1.1    202   Accepted  \n" | 
|  | "foo:   \n" | 
|  | "bar:\n" | 
|  | "baz: blat \n" | 
|  | "zip:\n", | 
|  |  | 
|  | "HTTP/1.1 202 Accepted\n" | 
|  | "foo: \n" | 
|  | "bar: \n" | 
|  | "baz: blat\n" | 
|  | "zip: \n", | 
|  |  | 
|  | HttpVersion(1, 1), 202, "Accepted"}, | 
|  | {// Normalize whitespace headers. | 
|  | "\n   \n", | 
|  |  | 
|  | "HTTP/1.0 200 OK\n", | 
|  |  | 
|  | HttpVersion(1, 0), 200, "OK"}, | 
|  | {// Has multiple Set-Cookie headers. | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Set-Cookie: x=1\n" | 
|  | "Set-Cookie: y=2\n", | 
|  |  | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Set-Cookie: x=1\n" | 
|  | "Set-Cookie: y=2\n", | 
|  |  | 
|  | HttpVersion(1, 1), 200, "OK"}, | 
|  | {// Has multiple cache-control headers. | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Cache-control: private\n" | 
|  | "cache-Control: no-store\n", | 
|  |  | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Cache-control: private\n" | 
|  | "cache-Control: no-store\n", | 
|  |  | 
|  | HttpVersion(1, 1), 200, "OK"}, | 
|  | }; | 
|  |  | 
|  | INSTANTIATE_TEST_CASE_P(HttpResponseHeaders, | 
|  | CommonHttpResponseHeadersTest, | 
|  | testing::ValuesIn(response_headers_tests)); | 
|  |  | 
|  | struct PersistData { | 
|  | HttpResponseHeaders::PersistOptions options; | 
|  | const char* raw_headers; | 
|  | const char* expected_headers; | 
|  | }; | 
|  |  | 
|  | class PersistenceTest | 
|  | : public HttpResponseHeadersTest, | 
|  | public ::testing::WithParamInterface<PersistData> { | 
|  | }; | 
|  |  | 
|  | TEST_P(PersistenceTest, Persist) { | 
|  | const PersistData test = GetParam(); | 
|  |  | 
|  | std::string headers = test.raw_headers; | 
|  | HeadersToRaw(&headers); | 
|  | scoped_refptr<HttpResponseHeaders> parsed1(new HttpResponseHeaders(headers)); | 
|  |  | 
|  | base::Pickle pickle; | 
|  | parsed1->Persist(&pickle, test.options); | 
|  |  | 
|  | base::PickleIterator iter(pickle); | 
|  | scoped_refptr<HttpResponseHeaders> parsed2(new HttpResponseHeaders(&iter)); | 
|  |  | 
|  | EXPECT_EQ(std::string(test.expected_headers), ToSimpleString(parsed2)); | 
|  | } | 
|  |  | 
|  | const struct PersistData persistence_tests[] = { | 
|  | {HttpResponseHeaders::PERSIST_ALL, | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Cache-control:private\n" | 
|  | "cache-Control:no-store\n", | 
|  |  | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Cache-control: private\n" | 
|  | "cache-Control: no-store\n"}, | 
|  | {HttpResponseHeaders::PERSIST_SANS_HOP_BY_HOP, | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "connection: keep-alive\n" | 
|  | "server: blah\n", | 
|  |  | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "server: blah\n"}, | 
|  | {HttpResponseHeaders::PERSIST_SANS_NON_CACHEABLE | | 
|  | HttpResponseHeaders::PERSIST_SANS_HOP_BY_HOP, | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "fOo: 1\n" | 
|  | "Foo: 2\n" | 
|  | "Transfer-Encoding: chunked\n" | 
|  | "CoNnection: keep-alive\n" | 
|  | "cache-control: private, no-cache=\"foo\"\n", | 
|  |  | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "cache-control: private, no-cache=\"foo\"\n"}, | 
|  | {HttpResponseHeaders::PERSIST_SANS_NON_CACHEABLE, | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Foo: 2\n" | 
|  | "Cache-Control: private,no-cache=\"foo, bar\"\n" | 
|  | "bar", | 
|  |  | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Cache-Control: private,no-cache=\"foo, bar\"\n"}, | 
|  | // Ignore bogus no-cache value. | 
|  | {HttpResponseHeaders::PERSIST_SANS_NON_CACHEABLE, | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Foo: 2\n" | 
|  | "Cache-Control: private,no-cache=foo\n", | 
|  |  | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Foo: 2\n" | 
|  | "Cache-Control: private,no-cache=foo\n"}, | 
|  | // Ignore bogus no-cache value. | 
|  | {HttpResponseHeaders::PERSIST_SANS_NON_CACHEABLE, | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Foo: 2\n" | 
|  | "Cache-Control: private, no-cache=\n", | 
|  |  | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Foo: 2\n" | 
|  | "Cache-Control: private, no-cache=\n"}, | 
|  | // Ignore empty no-cache value. | 
|  | {HttpResponseHeaders::PERSIST_SANS_NON_CACHEABLE, | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Foo: 2\n" | 
|  | "Cache-Control: private, no-cache=\"\"\n", | 
|  |  | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Foo: 2\n" | 
|  | "Cache-Control: private, no-cache=\"\"\n"}, | 
|  | // Ignore wrong quotes no-cache value. | 
|  | {HttpResponseHeaders::PERSIST_SANS_NON_CACHEABLE, | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Foo: 2\n" | 
|  | "Cache-Control: private, no-cache=\'foo\'\n", | 
|  |  | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Foo: 2\n" | 
|  | "Cache-Control: private, no-cache=\'foo\'\n"}, | 
|  | // Ignore unterminated quotes no-cache value. | 
|  | {HttpResponseHeaders::PERSIST_SANS_NON_CACHEABLE, | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Foo: 2\n" | 
|  | "Cache-Control: private, no-cache=\"foo\n", | 
|  |  | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Foo: 2\n" | 
|  | "Cache-Control: private, no-cache=\"foo\n"}, | 
|  | // Accept sloppy LWS. | 
|  | {HttpResponseHeaders::PERSIST_SANS_NON_CACHEABLE, | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Foo: 2\n" | 
|  | "Cache-Control: private, no-cache=\" foo\t, bar\"\n", | 
|  |  | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Cache-Control: private, no-cache=\" foo\t, bar\"\n"}, | 
|  | // Header name appears twice, separated by another header. | 
|  | {HttpResponseHeaders::PERSIST_ALL, | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Foo: 1\n" | 
|  | "Bar: 2\n" | 
|  | "Foo: 3\n", | 
|  |  | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Foo: 1\n" | 
|  | "Bar: 2\n" | 
|  | "Foo: 3\n"}, | 
|  | // Header name appears twice, separated by another header (type 2). | 
|  | {HttpResponseHeaders::PERSIST_ALL, | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Foo: 1, 3\n" | 
|  | "Bar: 2\n" | 
|  | "Foo: 4\n", | 
|  |  | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Foo: 1, 3\n" | 
|  | "Bar: 2\n" | 
|  | "Foo: 4\n"}, | 
|  | // Test filtering of cookie headers. | 
|  | {HttpResponseHeaders::PERSIST_SANS_COOKIES, | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Set-Cookie: foo=bar; httponly\n" | 
|  | "Set-Cookie: bar=foo\n" | 
|  | "Bar: 1\n" | 
|  | "Set-Cookie2: bar2=foo2\n", | 
|  |  | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Bar: 1\n"}, | 
|  | {HttpResponseHeaders::PERSIST_SANS_COOKIES, | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Set-Cookie: foo=bar\n" | 
|  | "Foo: 2\n" | 
|  | "Clear-Site-Data: { \"types\" : [ \"cookies\" ] }\n" | 
|  | "Bar: 3\n", | 
|  |  | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Foo: 2\n" | 
|  | "Bar: 3\n"}, | 
|  | // Test LWS at the end of a header. | 
|  | {HttpResponseHeaders::PERSIST_ALL, | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Content-Length: 450   \n" | 
|  | "Content-Encoding: gzip\n", | 
|  |  | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Content-Length: 450\n" | 
|  | "Content-Encoding: gzip\n"}, | 
|  | // Test LWS at the end of a header. | 
|  | {HttpResponseHeaders::PERSIST_RAW, | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Content-Length: 450   \n" | 
|  | "Content-Encoding: gzip\n", | 
|  |  | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Content-Length: 450\n" | 
|  | "Content-Encoding: gzip\n"}, | 
|  | // Test filtering of transport security state headers. | 
|  | {HttpResponseHeaders::PERSIST_SANS_SECURITY_STATE, | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Strict-Transport-Security: max-age=1576800\n" | 
|  | "Bar: 1\n" | 
|  | "Public-Key-Pins: max-age=100000; " | 
|  | "pin-sha256=\"1111111111111111111111111111111111111111111=\";" | 
|  | "pin-sha256=\"2222222222222222222222222222222222222222222=\"", | 
|  |  | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Bar: 1\n"}, | 
|  | }; | 
|  |  | 
|  | INSTANTIATE_TEST_CASE_P(HttpResponseHeaders, | 
|  | PersistenceTest, | 
|  | testing::ValuesIn(persistence_tests)); | 
|  |  | 
|  | TEST(HttpResponseHeadersTest, EnumerateHeader_Coalesced) { | 
|  | // Ensure that commas in quoted strings are not regarded as value separators. | 
|  | // Ensure that whitespace following a value is trimmed properly. | 
|  | std::string headers = | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Cache-control:private , no-cache=\"set-cookie,server\" \n" | 
|  | "cache-Control: no-store\n"; | 
|  | HeadersToRaw(&headers); | 
|  | scoped_refptr<HttpResponseHeaders> parsed(new HttpResponseHeaders(headers)); | 
|  |  | 
|  | size_t iter = 0; | 
|  | std::string value; | 
|  | EXPECT_TRUE(parsed->EnumerateHeader(&iter, "cache-control", &value)); | 
|  | EXPECT_EQ("private", value); | 
|  | EXPECT_TRUE(parsed->EnumerateHeader(&iter, "cache-control", &value)); | 
|  | EXPECT_EQ("no-cache=\"set-cookie,server\"", value); | 
|  | EXPECT_TRUE(parsed->EnumerateHeader(&iter, "cache-control", &value)); | 
|  | EXPECT_EQ("no-store", value); | 
|  | EXPECT_FALSE(parsed->EnumerateHeader(&iter, "cache-control", &value)); | 
|  | } | 
|  |  | 
|  | TEST(HttpResponseHeadersTest, EnumerateHeader_Challenge) { | 
|  | // Even though WWW-Authenticate has commas, it should not be treated as | 
|  | // coalesced values. | 
|  | std::string headers = | 
|  | "HTTP/1.1 401 OK\n" | 
|  | "WWW-Authenticate:Digest realm=foobar, nonce=x, domain=y\n" | 
|  | "WWW-Authenticate:Basic realm=quatar\n"; | 
|  | HeadersToRaw(&headers); | 
|  | scoped_refptr<HttpResponseHeaders> parsed(new HttpResponseHeaders(headers)); | 
|  |  | 
|  | size_t iter = 0; | 
|  | std::string value; | 
|  | EXPECT_TRUE(parsed->EnumerateHeader(&iter, "WWW-Authenticate", &value)); | 
|  | EXPECT_EQ("Digest realm=foobar, nonce=x, domain=y", value); | 
|  | EXPECT_TRUE(parsed->EnumerateHeader(&iter, "WWW-Authenticate", &value)); | 
|  | EXPECT_EQ("Basic realm=quatar", value); | 
|  | EXPECT_FALSE(parsed->EnumerateHeader(&iter, "WWW-Authenticate", &value)); | 
|  | } | 
|  |  | 
|  | TEST(HttpResponseHeadersTest, EnumerateHeader_DateValued) { | 
|  | // The comma in a date valued header should not be treated as a | 
|  | // field-value separator. | 
|  | std::string headers = | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Date: Tue, 07 Aug 2007 23:10:55 GMT\n" | 
|  | "Last-Modified: Wed, 01 Aug 2007 23:23:45 GMT\n"; | 
|  | HeadersToRaw(&headers); | 
|  | scoped_refptr<HttpResponseHeaders> parsed(new HttpResponseHeaders(headers)); | 
|  |  | 
|  | std::string value; | 
|  | EXPECT_TRUE(parsed->EnumerateHeader(NULL, "date", &value)); | 
|  | EXPECT_EQ("Tue, 07 Aug 2007 23:10:55 GMT", value); | 
|  | EXPECT_TRUE(parsed->EnumerateHeader(NULL, "last-modified", &value)); | 
|  | EXPECT_EQ("Wed, 01 Aug 2007 23:23:45 GMT", value); | 
|  | } | 
|  |  | 
|  | TEST(HttpResponseHeadersTest, DefaultDateToGMT) { | 
|  | // Verify we make the best interpretation when parsing dates that incorrectly | 
|  | // do not end in "GMT" as RFC2616 requires. | 
|  | std::string headers = | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Date: Tue, 07 Aug 2007 23:10:55\n" | 
|  | "Last-Modified: Tue, 07 Aug 2007 19:10:55 EDT\n" | 
|  | "Expires: Tue, 07 Aug 2007 23:10:55 UTC\n"; | 
|  | HeadersToRaw(&headers); | 
|  | scoped_refptr<HttpResponseHeaders> parsed(new HttpResponseHeaders(headers)); | 
|  | base::Time expected_value; | 
|  | ASSERT_TRUE(base::Time::FromString("Tue, 07 Aug 2007 23:10:55 GMT", | 
|  | &expected_value)); | 
|  |  | 
|  | base::Time value; | 
|  | // When the timezone is missing, GMT is a good guess as its what RFC2616 | 
|  | // requires. | 
|  | EXPECT_TRUE(parsed->GetDateValue(&value)); | 
|  | EXPECT_EQ(expected_value, value); | 
|  | // If GMT is missing but an RFC822-conforming one is present, use that. | 
|  | EXPECT_TRUE(parsed->GetLastModifiedValue(&value)); | 
|  | EXPECT_EQ(expected_value, value); | 
|  | // If an unknown timezone is present, treat like a missing timezone and | 
|  | // default to GMT.  The only example of a web server not specifying "GMT" | 
|  | // used "UTC" which is equivalent to GMT. | 
|  | if (parsed->GetExpiresValue(&value)) | 
|  | EXPECT_EQ(expected_value, value); | 
|  | } | 
|  |  | 
|  | TEST(HttpResponseHeadersTest, GetAgeValue10) { | 
|  | std::string headers = | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Age: 10\n"; | 
|  | HeadersToRaw(&headers); | 
|  | scoped_refptr<HttpResponseHeaders> parsed(new HttpResponseHeaders(headers)); | 
|  | base::TimeDelta age; | 
|  | ASSERT_TRUE(parsed->GetAgeValue(&age)); | 
|  | EXPECT_EQ(10, age.InSeconds()); | 
|  | } | 
|  |  | 
|  | TEST(HttpResponseHeadersTest, GetAgeValue0) { | 
|  | std::string headers = | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Age: 0\n"; | 
|  | HeadersToRaw(&headers); | 
|  | scoped_refptr<HttpResponseHeaders> parsed(new HttpResponseHeaders(headers)); | 
|  | base::TimeDelta age; | 
|  | ASSERT_TRUE(parsed->GetAgeValue(&age)); | 
|  | EXPECT_EQ(0, age.InSeconds()); | 
|  | } | 
|  |  | 
|  | TEST(HttpResponseHeadersTest, GetAgeValueBogus) { | 
|  | std::string headers = | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Age: donkey\n"; | 
|  | HeadersToRaw(&headers); | 
|  | scoped_refptr<HttpResponseHeaders> parsed(new HttpResponseHeaders(headers)); | 
|  | base::TimeDelta age; | 
|  | ASSERT_FALSE(parsed->GetAgeValue(&age)); | 
|  | } | 
|  |  | 
|  | TEST(HttpResponseHeadersTest, GetAgeValueNegative) { | 
|  | std::string headers = | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Age: -10\n"; | 
|  | HeadersToRaw(&headers); | 
|  | scoped_refptr<HttpResponseHeaders> parsed(new HttpResponseHeaders(headers)); | 
|  | base::TimeDelta age; | 
|  | ASSERT_FALSE(parsed->GetAgeValue(&age)); | 
|  | } | 
|  |  | 
|  | TEST(HttpResponseHeadersTest, GetAgeValueLeadingPlus) { | 
|  | std::string headers = | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Age: +10\n"; | 
|  | HeadersToRaw(&headers); | 
|  | scoped_refptr<HttpResponseHeaders> parsed(new HttpResponseHeaders(headers)); | 
|  | base::TimeDelta age; | 
|  | ASSERT_FALSE(parsed->GetAgeValue(&age)); | 
|  | } | 
|  |  | 
|  | TEST(HttpResponseHeadersTest, GetAgeValueOverflow) { | 
|  | std::string headers = | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Age: 999999999999999999999999999999999999999999\n"; | 
|  | HeadersToRaw(&headers); | 
|  | scoped_refptr<HttpResponseHeaders> parsed(new HttpResponseHeaders(headers)); | 
|  | base::TimeDelta age; | 
|  | ASSERT_TRUE(parsed->GetAgeValue(&age)); | 
|  |  | 
|  | // Should have saturated to 2^32 - 1. | 
|  | EXPECT_EQ(static_cast<int64_t>(0xFFFFFFFFL), age.InSeconds()); | 
|  | } | 
|  |  | 
|  | struct ContentTypeTestData { | 
|  | const std::string raw_headers; | 
|  | const std::string mime_type; | 
|  | const bool has_mimetype; | 
|  | const std::string charset; | 
|  | const bool has_charset; | 
|  | const std::string all_content_type; | 
|  | }; | 
|  |  | 
|  | class ContentTypeTest | 
|  | : public HttpResponseHeadersTest, | 
|  | public ::testing::WithParamInterface<ContentTypeTestData> { | 
|  | }; | 
|  |  | 
|  | TEST_P(ContentTypeTest, GetMimeType) { | 
|  | const ContentTypeTestData test = GetParam(); | 
|  |  | 
|  | std::string headers(test.raw_headers); | 
|  | HeadersToRaw(&headers); | 
|  | scoped_refptr<HttpResponseHeaders> parsed(new HttpResponseHeaders(headers)); | 
|  |  | 
|  | std::string value; | 
|  | EXPECT_EQ(test.has_mimetype, parsed->GetMimeType(&value)); | 
|  | EXPECT_EQ(test.mime_type, value); | 
|  | value.clear(); | 
|  | EXPECT_EQ(test.has_charset, parsed->GetCharset(&value)); | 
|  | EXPECT_EQ(test.charset, value); | 
|  | EXPECT_TRUE(parsed->GetNormalizedHeader("content-type", &value)); | 
|  | EXPECT_EQ(test.all_content_type, value); | 
|  | } | 
|  |  | 
|  | // clang-format off | 
|  | const ContentTypeTestData mimetype_tests[] = { | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "Content-type: text/html\n", | 
|  | "text/html", true, | 
|  | "", false, | 
|  | "text/html" }, | 
|  | // Multiple content-type headers should give us the last one. | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "Content-type: text/html\n" | 
|  | "Content-type: text/html\n", | 
|  | "text/html", true, | 
|  | "", false, | 
|  | "text/html, text/html" }, | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "Content-type: text/plain\n" | 
|  | "Content-type: text/html\n" | 
|  | "Content-type: text/plain\n" | 
|  | "Content-type: text/html\n", | 
|  | "text/html", true, | 
|  | "", false, | 
|  | "text/plain, text/html, text/plain, text/html" }, | 
|  | // Test charset parsing. | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "Content-type: text/html\n" | 
|  | "Content-type: text/html; charset=ISO-8859-1\n", | 
|  | "text/html", true, | 
|  | "iso-8859-1", true, | 
|  | "text/html, text/html; charset=ISO-8859-1" }, | 
|  | // Test charset in double quotes. | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "Content-type: text/html\n" | 
|  | "Content-type: text/html; charset=\"ISO-8859-1\"\n", | 
|  | "text/html", true, | 
|  | "iso-8859-1", true, | 
|  | "text/html, text/html; charset=\"ISO-8859-1\"" }, | 
|  | // If there are multiple matching content-type headers, we carry | 
|  | // over the charset value. | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "Content-type: text/html;charset=utf-8\n" | 
|  | "Content-type: text/html\n", | 
|  | "text/html", true, | 
|  | "utf-8", true, | 
|  | "text/html;charset=utf-8, text/html" }, | 
|  | // Regression test for https://crbug.com/772350: | 
|  | // Single quotes are not delimiters but must be treated as part of charset. | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "Content-type: text/html;charset='utf-8'\n" | 
|  | "Content-type: text/html\n", | 
|  | "text/html", true, | 
|  | "'utf-8'", true, | 
|  | "text/html;charset='utf-8', text/html" }, | 
|  | // First charset wins if matching content-type. | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "Content-type: text/html;charset=utf-8\n" | 
|  | "Content-type: text/html;charset=iso-8859-1\n", | 
|  | "text/html", true, | 
|  | "iso-8859-1", true, | 
|  | "text/html;charset=utf-8, text/html;charset=iso-8859-1" }, | 
|  | // Charset is ignored if the content types change. | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "Content-type: text/plain;charset=utf-8\n" | 
|  | "Content-type: text/html\n", | 
|  | "text/html", true, | 
|  | "", false, | 
|  | "text/plain;charset=utf-8, text/html" }, | 
|  | // Empty content-type. | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "Content-type: \n", | 
|  | "", false, | 
|  | "", false, | 
|  | "" }, | 
|  | // Emtpy charset. | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "Content-type: text/html;charset=\n", | 
|  | "text/html", true, | 
|  | "", false, | 
|  | "text/html;charset=" }, | 
|  | // Multiple charsets, first one wins. | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "Content-type: text/html;charset=utf-8; charset=iso-8859-1\n", | 
|  | "text/html", true, | 
|  | "utf-8", true, | 
|  | "text/html;charset=utf-8; charset=iso-8859-1" }, | 
|  | // Multiple params. | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "Content-type: text/html; foo=utf-8; charset=iso-8859-1\n", | 
|  | "text/html", true, | 
|  | "iso-8859-1", true, | 
|  | "text/html; foo=utf-8; charset=iso-8859-1" }, | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "Content-type: text/html ; charset=utf-8 ; bar=iso-8859-1\n", | 
|  | "text/html", true, | 
|  | "utf-8", true, | 
|  | "text/html ; charset=utf-8 ; bar=iso-8859-1" }, | 
|  | // Comma embeded in quotes. | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "Content-type: text/html ; charset=\"utf-8,text/plain\" ;\n", | 
|  | "text/html", true, | 
|  | "utf-8,text/plain", true, | 
|  | "text/html ; charset=\"utf-8,text/plain\" ;" }, | 
|  | // Charset with leading spaces. | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "Content-type: text/html ; charset= \"utf-8\" ;\n", | 
|  | "text/html", true, | 
|  | "utf-8", true, | 
|  | "text/html ; charset= \"utf-8\" ;" }, | 
|  | // Media type comments in mime-type. | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "Content-type: text/html (html)\n", | 
|  | "text/html", true, | 
|  | "", false, | 
|  | "text/html (html)" }, | 
|  | // Incomplete charset= param. | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "Content-type: text/html; char=\n", | 
|  | "text/html", true, | 
|  | "", false, | 
|  | "text/html; char=" }, | 
|  | // Invalid media type: no slash. | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "Content-type: texthtml\n", | 
|  | "", false, | 
|  | "", false, | 
|  | "texthtml" }, | 
|  | // Invalid media type: "*/*". | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "Content-type: */*\n", | 
|  | "", false, | 
|  | "", false, | 
|  | "*/*" }, | 
|  | }; | 
|  | // clang-format on | 
|  |  | 
|  | INSTANTIATE_TEST_CASE_P(HttpResponseHeaders, | 
|  | ContentTypeTest, | 
|  | testing::ValuesIn(mimetype_tests)); | 
|  |  | 
|  | struct RequiresValidationTestData { | 
|  | const char* headers; | 
|  | ValidationType validation_type; | 
|  | }; | 
|  |  | 
|  | class RequiresValidationTest | 
|  | : public HttpResponseHeadersTest, | 
|  | public ::testing::WithParamInterface<RequiresValidationTestData> { | 
|  | }; | 
|  |  | 
|  | TEST_P(RequiresValidationTest, RequiresValidation) { | 
|  | const RequiresValidationTestData test = GetParam(); | 
|  |  | 
|  | base::Time request_time, response_time, current_time; | 
|  | ASSERT_TRUE( | 
|  | base::Time::FromString("Wed, 28 Nov 2007 00:40:09 GMT", &request_time)); | 
|  | ASSERT_TRUE( | 
|  | base::Time::FromString("Wed, 28 Nov 2007 00:40:12 GMT", &response_time)); | 
|  | ASSERT_TRUE( | 
|  | base::Time::FromString("Wed, 28 Nov 2007 00:45:20 GMT", ¤t_time)); | 
|  |  | 
|  | std::string headers(test.headers); | 
|  | HeadersToRaw(&headers); | 
|  | scoped_refptr<HttpResponseHeaders> parsed(new HttpResponseHeaders(headers)); | 
|  |  | 
|  | ValidationType validation_type = | 
|  | parsed->RequiresValidation(request_time, response_time, current_time); | 
|  | EXPECT_EQ(test.validation_type, validation_type); | 
|  | } | 
|  |  | 
|  | const struct RequiresValidationTestData requires_validation_tests[] = { | 
|  | // No expiry info: expires immediately. | 
|  | {"HTTP/1.1 200 OK\n" | 
|  | "\n", | 
|  | VALIDATION_SYNCHRONOUS}, | 
|  | // No expiry info: expires immediately. | 
|  | {"HTTP/1.1 200 OK\n" | 
|  | "\n", | 
|  | VALIDATION_SYNCHRONOUS}, | 
|  | // Valid for a little while. | 
|  | {"HTTP/1.1 200 OK\n" | 
|  | "cache-control: max-age=10000\n" | 
|  | "\n", | 
|  | VALIDATION_NONE}, | 
|  | // Expires in the future. | 
|  | {"HTTP/1.1 200 OK\n" | 
|  | "date: Wed, 28 Nov 2007 00:40:11 GMT\n" | 
|  | "expires: Wed, 28 Nov 2007 01:00:00 GMT\n" | 
|  | "\n", | 
|  | VALIDATION_NONE}, | 
|  | // Already expired. | 
|  | {"HTTP/1.1 200 OK\n" | 
|  | "date: Wed, 28 Nov 2007 00:40:11 GMT\n" | 
|  | "expires: Wed, 28 Nov 2007 00:00:00 GMT\n" | 
|  | "\n", | 
|  | VALIDATION_SYNCHRONOUS}, | 
|  | // Max-age trumps expires. | 
|  | {"HTTP/1.1 200 OK\n" | 
|  | "date: Wed, 28 Nov 2007 00:40:11 GMT\n" | 
|  | "expires: Wed, 28 Nov 2007 00:00:00 GMT\n" | 
|  | "cache-control: max-age=10000\n" | 
|  | "\n", | 
|  | VALIDATION_NONE}, | 
|  | // Last-modified heuristic: modified a while ago. | 
|  | {"HTTP/1.1 200 OK\n" | 
|  | "date: Wed, 28 Nov 2007 00:40:11 GMT\n" | 
|  | "last-modified: Wed, 27 Nov 2007 08:00:00 GMT\n" | 
|  | "\n", | 
|  | VALIDATION_NONE}, | 
|  | {"HTTP/1.1 203 Non-Authoritative Information\n" | 
|  | "date: Wed, 28 Nov 2007 00:40:11 GMT\n" | 
|  | "last-modified: Wed, 27 Nov 2007 08:00:00 GMT\n" | 
|  | "\n", | 
|  | VALIDATION_NONE}, | 
|  | {"HTTP/1.1 206 Partial Content\n" | 
|  | "date: Wed, 28 Nov 2007 00:40:11 GMT\n" | 
|  | "last-modified: Wed, 27 Nov 2007 08:00:00 GMT\n" | 
|  | "\n", | 
|  | VALIDATION_NONE}, | 
|  | // Last-modified heuristic: modified recently. | 
|  | {"HTTP/1.1 200 OK\n" | 
|  | "date: Wed, 28 Nov 2007 00:40:11 GMT\n" | 
|  | "last-modified: Wed, 28 Nov 2007 00:40:10 GMT\n" | 
|  | "\n", | 
|  | VALIDATION_SYNCHRONOUS}, | 
|  | {"HTTP/1.1 203 Non-Authoritative Information\n" | 
|  | "date: Wed, 28 Nov 2007 00:40:11 GMT\n" | 
|  | "last-modified: Wed, 28 Nov 2007 00:40:10 GMT\n" | 
|  | "\n", | 
|  | VALIDATION_SYNCHRONOUS}, | 
|  | {"HTTP/1.1 206 Partial Content\n" | 
|  | "date: Wed, 28 Nov 2007 00:40:11 GMT\n" | 
|  | "last-modified: Wed, 28 Nov 2007 00:40:10 GMT\n" | 
|  | "\n", | 
|  | VALIDATION_SYNCHRONOUS}, | 
|  | // Cached permanent redirect. | 
|  | {"HTTP/1.1 301 Moved Permanently\n" | 
|  | "\n", | 
|  | VALIDATION_NONE}, | 
|  | // Another cached permanent redirect. | 
|  | {"HTTP/1.1 308 Permanent Redirect\n" | 
|  | "\n", | 
|  | VALIDATION_NONE}, | 
|  | // Cached redirect: not reusable even though by default it would be. | 
|  | {"HTTP/1.1 300 Multiple Choices\n" | 
|  | "Cache-Control: no-cache\n" | 
|  | "\n", | 
|  | VALIDATION_SYNCHRONOUS}, | 
|  | // Cached forever by default. | 
|  | {"HTTP/1.1 410 Gone\n" | 
|  | "\n", | 
|  | VALIDATION_NONE}, | 
|  | // Cached temporary redirect: not reusable. | 
|  | {"HTTP/1.1 302 Found\n" | 
|  | "\n", | 
|  | VALIDATION_SYNCHRONOUS}, | 
|  | // Cached temporary redirect: reusable. | 
|  | {"HTTP/1.1 302 Found\n" | 
|  | "cache-control: max-age=10000\n" | 
|  | "\n", | 
|  | VALIDATION_NONE}, | 
|  | // Cache-control: max-age=N overrides expires: date in the past. | 
|  | {"HTTP/1.1 200 OK\n" | 
|  | "date: Wed, 28 Nov 2007 00:40:11 GMT\n" | 
|  | "expires: Wed, 28 Nov 2007 00:20:11 GMT\n" | 
|  | "cache-control: max-age=10000\n" | 
|  | "\n", | 
|  | VALIDATION_NONE}, | 
|  | // Cache-control: no-store overrides expires: in the future. | 
|  | {"HTTP/1.1 200 OK\n" | 
|  | "date: Wed, 28 Nov 2007 00:40:11 GMT\n" | 
|  | "expires: Wed, 29 Nov 2007 00:40:11 GMT\n" | 
|  | "cache-control: no-store,private,no-cache=\"foo\"\n" | 
|  | "\n", | 
|  | VALIDATION_SYNCHRONOUS}, | 
|  | // Pragma: no-cache overrides last-modified heuristic. | 
|  | {"HTTP/1.1 200 OK\n" | 
|  | "date: Wed, 28 Nov 2007 00:40:11 GMT\n" | 
|  | "last-modified: Wed, 27 Nov 2007 08:00:00 GMT\n" | 
|  | "pragma: no-cache\n" | 
|  | "\n", | 
|  | VALIDATION_SYNCHRONOUS}, | 
|  | // max-age has expired, needs synchronous revalidation | 
|  | {"HTTP/1.1 200 OK\n" | 
|  | "date: Wed, 28 Nov 2007 00:40:11 GMT\n" | 
|  | "cache-control: max-age=300\n" | 
|  | "\n", | 
|  | VALIDATION_SYNCHRONOUS}, | 
|  | // max-age has expired, stale-while-revalidate has not, eligible for | 
|  | // asynchronous revalidation | 
|  | {"HTTP/1.1 200 OK\n" | 
|  | "date: Wed, 28 Nov 2007 00:40:11 GMT\n" | 
|  | "cache-control: max-age=300, stale-while-revalidate=3600\n" | 
|  | "\n", | 
|  | VALIDATION_ASYNCHRONOUS}, | 
|  | // max-age and stale-while-revalidate have expired, needs synchronous | 
|  | // revalidation | 
|  | {"HTTP/1.1 200 OK\n" | 
|  | "date: Wed, 28 Nov 2007 00:40:11 GMT\n" | 
|  | "cache-control: max-age=300, stale-while-revalidate=5\n" | 
|  | "\n", | 
|  | VALIDATION_SYNCHRONOUS}, | 
|  | // max-age is 0, stale-while-revalidate is large enough to permit | 
|  | // asynchronous revalidation | 
|  | {"HTTP/1.1 200 OK\n" | 
|  | "date: Wed, 28 Nov 2007 00:40:11 GMT\n" | 
|  | "cache-control: max-age=0, stale-while-revalidate=360\n" | 
|  | "\n", | 
|  | VALIDATION_ASYNCHRONOUS}, | 
|  | // stale-while-revalidate must not override no-cache or similar directives. | 
|  | {"HTTP/1.1 200 OK\n" | 
|  | "date: Wed, 28 Nov 2007 00:40:11 GMT\n" | 
|  | "cache-control: no-cache, stale-while-revalidate=360\n" | 
|  | "\n", | 
|  | VALIDATION_SYNCHRONOUS}, | 
|  | // max-age has not expired, so no revalidation is needed. | 
|  | {"HTTP/1.1 200 OK\n" | 
|  | "date: Wed, 28 Nov 2007 00:40:11 GMT\n" | 
|  | "cache-control: max-age=3600, stale-while-revalidate=3600\n" | 
|  | "\n", | 
|  | VALIDATION_NONE}, | 
|  | // must-revalidate overrides stale-while-revalidate, so synchronous | 
|  | // validation | 
|  | // is needed. | 
|  | {"HTTP/1.1 200 OK\n" | 
|  | "date: Wed, 28 Nov 2007 00:40:11 GMT\n" | 
|  | "cache-control: must-revalidate, max-age=300, " | 
|  | "stale-while-revalidate=3600\n" | 
|  | "\n", | 
|  | VALIDATION_SYNCHRONOUS}, | 
|  |  | 
|  | // TODO(darin): Add many many more tests here. | 
|  | }; | 
|  |  | 
|  | INSTANTIATE_TEST_CASE_P(HttpResponseHeaders, | 
|  | RequiresValidationTest, | 
|  | testing::ValuesIn(requires_validation_tests)); | 
|  |  | 
|  | struct UpdateTestData { | 
|  | const char* orig_headers; | 
|  | const char* new_headers; | 
|  | const char* expected_headers; | 
|  | }; | 
|  |  | 
|  | class UpdateTest | 
|  | : public HttpResponseHeadersTest, | 
|  | public ::testing::WithParamInterface<UpdateTestData> { | 
|  | }; | 
|  |  | 
|  | TEST_P(UpdateTest, Update) { | 
|  | const UpdateTestData test = GetParam(); | 
|  |  | 
|  | std::string orig_headers(test.orig_headers); | 
|  | HeadersToRaw(&orig_headers); | 
|  | scoped_refptr<HttpResponseHeaders> parsed( | 
|  | new HttpResponseHeaders(orig_headers)); | 
|  |  | 
|  | std::string new_headers(test.new_headers); | 
|  | HeadersToRaw(&new_headers); | 
|  | scoped_refptr<HttpResponseHeaders> new_parsed( | 
|  | new HttpResponseHeaders(new_headers)); | 
|  |  | 
|  | parsed->Update(*new_parsed.get()); | 
|  |  | 
|  | EXPECT_EQ(std::string(test.expected_headers), ToSimpleString(parsed)); | 
|  | } | 
|  |  | 
|  | const UpdateTestData update_tests[] = { | 
|  | { "HTTP/1.1 200 OK\n", | 
|  |  | 
|  | "HTTP/1/1 304 Not Modified\n" | 
|  | "connection: keep-alive\n" | 
|  | "Cache-control: max-age=10000\n", | 
|  |  | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Cache-control: max-age=10000\n" | 
|  | }, | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "Foo: 1\n" | 
|  | "Cache-control: private\n", | 
|  |  | 
|  | "HTTP/1/1 304 Not Modified\n" | 
|  | "connection: keep-alive\n" | 
|  | "Cache-control: max-age=10000\n", | 
|  |  | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Cache-control: max-age=10000\n" | 
|  | "Foo: 1\n" | 
|  | }, | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "Foo: 1\n" | 
|  | "Cache-control: private\n", | 
|  |  | 
|  | "HTTP/1/1 304 Not Modified\n" | 
|  | "connection: keep-alive\n" | 
|  | "Cache-CONTROL: max-age=10000\n", | 
|  |  | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Cache-CONTROL: max-age=10000\n" | 
|  | "Foo: 1\n" | 
|  | }, | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "Content-Length: 450\n", | 
|  |  | 
|  | "HTTP/1/1 304 Not Modified\n" | 
|  | "connection: keep-alive\n" | 
|  | "Cache-control:      max-age=10001   \n", | 
|  |  | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Cache-control: max-age=10001\n" | 
|  | "Content-Length: 450\n" | 
|  | }, | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "X-Frame-Options: DENY\n", | 
|  |  | 
|  | "HTTP/1/1 304 Not Modified\n" | 
|  | "X-Frame-Options: ALLOW\n", | 
|  |  | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "X-Frame-Options: DENY\n", | 
|  | }, | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "X-WebKit-CSP: default-src 'none'\n", | 
|  |  | 
|  | "HTTP/1/1 304 Not Modified\n" | 
|  | "X-WebKit-CSP: default-src *\n", | 
|  |  | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "X-WebKit-CSP: default-src 'none'\n", | 
|  | }, | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "X-XSS-Protection: 1\n", | 
|  |  | 
|  | "HTTP/1/1 304 Not Modified\n" | 
|  | "X-XSS-Protection: 0\n", | 
|  |  | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "X-XSS-Protection: 1\n", | 
|  | }, | 
|  | { "HTTP/1.1 200 OK\n", | 
|  |  | 
|  | "HTTP/1/1 304 Not Modified\n" | 
|  | "X-Content-Type-Options: nosniff\n", | 
|  |  | 
|  | "HTTP/1.1 200 OK\n" | 
|  | }, | 
|  | }; | 
|  |  | 
|  | INSTANTIATE_TEST_CASE_P(HttpResponseHeaders, | 
|  | UpdateTest, | 
|  | testing::ValuesIn(update_tests)); | 
|  |  | 
|  | struct EnumerateHeaderTestData { | 
|  | const char* headers; | 
|  | const char* expected_lines; | 
|  | }; | 
|  |  | 
|  | class EnumerateHeaderLinesTest | 
|  | : public HttpResponseHeadersTest, | 
|  | public ::testing::WithParamInterface<EnumerateHeaderTestData> { | 
|  | }; | 
|  |  | 
|  | TEST_P(EnumerateHeaderLinesTest, EnumerateHeaderLines) { | 
|  | const EnumerateHeaderTestData test = GetParam(); | 
|  |  | 
|  | std::string headers(test.headers); | 
|  | HeadersToRaw(&headers); | 
|  | scoped_refptr<HttpResponseHeaders> parsed(new HttpResponseHeaders(headers)); | 
|  |  | 
|  | std::string name, value, lines; | 
|  |  | 
|  | size_t iter = 0; | 
|  | while (parsed->EnumerateHeaderLines(&iter, &name, &value)) { | 
|  | lines.append(name); | 
|  | lines.append(": "); | 
|  | lines.append(value); | 
|  | lines.append("\n"); | 
|  | } | 
|  |  | 
|  | EXPECT_EQ(std::string(test.expected_lines), lines); | 
|  | } | 
|  |  | 
|  | const EnumerateHeaderTestData enumerate_header_tests[] = { | 
|  | { "HTTP/1.1 200 OK\n", | 
|  |  | 
|  | "" | 
|  | }, | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "Foo: 1\n", | 
|  |  | 
|  | "Foo: 1\n" | 
|  | }, | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "Foo: 1\n" | 
|  | "Bar: 2\n" | 
|  | "Foo: 3\n", | 
|  |  | 
|  | "Foo: 1\nBar: 2\nFoo: 3\n" | 
|  | }, | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "Foo: 1, 2, 3\n", | 
|  |  | 
|  | "Foo: 1, 2, 3\n" | 
|  | }, | 
|  | }; | 
|  |  | 
|  | INSTANTIATE_TEST_CASE_P(HttpResponseHeaders, | 
|  | EnumerateHeaderLinesTest, | 
|  | testing::ValuesIn(enumerate_header_tests)); | 
|  |  | 
|  | struct IsRedirectTestData { | 
|  | const char* headers; | 
|  | const char* location; | 
|  | bool is_redirect; | 
|  | }; | 
|  |  | 
|  | class IsRedirectTest | 
|  | : public HttpResponseHeadersTest, | 
|  | public ::testing::WithParamInterface<IsRedirectTestData> { | 
|  | }; | 
|  |  | 
|  | TEST_P(IsRedirectTest, IsRedirect) { | 
|  | const IsRedirectTestData test = GetParam(); | 
|  |  | 
|  | std::string headers(test.headers); | 
|  | HeadersToRaw(&headers); | 
|  | scoped_refptr<HttpResponseHeaders> parsed(new HttpResponseHeaders(headers)); | 
|  |  | 
|  | std::string location; | 
|  | EXPECT_EQ(parsed->IsRedirect(&location), test.is_redirect); | 
|  | EXPECT_EQ(location, test.location); | 
|  | } | 
|  |  | 
|  | const IsRedirectTestData is_redirect_tests[] = { | 
|  | { "HTTP/1.1 200 OK\n", | 
|  | "", | 
|  | false | 
|  | }, | 
|  | { "HTTP/1.1 301 Moved\n" | 
|  | "Location: http://foopy/\n", | 
|  | "http://foopy/", | 
|  | true | 
|  | }, | 
|  | { "HTTP/1.1 301 Moved\n" | 
|  | "Location: \t \n", | 
|  | "", | 
|  | false | 
|  | }, | 
|  | // We use the first location header as the target of the redirect. | 
|  | { "HTTP/1.1 301 Moved\n" | 
|  | "Location: http://foo/\n" | 
|  | "Location: http://bar/\n", | 
|  | "http://foo/", | 
|  | true | 
|  | }, | 
|  | // We use the first _valid_ location header as the target of the redirect. | 
|  | { "HTTP/1.1 301 Moved\n" | 
|  | "Location: \n" | 
|  | "Location: http://bar/\n", | 
|  | "http://bar/", | 
|  | true | 
|  | }, | 
|  | // Bug 1050541 (location header with an unescaped comma). | 
|  | { "HTTP/1.1 301 Moved\n" | 
|  | "Location: http://foo/bar,baz.html\n", | 
|  | "http://foo/bar,baz.html", | 
|  | true | 
|  | }, | 
|  | // Bug 1224617 (location header with non-ASCII bytes). | 
|  | { "HTTP/1.1 301 Moved\n" | 
|  | "Location: http://foo/bar?key=\xE4\xF6\xFC\n", | 
|  | "http://foo/bar?key=%E4%F6%FC", | 
|  | true | 
|  | }, | 
|  | // Shift_JIS, Big5, and GBK contain multibyte characters with the trailing | 
|  | // byte falling in the ASCII range. | 
|  | { "HTTP/1.1 301 Moved\n" | 
|  | "Location: http://foo/bar?key=\x81\x5E\xD8\xBF\n", | 
|  | "http://foo/bar?key=%81^%D8%BF", | 
|  | true | 
|  | }, | 
|  | { "HTTP/1.1 301 Moved\n" | 
|  | "Location: http://foo/bar?key=\x82\x40\xBD\xC4\n", | 
|  | "http://foo/bar?key=%82@%BD%C4", | 
|  | true | 
|  | }, | 
|  | { "HTTP/1.1 301 Moved\n" | 
|  | "Location: http://foo/bar?key=\x83\x5C\x82\x5D\xCB\xD7\n", | 
|  | "http://foo/bar?key=%83\\%82]%CB%D7", | 
|  | true | 
|  | }, | 
|  | }; | 
|  |  | 
|  | INSTANTIATE_TEST_CASE_P(HttpResponseHeaders, | 
|  | IsRedirectTest, | 
|  | testing::ValuesIn(is_redirect_tests)); | 
|  |  | 
|  | struct ContentLengthTestData { | 
|  | const char* headers; | 
|  | int64_t expected_len; | 
|  | }; | 
|  |  | 
|  | class GetContentLengthTest | 
|  | : public HttpResponseHeadersTest, | 
|  | public ::testing::WithParamInterface<ContentLengthTestData> { | 
|  | }; | 
|  |  | 
|  | TEST_P(GetContentLengthTest, GetContentLength) { | 
|  | const ContentLengthTestData test = GetParam(); | 
|  |  | 
|  | std::string headers(test.headers); | 
|  | HeadersToRaw(&headers); | 
|  | scoped_refptr<HttpResponseHeaders> parsed(new HttpResponseHeaders(headers)); | 
|  |  | 
|  | EXPECT_EQ(test.expected_len, parsed->GetContentLength()); | 
|  | } | 
|  |  | 
|  | const ContentLengthTestData content_length_tests[] = { | 
|  | {"HTTP/1.1 200 OK\n", -1}, | 
|  | {"HTTP/1.1 200 OK\n" | 
|  | "Content-Length: 10\n", | 
|  | 10}, | 
|  | {"HTTP/1.1 200 OK\n" | 
|  | "Content-Length: \n", | 
|  | -1}, | 
|  | {"HTTP/1.1 200 OK\n" | 
|  | "Content-Length: abc\n", | 
|  | -1}, | 
|  | {"HTTP/1.1 200 OK\n" | 
|  | "Content-Length: -10\n", | 
|  | -1}, | 
|  | {"HTTP/1.1 200 OK\n" | 
|  | "Content-Length:  +10\n", | 
|  | -1}, | 
|  | {"HTTP/1.1 200 OK\n" | 
|  | "Content-Length: 23xb5\n", | 
|  | -1}, | 
|  | {"HTTP/1.1 200 OK\n" | 
|  | "Content-Length: 0xA\n", | 
|  | -1}, | 
|  | {"HTTP/1.1 200 OK\n" | 
|  | "Content-Length: 010\n", | 
|  | 10}, | 
|  | // Content-Length too big, will overflow an int64_t. | 
|  | {"HTTP/1.1 200 OK\n" | 
|  | "Content-Length: 40000000000000000000\n", | 
|  | -1}, | 
|  | {"HTTP/1.1 200 OK\n" | 
|  | "Content-Length:       10\n", | 
|  | 10}, | 
|  | {"HTTP/1.1 200 OK\n" | 
|  | "Content-Length: 10  \n", | 
|  | 10}, | 
|  | {"HTTP/1.1 200 OK\n" | 
|  | "Content-Length: \t10\n", | 
|  | 10}, | 
|  | {"HTTP/1.1 200 OK\n" | 
|  | "Content-Length: \v10\n", | 
|  | -1}, | 
|  | {"HTTP/1.1 200 OK\n" | 
|  | "Content-Length: \f10\n", | 
|  | -1}, | 
|  | {"HTTP/1.1 200 OK\n" | 
|  | "cOnTeNt-LENgth: 33\n", | 
|  | 33}, | 
|  | {"HTTP/1.1 200 OK\n" | 
|  | "Content-Length: 34\r\n", | 
|  | -1}, | 
|  | }; | 
|  |  | 
|  | INSTANTIATE_TEST_CASE_P(HttpResponseHeaders, | 
|  | GetContentLengthTest, | 
|  | testing::ValuesIn(content_length_tests)); | 
|  |  | 
|  | struct ContentRangeTestData { | 
|  | const char* headers; | 
|  | bool expected_return_value; | 
|  | int64_t expected_first_byte_position; | 
|  | int64_t expected_last_byte_position; | 
|  | int64_t expected_instance_size; | 
|  | }; | 
|  |  | 
|  | class ContentRangeTest | 
|  | : public HttpResponseHeadersTest, | 
|  | public ::testing::WithParamInterface<ContentRangeTestData> { | 
|  | }; | 
|  |  | 
|  | TEST_P(ContentRangeTest, GetContentRangeFor206) { | 
|  | const ContentRangeTestData test = GetParam(); | 
|  |  | 
|  | std::string headers(test.headers); | 
|  | HeadersToRaw(&headers); | 
|  | scoped_refptr<HttpResponseHeaders> parsed(new HttpResponseHeaders(headers)); | 
|  |  | 
|  | int64_t first_byte_position; | 
|  | int64_t last_byte_position; | 
|  | int64_t instance_size; | 
|  | bool return_value = parsed->GetContentRangeFor206( | 
|  | &first_byte_position, &last_byte_position, &instance_size); | 
|  | EXPECT_EQ(test.expected_return_value, return_value); | 
|  | EXPECT_EQ(test.expected_first_byte_position, first_byte_position); | 
|  | EXPECT_EQ(test.expected_last_byte_position, last_byte_position); | 
|  | EXPECT_EQ(test.expected_instance_size, instance_size); | 
|  | } | 
|  |  | 
|  | const ContentRangeTestData content_range_tests[] = { | 
|  | {"HTTP/1.1 206 Partial Content", false, -1, -1, -1}, | 
|  | {"HTTP/1.1 206 Partial Content\n" | 
|  | "Content-Range:", | 
|  | false, -1, -1, -1}, | 
|  | {"HTTP/1.1 206 Partial Content\n" | 
|  | "Content-Range: bytes 0-50/51", | 
|  | true, 0, 50, 51}, | 
|  | {"HTTP/1.1 206 Partial Content\n" | 
|  | "Content-Range: bytes 50-0/51", | 
|  | false, -1, -1, -1}, | 
|  | {"HTTP/1.1 416 Requested range not satisfiable\n" | 
|  | "Content-Range: bytes */*", | 
|  | false, -1, -1, -1}, | 
|  | {"HTTP/1.1 206 Partial Content\n" | 
|  | "Content-Range: bytes 0-50/*", | 
|  | false, -1, -1, -1}, | 
|  | }; | 
|  |  | 
|  | INSTANTIATE_TEST_CASE_P(HttpResponseHeaders, | 
|  | ContentRangeTest, | 
|  | testing::ValuesIn(content_range_tests)); | 
|  |  | 
|  | struct KeepAliveTestData { | 
|  | const char* headers; | 
|  | bool expected_keep_alive; | 
|  | }; | 
|  |  | 
|  | // Enable GTest to print KeepAliveTestData in an intelligible way if the test | 
|  | // fails. | 
|  | void PrintTo(const KeepAliveTestData& keep_alive_test_data, | 
|  | std::ostream* os) { | 
|  | *os << "{\"" << keep_alive_test_data.headers << "\", " << std::boolalpha | 
|  | << keep_alive_test_data.expected_keep_alive << "}"; | 
|  | } | 
|  |  | 
|  | class IsKeepAliveTest | 
|  | : public HttpResponseHeadersTest, | 
|  | public ::testing::WithParamInterface<KeepAliveTestData> { | 
|  | }; | 
|  |  | 
|  | TEST_P(IsKeepAliveTest, IsKeepAlive) { | 
|  | const KeepAliveTestData test = GetParam(); | 
|  |  | 
|  | std::string headers(test.headers); | 
|  | HeadersToRaw(&headers); | 
|  | scoped_refptr<HttpResponseHeaders> parsed(new HttpResponseHeaders(headers)); | 
|  |  | 
|  | EXPECT_EQ(test.expected_keep_alive, parsed->IsKeepAlive()); | 
|  | } | 
|  |  | 
|  | const KeepAliveTestData keepalive_tests[] = { | 
|  | // The status line fabricated by HttpNetworkTransaction for a 0.9 response. | 
|  | // Treated as 0.9. | 
|  | { "HTTP/0.9 200 OK", | 
|  | false | 
|  | }, | 
|  | // This could come from a broken server.  Treated as 1.0 because it has a | 
|  | // header. | 
|  | { "HTTP/0.9 200 OK\n" | 
|  | "connection: keep-alive\n", | 
|  | true | 
|  | }, | 
|  | { "HTTP/1.1 200 OK\n", | 
|  | true | 
|  | }, | 
|  | { "HTTP/1.0 200 OK\n", | 
|  | false | 
|  | }, | 
|  | { "HTTP/1.0 200 OK\n" | 
|  | "connection: close\n", | 
|  | false | 
|  | }, | 
|  | { "HTTP/1.0 200 OK\n" | 
|  | "connection: keep-alive\n", | 
|  | true | 
|  | }, | 
|  | { "HTTP/1.0 200 OK\n" | 
|  | "connection: kEeP-AliVe\n", | 
|  | true | 
|  | }, | 
|  | { "HTTP/1.0 200 OK\n" | 
|  | "connection: keep-aliveX\n", | 
|  | false | 
|  | }, | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "connection: close\n", | 
|  | false | 
|  | }, | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "connection: keep-alive\n", | 
|  | true | 
|  | }, | 
|  | { "HTTP/1.0 200 OK\n" | 
|  | "proxy-connection: close\n", | 
|  | false | 
|  | }, | 
|  | { "HTTP/1.0 200 OK\n" | 
|  | "proxy-connection: keep-alive\n", | 
|  | true | 
|  | }, | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "proxy-connection: close\n", | 
|  | false | 
|  | }, | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "proxy-connection: keep-alive\n", | 
|  | true | 
|  | }, | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "Connection: Upgrade, close\n", | 
|  | false | 
|  | }, | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "Connection: Upgrade, keep-alive\n", | 
|  | true | 
|  | }, | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "Connection: Upgrade\n" | 
|  | "Connection: close\n", | 
|  | false | 
|  | }, | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "Connection: Upgrade\n" | 
|  | "Connection: keep-alive\n", | 
|  | true | 
|  | }, | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "Connection: close, Upgrade\n", | 
|  | false | 
|  | }, | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "Connection: keep-alive, Upgrade\n", | 
|  | true | 
|  | }, | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "Connection: Upgrade\n" | 
|  | "Proxy-Connection: close\n", | 
|  | false | 
|  | }, | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "Connection: Upgrade\n" | 
|  | "Proxy-Connection: keep-alive\n", | 
|  | true | 
|  | }, | 
|  | // In situations where the response headers conflict with themselves, use the | 
|  | // first one for backwards-compatibility. | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "Connection: close\n" | 
|  | "Connection: keep-alive\n", | 
|  | false | 
|  | }, | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "Connection: keep-alive\n" | 
|  | "Connection: close\n", | 
|  | true | 
|  | }, | 
|  | { "HTTP/1.0 200 OK\n" | 
|  | "Connection: close\n" | 
|  | "Connection: keep-alive\n", | 
|  | false | 
|  | }, | 
|  | { "HTTP/1.0 200 OK\n" | 
|  | "Connection: keep-alive\n" | 
|  | "Connection: close\n", | 
|  | true | 
|  | }, | 
|  | // Ignore the Proxy-Connection header if at all possible. | 
|  | { "HTTP/1.0 200 OK\n" | 
|  | "Proxy-Connection: keep-alive\n" | 
|  | "Connection: close\n", | 
|  | false | 
|  | }, | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "Proxy-Connection: close\n" | 
|  | "Connection: keep-alive\n", | 
|  | true | 
|  | }, | 
|  | // Older versions of Chrome would have ignored Proxy-Connection in this case, | 
|  | // but it doesn't seem safe. | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "Proxy-Connection: close\n" | 
|  | "Connection: Transfer-Encoding\n", | 
|  | false | 
|  | }, | 
|  | }; | 
|  |  | 
|  | INSTANTIATE_TEST_CASE_P(HttpResponseHeaders, | 
|  | IsKeepAliveTest, | 
|  | testing::ValuesIn(keepalive_tests)); | 
|  |  | 
|  | struct HasStrongValidatorsTestData { | 
|  | const char* headers; | 
|  | bool expected_result; | 
|  | }; | 
|  |  | 
|  | class HasStrongValidatorsTest | 
|  | : public HttpResponseHeadersTest, | 
|  | public ::testing::WithParamInterface<HasStrongValidatorsTestData> { | 
|  | }; | 
|  |  | 
|  | TEST_P(HasStrongValidatorsTest, HasStrongValidators) { | 
|  | const HasStrongValidatorsTestData test = GetParam(); | 
|  |  | 
|  | std::string headers(test.headers); | 
|  | HeadersToRaw(&headers); | 
|  | scoped_refptr<HttpResponseHeaders> parsed(new HttpResponseHeaders(headers)); | 
|  |  | 
|  | EXPECT_EQ(test.expected_result, parsed->HasStrongValidators()); | 
|  | } | 
|  |  | 
|  | const HasStrongValidatorsTestData strong_validators_tests[] = { | 
|  | { "HTTP/0.9 200 OK", | 
|  | false | 
|  | }, | 
|  | { "HTTP/1.0 200 OK\n" | 
|  | "Date: Wed, 28 Nov 2007 01:40:10 GMT\n" | 
|  | "Last-Modified: Wed, 28 Nov 2007 00:40:10 GMT\n" | 
|  | "ETag: \"foo\"\n", | 
|  | false | 
|  | }, | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "Date: Wed, 28 Nov 2007 01:40:10 GMT\n" | 
|  | "Last-Modified: Wed, 28 Nov 2007 00:40:10 GMT\n" | 
|  | "ETag: \"foo\"\n", | 
|  | true | 
|  | }, | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "Date: Wed, 28 Nov 2007 00:41:10 GMT\n" | 
|  | "Last-Modified: Wed, 28 Nov 2007 00:40:10 GMT\n", | 
|  | true | 
|  | }, | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "Date: Wed, 28 Nov 2007 00:41:09 GMT\n" | 
|  | "Last-Modified: Wed, 28 Nov 2007 00:40:10 GMT\n", | 
|  | false | 
|  | }, | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "ETag: \"foo\"\n", | 
|  | true | 
|  | }, | 
|  | // This is not really a weak etag: | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "etag: \"w/foo\"\n", | 
|  | true | 
|  | }, | 
|  | // This is a weak etag: | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "etag: w/\"foo\"\n", | 
|  | false | 
|  | }, | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "etag:    W  /   \"foo\"\n", | 
|  | false | 
|  | } | 
|  | }; | 
|  |  | 
|  | INSTANTIATE_TEST_CASE_P(HttpResponseHeaders, | 
|  | HasStrongValidatorsTest, | 
|  | testing::ValuesIn(strong_validators_tests)); | 
|  |  | 
|  | TEST(HttpResponseHeadersTest, HasValidatorsNone) { | 
|  | std::string headers("HTTP/1.1 200 OK"); | 
|  | HeadersToRaw(&headers); | 
|  | scoped_refptr<HttpResponseHeaders> parsed(new HttpResponseHeaders(headers)); | 
|  | EXPECT_FALSE(parsed->HasValidators()); | 
|  | } | 
|  |  | 
|  | TEST(HttpResponseHeadersTest, HasValidatorsEtag) { | 
|  | std::string headers( | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "etag: \"anything\""); | 
|  | HeadersToRaw(&headers); | 
|  | scoped_refptr<HttpResponseHeaders> parsed(new HttpResponseHeaders(headers)); | 
|  | EXPECT_TRUE(parsed->HasValidators()); | 
|  | } | 
|  |  | 
|  | TEST(HttpResponseHeadersTest, HasValidatorsLastModified) { | 
|  | std::string headers( | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Last-Modified: Wed, 28 Nov 2007 00:40:10 GMT"); | 
|  | HeadersToRaw(&headers); | 
|  | scoped_refptr<HttpResponseHeaders> parsed(new HttpResponseHeaders(headers)); | 
|  | EXPECT_TRUE(parsed->HasValidators()); | 
|  | } | 
|  |  | 
|  | TEST(HttpResponseHeadersTest, HasValidatorsWeakEtag) { | 
|  | std::string headers( | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "etag: W/\"anything\""); | 
|  | HeadersToRaw(&headers); | 
|  | scoped_refptr<HttpResponseHeaders> parsed(new HttpResponseHeaders(headers)); | 
|  | EXPECT_TRUE(parsed->HasValidators()); | 
|  | } | 
|  |  | 
|  | TEST(HttpResponseHeadersTest, GetNormalizedHeaderWithEmptyValues) { | 
|  | std::string headers( | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "a:\n" | 
|  | "b: \n" | 
|  | "c:*\n" | 
|  | "d: *\n" | 
|  | "e:    \n" | 
|  | "a: \n" | 
|  | "b:*\n" | 
|  | "c:\n" | 
|  | "d:*\n" | 
|  | "a:\n"); | 
|  | HeadersToRaw(&headers); | 
|  | auto parsed = base::MakeRefCounted<HttpResponseHeaders>(headers); | 
|  | std::string value; | 
|  |  | 
|  | EXPECT_TRUE(parsed->GetNormalizedHeader("a", &value)); | 
|  | EXPECT_EQ(value, ", , "); | 
|  | EXPECT_TRUE(parsed->GetNormalizedHeader("b", &value)); | 
|  | EXPECT_EQ(value, ", *"); | 
|  | EXPECT_TRUE(parsed->GetNormalizedHeader("c", &value)); | 
|  | EXPECT_EQ(value, "*, "); | 
|  | EXPECT_TRUE(parsed->GetNormalizedHeader("d", &value)); | 
|  | EXPECT_EQ(value, "*, *"); | 
|  | EXPECT_TRUE(parsed->GetNormalizedHeader("e", &value)); | 
|  | EXPECT_EQ(value, ""); | 
|  | EXPECT_FALSE(parsed->GetNormalizedHeader("f", &value)); | 
|  | } | 
|  |  | 
|  | struct AddHeaderTestData { | 
|  | const char* orig_headers; | 
|  | const char* new_header; | 
|  | const char* expected_headers; | 
|  | }; | 
|  |  | 
|  | class AddHeaderTest | 
|  | : public HttpResponseHeadersTest, | 
|  | public ::testing::WithParamInterface<AddHeaderTestData> { | 
|  | }; | 
|  |  | 
|  | TEST_P(AddHeaderTest, AddHeader) { | 
|  | const AddHeaderTestData test = GetParam(); | 
|  |  | 
|  | std::string orig_headers(test.orig_headers); | 
|  | HeadersToRaw(&orig_headers); | 
|  | scoped_refptr<HttpResponseHeaders> parsed( | 
|  | new HttpResponseHeaders(orig_headers)); | 
|  |  | 
|  | std::string new_header(test.new_header); | 
|  | parsed->AddHeader(new_header); | 
|  |  | 
|  | EXPECT_EQ(std::string(test.expected_headers), ToSimpleString(parsed)); | 
|  | } | 
|  |  | 
|  | const AddHeaderTestData add_header_tests[] = { | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "connection: keep-alive\n" | 
|  | "Cache-control: max-age=10000\n", | 
|  |  | 
|  | "Content-Length: 450", | 
|  |  | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "connection: keep-alive\n" | 
|  | "Cache-control: max-age=10000\n" | 
|  | "Content-Length: 450\n" | 
|  | }, | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "connection: keep-alive\n" | 
|  | "Cache-control: max-age=10000    \n", | 
|  |  | 
|  | "Content-Length: 450  ", | 
|  |  | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "connection: keep-alive\n" | 
|  | "Cache-control: max-age=10000\n" | 
|  | "Content-Length: 450\n" | 
|  | }, | 
|  | }; | 
|  |  | 
|  | INSTANTIATE_TEST_CASE_P(HttpResponseHeaders, | 
|  | AddHeaderTest, | 
|  | testing::ValuesIn(add_header_tests)); | 
|  |  | 
|  | struct RemoveHeaderTestData { | 
|  | const char* orig_headers; | 
|  | const char* to_remove; | 
|  | const char* expected_headers; | 
|  | }; | 
|  |  | 
|  | class RemoveHeaderTest | 
|  | : public HttpResponseHeadersTest, | 
|  | public ::testing::WithParamInterface<RemoveHeaderTestData> { | 
|  | }; | 
|  |  | 
|  | TEST_P(RemoveHeaderTest, RemoveHeader) { | 
|  | const RemoveHeaderTestData test = GetParam(); | 
|  |  | 
|  | std::string orig_headers(test.orig_headers); | 
|  | HeadersToRaw(&orig_headers); | 
|  | scoped_refptr<HttpResponseHeaders> parsed( | 
|  | new HttpResponseHeaders(orig_headers)); | 
|  |  | 
|  | std::string name(test.to_remove); | 
|  | parsed->RemoveHeader(name); | 
|  |  | 
|  | EXPECT_EQ(std::string(test.expected_headers), ToSimpleString(parsed)); | 
|  | } | 
|  |  | 
|  | const RemoveHeaderTestData remove_header_tests[] = { | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "connection: keep-alive\n" | 
|  | "Cache-control: max-age=10000\n" | 
|  | "Content-Length: 450\n", | 
|  |  | 
|  | "Content-Length", | 
|  |  | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "connection: keep-alive\n" | 
|  | "Cache-control: max-age=10000\n" | 
|  | }, | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "connection: keep-alive  \n" | 
|  | "Content-Length  : 450  \n" | 
|  | "Cache-control: max-age=10000\n", | 
|  |  | 
|  | "Content-Length", | 
|  |  | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "connection: keep-alive\n" | 
|  | "Cache-control: max-age=10000\n" | 
|  | }, | 
|  | }; | 
|  |  | 
|  | INSTANTIATE_TEST_CASE_P(HttpResponseHeaders, | 
|  | RemoveHeaderTest, | 
|  | testing::ValuesIn(remove_header_tests)); | 
|  |  | 
|  | struct RemoveHeadersTestData { | 
|  | const char* orig_headers; | 
|  | const char* to_remove[2]; | 
|  | const char* expected_headers; | 
|  | }; | 
|  |  | 
|  | class RemoveHeadersTest | 
|  | : public HttpResponseHeadersTest, | 
|  | public ::testing::WithParamInterface<RemoveHeadersTestData> {}; | 
|  |  | 
|  | TEST_P(RemoveHeadersTest, RemoveHeaders) { | 
|  | const RemoveHeadersTestData test = GetParam(); | 
|  |  | 
|  | std::string orig_headers(test.orig_headers); | 
|  | HeadersToRaw(&orig_headers); | 
|  | scoped_refptr<HttpResponseHeaders> parsed( | 
|  | new HttpResponseHeaders(orig_headers)); | 
|  |  | 
|  | std::unordered_set<std::string> to_remove; | 
|  | for (auto* header : test.to_remove) { | 
|  | if (header) | 
|  | to_remove.insert(header); | 
|  | } | 
|  | parsed->RemoveHeaders(to_remove); | 
|  |  | 
|  | EXPECT_EQ(std::string(test.expected_headers), ToSimpleString(parsed)); | 
|  | } | 
|  |  | 
|  | const RemoveHeadersTestData remove_headers_tests[] = { | 
|  | {"HTTP/1.1 200 OK\n" | 
|  | "connection: keep-alive\n" | 
|  | "Cache-control: max-age=10000\n" | 
|  | "Content-Length: 450\n", | 
|  |  | 
|  | {"Content-Length", "CACHE-control"}, | 
|  |  | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "connection: keep-alive\n"}, | 
|  |  | 
|  | {"HTTP/1.1 200 OK\n" | 
|  | "connection: keep-alive\n" | 
|  | "Content-Length: 450\n", | 
|  |  | 
|  | {"foo", "bar"}, | 
|  |  | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "connection: keep-alive\n" | 
|  | "Content-Length: 450\n"}, | 
|  |  | 
|  | {"HTTP/1.1 404 Kinda not OK\n" | 
|  | "connection: keep-alive  \n", | 
|  |  | 
|  | {}, | 
|  |  | 
|  | "HTTP/1.1 404 Kinda not OK\n" | 
|  | "connection: keep-alive\n"}, | 
|  | }; | 
|  |  | 
|  | INSTANTIATE_TEST_CASE_P(HttpResponseHeaders, | 
|  | RemoveHeadersTest, | 
|  | testing::ValuesIn(remove_headers_tests)); | 
|  |  | 
|  | struct RemoveIndividualHeaderTestData { | 
|  | const char* orig_headers; | 
|  | const char* to_remove_name; | 
|  | const char* to_remove_value; | 
|  | const char* expected_headers; | 
|  | }; | 
|  |  | 
|  | class RemoveIndividualHeaderTest | 
|  | : public HttpResponseHeadersTest, | 
|  | public ::testing::WithParamInterface<RemoveIndividualHeaderTestData> { | 
|  | }; | 
|  |  | 
|  | TEST_P(RemoveIndividualHeaderTest, RemoveIndividualHeader) { | 
|  | const RemoveIndividualHeaderTestData test = GetParam(); | 
|  |  | 
|  | std::string orig_headers(test.orig_headers); | 
|  | HeadersToRaw(&orig_headers); | 
|  | scoped_refptr<HttpResponseHeaders> parsed( | 
|  | new HttpResponseHeaders(orig_headers)); | 
|  |  | 
|  | std::string name(test.to_remove_name); | 
|  | std::string value(test.to_remove_value); | 
|  | parsed->RemoveHeaderLine(name, value); | 
|  |  | 
|  | EXPECT_EQ(std::string(test.expected_headers), ToSimpleString(parsed)); | 
|  | } | 
|  |  | 
|  | const RemoveIndividualHeaderTestData remove_individual_header_tests[] = { | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "connection: keep-alive\n" | 
|  | "Cache-control: max-age=10000\n" | 
|  | "Content-Length: 450\n", | 
|  |  | 
|  | "Content-Length", | 
|  |  | 
|  | "450", | 
|  |  | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "connection: keep-alive\n" | 
|  | "Cache-control: max-age=10000\n" | 
|  | }, | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "connection: keep-alive  \n" | 
|  | "Content-Length  : 450  \n" | 
|  | "Cache-control: max-age=10000\n", | 
|  |  | 
|  | "Content-Length", | 
|  |  | 
|  | "450", | 
|  |  | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "connection: keep-alive\n" | 
|  | "Cache-control: max-age=10000\n" | 
|  | }, | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "connection: keep-alive  \n" | 
|  | "Content-Length: 450\n" | 
|  | "Cache-control: max-age=10000\n", | 
|  |  | 
|  | "Content-Length",  // Matching name. | 
|  |  | 
|  | "999",  // Mismatching value. | 
|  |  | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "connection: keep-alive\n" | 
|  | "Content-Length: 450\n" | 
|  | "Cache-control: max-age=10000\n" | 
|  | }, | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "connection: keep-alive  \n" | 
|  | "Foo: bar, baz\n" | 
|  | "Foo: bar\n" | 
|  | "Cache-control: max-age=10000\n", | 
|  |  | 
|  | "Foo", | 
|  |  | 
|  | "bar, baz",  // Space in value. | 
|  |  | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "connection: keep-alive\n" | 
|  | "Foo: bar\n" | 
|  | "Cache-control: max-age=10000\n" | 
|  | }, | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "connection: keep-alive  \n" | 
|  | "Foo: bar, baz\n" | 
|  | "Cache-control: max-age=10000\n", | 
|  |  | 
|  | "Foo", | 
|  |  | 
|  | "baz",  // Only partial match -> ignored. | 
|  |  | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "connection: keep-alive\n" | 
|  | "Foo: bar, baz\n" | 
|  | "Cache-control: max-age=10000\n" | 
|  | }, | 
|  | }; | 
|  |  | 
|  | INSTANTIATE_TEST_CASE_P(HttpResponseHeaders, | 
|  | RemoveIndividualHeaderTest, | 
|  | testing::ValuesIn(remove_individual_header_tests)); | 
|  |  | 
|  | struct ReplaceStatusTestData { | 
|  | const char* orig_headers; | 
|  | const char* new_status; | 
|  | const char* expected_headers; | 
|  | }; | 
|  |  | 
|  | class ReplaceStatusTest | 
|  | : public HttpResponseHeadersTest, | 
|  | public ::testing::WithParamInterface<ReplaceStatusTestData> { | 
|  | }; | 
|  |  | 
|  | TEST_P(ReplaceStatusTest, ReplaceStatus) { | 
|  | const ReplaceStatusTestData test = GetParam(); | 
|  |  | 
|  | std::string orig_headers(test.orig_headers); | 
|  | HeadersToRaw(&orig_headers); | 
|  | scoped_refptr<HttpResponseHeaders> parsed( | 
|  | new HttpResponseHeaders(orig_headers)); | 
|  |  | 
|  | std::string name(test.new_status); | 
|  | parsed->ReplaceStatusLine(name); | 
|  |  | 
|  | EXPECT_EQ(std::string(test.expected_headers), ToSimpleString(parsed)); | 
|  | } | 
|  |  | 
|  | const ReplaceStatusTestData replace_status_tests[] = { | 
|  | { "HTTP/1.1 206 Partial Content\n" | 
|  | "connection: keep-alive\n" | 
|  | "Cache-control: max-age=10000\n" | 
|  | "Content-Length: 450\n", | 
|  |  | 
|  | "HTTP/1.1 200 OK", | 
|  |  | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "connection: keep-alive\n" | 
|  | "Cache-control: max-age=10000\n" | 
|  | "Content-Length: 450\n" | 
|  | }, | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "connection: keep-alive\n", | 
|  |  | 
|  | "HTTP/1.1 304 Not Modified", | 
|  |  | 
|  | "HTTP/1.1 304 Not Modified\n" | 
|  | "connection: keep-alive\n" | 
|  | }, | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "connection: keep-alive  \n" | 
|  | "Content-Length  : 450   \n" | 
|  | "Cache-control: max-age=10000\n", | 
|  |  | 
|  | "HTTP/1//1 304 Not Modified", | 
|  |  | 
|  | "HTTP/1.0 304 Not Modified\n" | 
|  | "connection: keep-alive\n" | 
|  | "Content-Length: 450\n" | 
|  | "Cache-control: max-age=10000\n" | 
|  | }, | 
|  | }; | 
|  |  | 
|  | INSTANTIATE_TEST_CASE_P(HttpResponseHeaders, | 
|  | ReplaceStatusTest, | 
|  | testing::ValuesIn(replace_status_tests)); | 
|  |  | 
|  | struct UpdateWithNewRangeTestData { | 
|  | const char* orig_headers; | 
|  | const char* expected_headers; | 
|  | const char* expected_headers_with_replaced_status; | 
|  | }; | 
|  |  | 
|  | class UpdateWithNewRangeTest | 
|  | : public HttpResponseHeadersTest, | 
|  | public ::testing::WithParamInterface<UpdateWithNewRangeTestData> { | 
|  | }; | 
|  |  | 
|  | TEST_P(UpdateWithNewRangeTest, UpdateWithNewRange) { | 
|  | const UpdateWithNewRangeTestData test = GetParam(); | 
|  |  | 
|  | const HttpByteRange range = HttpByteRange::Bounded(3, 5); | 
|  |  | 
|  | std::string orig_headers(test.orig_headers); | 
|  | std::replace(orig_headers.begin(), orig_headers.end(), '\n', '\0'); | 
|  | scoped_refptr<HttpResponseHeaders> parsed( | 
|  | new HttpResponseHeaders(orig_headers + '\0')); | 
|  | int64_t content_size = parsed->GetContentLength(); | 
|  |  | 
|  | // Update headers without replacing status line. | 
|  | parsed->UpdateWithNewRange(range, content_size, false); | 
|  | EXPECT_EQ(std::string(test.expected_headers), ToSimpleString(parsed)); | 
|  |  | 
|  | // Replace status line too. | 
|  | parsed->UpdateWithNewRange(range, content_size, true); | 
|  | EXPECT_EQ(std::string(test.expected_headers_with_replaced_status), | 
|  | ToSimpleString(parsed)); | 
|  | } | 
|  |  | 
|  | const UpdateWithNewRangeTestData update_range_tests[] = { | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "Content-Length: 450\n", | 
|  |  | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Content-Range: bytes 3-5/450\n" | 
|  | "Content-Length: 3\n", | 
|  |  | 
|  | "HTTP/1.1 206 Partial Content\n" | 
|  | "Content-Range: bytes 3-5/450\n" | 
|  | "Content-Length: 3\n", | 
|  | }, | 
|  | { "HTTP/1.1 200 OK\n" | 
|  | "Content-Length: 5\n", | 
|  |  | 
|  | "HTTP/1.1 200 OK\n" | 
|  | "Content-Range: bytes 3-5/5\n" | 
|  | "Content-Length: 3\n", | 
|  |  | 
|  | "HTTP/1.1 206 Partial Content\n" | 
|  | "Content-Range: bytes 3-5/5\n" | 
|  | "Content-Length: 3\n", | 
|  | }, | 
|  | }; | 
|  |  | 
|  | INSTANTIATE_TEST_CASE_P(HttpResponseHeaders, | 
|  | UpdateWithNewRangeTest, | 
|  | testing::ValuesIn(update_range_tests)); | 
|  |  | 
|  | TEST_F(HttpResponseHeadersCacheControlTest, AbsentMaxAgeReturnsFalse) { | 
|  | InitializeHeadersWithCacheControl("nocache"); | 
|  | EXPECT_FALSE(headers()->GetMaxAgeValue(TimeDeltaPointer())); | 
|  | } | 
|  |  | 
|  | TEST_F(HttpResponseHeadersCacheControlTest, MaxAgeWithNoParameterRejected) { | 
|  | InitializeHeadersWithCacheControl("max-age=,private"); | 
|  | EXPECT_FALSE(headers()->GetMaxAgeValue(TimeDeltaPointer())); | 
|  | } | 
|  |  | 
|  | TEST_F(HttpResponseHeadersCacheControlTest, MaxAgeWithSpaceParameterRejected) { | 
|  | InitializeHeadersWithCacheControl("max-age= ,private"); | 
|  | EXPECT_FALSE(headers()->GetMaxAgeValue(TimeDeltaPointer())); | 
|  | } | 
|  |  | 
|  | TEST_F(HttpResponseHeadersCacheControlTest, | 
|  | MaxAgeWithSpaceBeforeEqualsIsRejected) { | 
|  | InitializeHeadersWithCacheControl("max-age = 7"); | 
|  | EXPECT_FALSE(headers()->GetMaxAgeValue(TimeDeltaPointer())); | 
|  | } | 
|  |  | 
|  | TEST_F(HttpResponseHeadersCacheControlTest, MaxAgeFirstMatchUsed) { | 
|  | InitializeHeadersWithCacheControl("max-age=10, max-age=20"); | 
|  | EXPECT_EQ(TimeDelta::FromSeconds(10), GetMaxAgeValue()); | 
|  | } | 
|  |  | 
|  | TEST_F(HttpResponseHeadersCacheControlTest, MaxAgeBogusFirstMatchUsed) { | 
|  | // "max-age10" isn't parsed as "max-age"; "max-age=now" is parsed as | 
|  | // "max-age=0" and so "max-age=20" is not used. | 
|  | InitializeHeadersWithCacheControl("max-age10, max-age=now, max-age=20"); | 
|  | EXPECT_EQ(TimeDelta::FromSeconds(0), GetMaxAgeValue()); | 
|  | } | 
|  |  | 
|  | TEST_F(HttpResponseHeadersCacheControlTest, MaxAgeCaseInsensitive) { | 
|  | InitializeHeadersWithCacheControl("Max-aGe=15"); | 
|  | EXPECT_EQ(TimeDelta::FromSeconds(15), GetMaxAgeValue()); | 
|  | } | 
|  |  | 
|  | struct MaxAgeTestData { | 
|  | const char* max_age_string; | 
|  | const int64_t expected_seconds; | 
|  | }; | 
|  |  | 
|  | class MaxAgeEdgeCasesTest | 
|  | : public HttpResponseHeadersCacheControlTest, | 
|  | public ::testing::WithParamInterface<MaxAgeTestData> { | 
|  | }; | 
|  |  | 
|  | TEST_P(MaxAgeEdgeCasesTest, MaxAgeEdgeCases) { | 
|  | const MaxAgeTestData test = GetParam(); | 
|  |  | 
|  | std::string max_age = "max-age="; | 
|  | InitializeHeadersWithCacheControl( | 
|  | (max_age + test.max_age_string).c_str()); | 
|  | EXPECT_EQ(test.expected_seconds, GetMaxAgeValue().InSeconds()) | 
|  | << " for max-age=" << test.max_age_string; | 
|  | } | 
|  |  | 
|  | const MaxAgeTestData max_age_tests[] = { | 
|  | {" 1 ", 1},  // Spaces are ignored. | 
|  | {"-1", -1},  // Negative numbers are passed through. | 
|  | {"--1", 0},  // Leading junk gives 0. | 
|  | {"2s", 2},   // Trailing junk is ignored. | 
|  | {"3 days", 3}, | 
|  | {"'4'", 0},    // Single quotes don't work. | 
|  | {"\"5\"", 0},  // Double quotes don't work. | 
|  | {"0x6", 0},    // Hex not parsed as hex. | 
|  | {"7F", 7},     // Hex without 0x still not parsed as hex. | 
|  | {"010", 10},   // Octal not parsed as octal. | 
|  | {"9223372036854", 9223372036854}, | 
|  | //  {"9223372036855", -9223372036854},  // Undefined behaviour. | 
|  | //  {"9223372036854775806", -2},        // Undefined behaviour. | 
|  | {"9223372036854775807", 9223372036854775807}, | 
|  | {"20000000000000000000", | 
|  | std::numeric_limits<int64_t>::max()},  // Overflow int64_t. | 
|  | }; | 
|  |  | 
|  | INSTANTIATE_TEST_CASE_P(HttpResponseHeadersCacheControl, | 
|  | MaxAgeEdgeCasesTest, | 
|  | testing::ValuesIn(max_age_tests)); | 
|  |  | 
|  | TEST_F(HttpResponseHeadersCacheControlTest, | 
|  | AbsentStaleWhileRevalidateReturnsFalse) { | 
|  | InitializeHeadersWithCacheControl("max-age=3600"); | 
|  | EXPECT_FALSE(headers()->GetStaleWhileRevalidateValue(TimeDeltaPointer())); | 
|  | } | 
|  |  | 
|  | TEST_F(HttpResponseHeadersCacheControlTest, | 
|  | StaleWhileRevalidateWithoutValueRejected) { | 
|  | InitializeHeadersWithCacheControl("max-age=3600,stale-while-revalidate="); | 
|  | EXPECT_FALSE(headers()->GetStaleWhileRevalidateValue(TimeDeltaPointer())); | 
|  | } | 
|  |  | 
|  | TEST_F(HttpResponseHeadersCacheControlTest, | 
|  | StaleWhileRevalidateWithInvalidValueTreatedAsZero) { | 
|  | InitializeHeadersWithCacheControl("max-age=3600,stale-while-revalidate=true"); | 
|  | EXPECT_EQ(TimeDelta(), GetStaleWhileRevalidateValue()); | 
|  | } | 
|  |  | 
|  | TEST_F(HttpResponseHeadersCacheControlTest, StaleWhileRevalidateValueReturned) { | 
|  | InitializeHeadersWithCacheControl("max-age=3600,stale-while-revalidate=7200"); | 
|  | EXPECT_EQ(TimeDelta::FromSeconds(7200), GetStaleWhileRevalidateValue()); | 
|  | } | 
|  |  | 
|  | TEST_F(HttpResponseHeadersCacheControlTest, | 
|  | FirstStaleWhileRevalidateValueUsed) { | 
|  | InitializeHeadersWithCacheControl( | 
|  | "stale-while-revalidate=1,stale-while-revalidate=7200"); | 
|  | EXPECT_EQ(TimeDelta::FromSeconds(1), GetStaleWhileRevalidateValue()); | 
|  | } | 
|  |  | 
|  | struct GetCurrentAgeTestData { | 
|  | const char* headers; | 
|  | const char* request_time; | 
|  | const char* response_time; | 
|  | const char* current_time; | 
|  | const int expected_age; | 
|  | }; | 
|  |  | 
|  | class GetCurrentAgeTest | 
|  | : public HttpResponseHeadersTest, | 
|  | public ::testing::WithParamInterface<GetCurrentAgeTestData> { | 
|  | }; | 
|  |  | 
|  | TEST_P(GetCurrentAgeTest, GetCurrentAge) { | 
|  | const GetCurrentAgeTestData test = GetParam(); | 
|  |  | 
|  | base::Time request_time, response_time, current_time; | 
|  | ASSERT_TRUE(base::Time::FromString(test.request_time, &request_time)); | 
|  | ASSERT_TRUE(base::Time::FromString(test.response_time, &response_time)); | 
|  | ASSERT_TRUE(base::Time::FromString(test.current_time, ¤t_time)); | 
|  |  | 
|  | std::string headers(test.headers); | 
|  | HeadersToRaw(&headers); | 
|  | scoped_refptr<HttpResponseHeaders> parsed(new HttpResponseHeaders(headers)); | 
|  |  | 
|  | base::TimeDelta age = | 
|  | parsed->GetCurrentAge(request_time, response_time, current_time); | 
|  | EXPECT_EQ(test.expected_age, age.InSeconds()); | 
|  | } | 
|  |  | 
|  | const struct GetCurrentAgeTestData get_current_age_tests[] = { | 
|  | // Without Date header. | 
|  | {"HTTP/1.1 200 OK\n" | 
|  | "Age: 2", | 
|  | "Fri, 20 Jan 2011 10:40:08 GMT", "Fri, 20 Jan 2011 10:40:12 GMT", | 
|  | "Fri, 20 Jan 2011 10:40:14 GMT", 8}, | 
|  | // Without Age header. | 
|  | {"HTTP/1.1 200 OK\n" | 
|  | "Date: Fri, 20 Jan 2011 10:40:10 GMT\n", | 
|  | "Fri, 20 Jan 2011 10:40:08 GMT", "Fri, 20 Jan 2011 10:40:12 GMT", | 
|  | "Fri, 20 Jan 2011 10:40:14 GMT", 6}, | 
|  | // date_value > response_time with Age header. | 
|  | {"HTTP/1.1 200 OK\n" | 
|  | "Date: Fri, 20 Jan 2011 10:40:14 GMT\n" | 
|  | "Age: 2\n", | 
|  | "Fri, 20 Jan 2011 10:40:08 GMT", "Fri, 20 Jan 2011 10:40:12 GMT", | 
|  | "Fri, 20 Jan 2011 10:40:14 GMT", 8}, | 
|  | // date_value > response_time without Age header. | 
|  | {"HTTP/1.1 200 OK\n" | 
|  | "Date: Fri, 20 Jan 2011 10:40:14 GMT\n", | 
|  | "Fri, 20 Jan 2011 10:40:08 GMT", "Fri, 20 Jan 2011 10:40:12 GMT", | 
|  | "Fri, 20 Jan 2011 10:40:14 GMT", 6}, | 
|  | // apparent_age > corrected_age_value | 
|  | {"HTTP/1.1 200 OK\n" | 
|  | "Date: Fri, 20 Jan 2011 10:40:07 GMT\n" | 
|  | "Age: 0\n", | 
|  | "Fri, 20 Jan 2011 10:40:08 GMT", "Fri, 20 Jan 2011 10:40:12 GMT", | 
|  | "Fri, 20 Jan 2011 10:40:14 GMT", 7}}; | 
|  |  | 
|  | INSTANTIATE_TEST_CASE_P(HttpResponseHeaders, | 
|  | GetCurrentAgeTest, | 
|  | testing::ValuesIn(get_current_age_tests)); | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | }  // namespace net |