Home | History | Annotate | Download | only in http
      1 // Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include <algorithm>
      6 
      7 #include "base/basictypes.h"
      8 #include "base/pickle.h"
      9 #include "base/time.h"
     10 #include "net/http/http_response_headers.h"
     11 #include "testing/gtest/include/gtest/gtest.h"
     12 
     13 using namespace std;
     14 using base::Time;
     15 using net::HttpResponseHeaders;
     16 using net::HttpVersion;
     17 
     18 namespace {
     19 
     20 struct TestData {
     21   const char* raw_headers;
     22   const char* expected_headers;
     23   int expected_response_code;
     24   HttpVersion expected_parsed_version;
     25   HttpVersion expected_version;
     26 };
     27 
     28 struct ContentTypeTestData {
     29   const string raw_headers;
     30   const string mime_type;
     31   const bool has_mimetype;
     32   const string charset;
     33   const bool has_charset;
     34   const string all_content_type;
     35 };
     36 
     37 class HttpResponseHeadersTest : public testing::Test {
     38 };
     39 
     40 // Transform "normal"-looking headers (\n-separated) to the appropriate
     41 // input format for ParseRawHeaders (\0-separated).
     42 void HeadersToRaw(std::string* headers) {
     43   replace(headers->begin(), headers->end(), '\n', '\0');
     44   if (!headers->empty())
     45     *headers += '\0';
     46 }
     47 
     48 void TestCommon(const TestData& test) {
     49   string raw_headers(test.raw_headers);
     50   HeadersToRaw(&raw_headers);
     51   string expected_headers(test.expected_headers);
     52 
     53   string headers;
     54   scoped_refptr<HttpResponseHeaders> parsed =
     55       new HttpResponseHeaders(raw_headers);
     56   parsed->GetNormalizedHeaders(&headers);
     57 
     58   // Transform to readable output format (so it's easier to see diffs).
     59   replace(headers.begin(), headers.end(), ' ', '_');
     60   replace(headers.begin(), headers.end(), '\n', '\\');
     61   replace(expected_headers.begin(), expected_headers.end(), ' ', '_');
     62   replace(expected_headers.begin(), expected_headers.end(), '\n', '\\');
     63 
     64   EXPECT_EQ(expected_headers, headers);
     65 
     66   EXPECT_EQ(test.expected_response_code, parsed->response_code());
     67 
     68   EXPECT_TRUE(test.expected_parsed_version == parsed->GetParsedHttpVersion());
     69   EXPECT_TRUE(test.expected_version == parsed->GetHttpVersion());
     70 }
     71 
     72 } // end namespace
     73 
     74 // Check that we normalize headers properly.
     75 TEST(HttpResponseHeadersTest, NormalizeHeadersWhitespace) {
     76   TestData test = {
     77     "HTTP/1.1    202   Accepted  \n"
     78     "Content-TYPE  : text/html; charset=utf-8  \n"
     79     "Set-Cookie: a \n"
     80     "Set-Cookie:   b \n",
     81 
     82     "HTTP/1.1 202 Accepted\n"
     83     "Content-TYPE: text/html; charset=utf-8\n"
     84     "Set-Cookie: a, b\n",
     85 
     86     202,
     87     HttpVersion(1,1),
     88     HttpVersion(1,1)
     89   };
     90   TestCommon(test);
     91 }
     92 
     93 // Check that we normalize headers properly (header name is invalid if starts
     94 // with LWS).
     95 TEST(HttpResponseHeadersTest, NormalizeHeadersLeadingWhitespace) {
     96   TestData test = {
     97     "HTTP/1.1    202   Accepted  \n"
     98     // Starts with space -- will be skipped as invalid.
     99     "  Content-TYPE  : text/html; charset=utf-8  \n"
    100     "Set-Cookie: a \n"
    101     "Set-Cookie:   b \n",
    102 
    103     "HTTP/1.1 202 Accepted\n"
    104     "Set-Cookie: a, b\n",
    105 
    106     202,
    107     HttpVersion(1,1),
    108     HttpVersion(1,1)
    109   };
    110   TestCommon(test);
    111 }
    112 
    113 TEST(HttpResponseHeadersTest, BlankHeaders) {
    114   TestData test = {
    115     "HTTP/1.1 200 OK\n"
    116     "Header1 :          \n"
    117     "Header2: \n"
    118     "Header3:\n"
    119     "Header4\n"
    120     "Header5    :\n",
    121 
    122     "HTTP/1.1 200 OK\n"
    123     "Header1: \n"
    124     "Header2: \n"
    125     "Header3: \n"
    126     "Header5: \n",
    127 
    128     200,
    129     HttpVersion(1,1),
    130     HttpVersion(1,1)
    131   };
    132   TestCommon(test);
    133 }
    134 
    135 TEST(HttpResponseHeadersTest, NormalizeHeadersVersion) {
    136   // Don't believe the http/0.9 version if there are headers!
    137   TestData test = {
    138     "hTtP/0.9 201\n"
    139     "Content-TYPE: text/html; charset=utf-8\n",
    140 
    141     "HTTP/1.0 201 OK\n"
    142     "Content-TYPE: text/html; charset=utf-8\n",
    143 
    144     201,
    145     HttpVersion(0,9),
    146     HttpVersion(1,0)
    147   };
    148   TestCommon(test);
    149 }
    150 
    151 TEST(HttpResponseHeadersTest, PreserveHttp09) {
    152   // Accept the HTTP/0.9 version number if there are no headers.
    153   // This is how HTTP/0.9 responses get constructed from HttpNetworkTransaction.
    154   TestData test = {
    155     "hTtP/0.9 200 OK\n",
    156 
    157     "HTTP/0.9 200 OK\n",
    158 
    159     200,
    160     HttpVersion(0,9),
    161     HttpVersion(0,9)
    162   };
    163   TestCommon(test);
    164 }
    165 
    166 TEST(HttpResponseHeadersTest, NormalizeHeadersMissingOK) {
    167   TestData test = {
    168     "HTTP/1.1 201\n"
    169     "Content-TYPE: text/html; charset=utf-8\n",
    170 
    171     "HTTP/1.1 201 OK\n"
    172     "Content-TYPE: text/html; charset=utf-8\n",
    173 
    174     201,
    175     HttpVersion(1,1),
    176     HttpVersion(1,1)
    177   };
    178   TestCommon(test);
    179 }
    180 
    181 TEST(HttpResponseHeadersTest, NormalizeHeadersBadStatus) {
    182   TestData test = {
    183     "SCREWED_UP_STATUS_LINE\n"
    184     "Content-TYPE: text/html; charset=utf-8\n",
    185 
    186     "HTTP/1.0 200 OK\n"
    187     "Content-TYPE: text/html; charset=utf-8\n",
    188 
    189     200,
    190     HttpVersion(0,0), // Parse error
    191     HttpVersion(1,0)
    192   };
    193   TestCommon(test);
    194 }
    195 
    196 TEST(HttpResponseHeadersTest, NormalizeHeadersEmpty) {
    197   TestData test = {
    198     "",
    199 
    200     "HTTP/1.0 200 OK\n",
    201 
    202     200,
    203     HttpVersion(0,0), // Parse Error
    204     HttpVersion(1,0)
    205   };
    206   TestCommon(test);
    207 }
    208 
    209 TEST(HttpResponseHeadersTest, NormalizeHeadersStartWithColon) {
    210   TestData test = {
    211     "HTTP/1.1    202   Accepted  \n"
    212     "foo: bar\n"
    213     ": a \n"
    214     " : b\n"
    215     "baz: blat \n",
    216 
    217     "HTTP/1.1 202 Accepted\n"
    218     "foo: bar\n"
    219     "baz: blat\n",
    220 
    221     202,
    222     HttpVersion(1,1),
    223     HttpVersion(1,1)
    224   };
    225   TestCommon(test);
    226 }
    227 
    228 TEST(HttpResponseHeadersTest, NormalizeHeadersStartWithColonAtEOL) {
    229   TestData test = {
    230     "HTTP/1.1    202   Accepted  \n"
    231     "foo:   \n"
    232     "bar:\n"
    233     "baz: blat \n"
    234     "zip:\n",
    235 
    236     "HTTP/1.1 202 Accepted\n"
    237     "foo: \n"
    238     "bar: \n"
    239     "baz: blat\n"
    240     "zip: \n",
    241 
    242     202,
    243     HttpVersion(1,1),
    244     HttpVersion(1,1)
    245   };
    246   TestCommon(test);
    247 }
    248 
    249 TEST(HttpResponseHeadersTest, NormalizeHeadersOfWhitepace) {
    250   TestData test = {
    251     "\n   \n",
    252 
    253     "HTTP/1.0 200 OK\n",
    254 
    255     200,
    256     HttpVersion(0,0),  // Parse error
    257     HttpVersion(1,0)
    258   };
    259   TestCommon(test);
    260 }
    261 
    262 TEST(HttpResponseHeadersTest, RepeatedSetCookie) {
    263   TestData test = {
    264     "HTTP/1.1 200 OK\n"
    265     "Set-Cookie: x=1\n"
    266     "Set-Cookie: y=2\n",
    267 
    268     "HTTP/1.1 200 OK\n"
    269     "Set-Cookie: x=1, y=2\n",
    270 
    271     200,
    272     HttpVersion(1,1),
    273     HttpVersion(1,1)
    274   };
    275   TestCommon(test);
    276 }
    277 
    278 TEST(HttpResponseHeadersTest, GetNormalizedHeader) {
    279   std::string headers =
    280     "HTTP/1.1 200 OK\n"
    281     "Cache-control: private\n"
    282     "cache-Control: no-store\n";
    283   HeadersToRaw(&headers);
    284   scoped_refptr<HttpResponseHeaders> parsed = new HttpResponseHeaders(headers);
    285 
    286   std::string value;
    287   EXPECT_TRUE(parsed->GetNormalizedHeader("cache-control", &value));
    288   EXPECT_EQ("private, no-store", value);
    289 }
    290 
    291 TEST(HttpResponseHeadersTest, Persist) {
    292   const struct {
    293     net::HttpResponseHeaders::PersistOptions options;
    294     const char* raw_headers;
    295     const char* expected_headers;
    296   } tests[] = {
    297     { net::HttpResponseHeaders::PERSIST_ALL,
    298       "HTTP/1.1 200 OK\n"
    299       "Cache-control:private\n"
    300       "cache-Control:no-store\n",
    301 
    302       "HTTP/1.1 200 OK\n"
    303       "Cache-control: private, no-store\n"
    304     },
    305     { net::HttpResponseHeaders::PERSIST_SANS_HOP_BY_HOP,
    306       "HTTP/1.1 200 OK\n"
    307       "connection: keep-alive\n"
    308       "server: blah\n",
    309 
    310       "HTTP/1.1 200 OK\n"
    311       "server: blah\n"
    312     },
    313     { net::HttpResponseHeaders::PERSIST_SANS_NON_CACHEABLE |
    314       net::HttpResponseHeaders::PERSIST_SANS_HOP_BY_HOP,
    315       "HTTP/1.1 200 OK\n"
    316       "fOo: 1\n"
    317       "Foo: 2\n"
    318       "Transfer-Encoding: chunked\n"
    319       "CoNnection: keep-alive\n"
    320       "cache-control: private, no-cache=\"foo\"\n",
    321 
    322       "HTTP/1.1 200 OK\n"
    323       "cache-control: private, no-cache=\"foo\"\n"
    324     },
    325     { net::HttpResponseHeaders::PERSIST_SANS_NON_CACHEABLE,
    326       "HTTP/1.1 200 OK\n"
    327       "Foo: 2\n"
    328       "Cache-Control: private,no-cache=\"foo, bar\"\n"
    329       "bar",
    330 
    331       "HTTP/1.1 200 OK\n"
    332       "Cache-Control: private,no-cache=\"foo, bar\"\n"
    333     },
    334     // ignore bogus no-cache value
    335     { net::HttpResponseHeaders::PERSIST_SANS_NON_CACHEABLE,
    336       "HTTP/1.1 200 OK\n"
    337       "Foo: 2\n"
    338       "Cache-Control: private,no-cache=foo\n",
    339 
    340       "HTTP/1.1 200 OK\n"
    341       "Foo: 2\n"
    342       "Cache-Control: private,no-cache=foo\n"
    343     },
    344     // ignore bogus no-cache value
    345     { net::HttpResponseHeaders::PERSIST_SANS_NON_CACHEABLE,
    346       "HTTP/1.1 200 OK\n"
    347       "Foo: 2\n"
    348       "Cache-Control: private, no-cache=\n",
    349 
    350       "HTTP/1.1 200 OK\n"
    351       "Foo: 2\n"
    352       "Cache-Control: private, no-cache=\n"
    353     },
    354     // ignore empty no-cache value
    355     { net::HttpResponseHeaders::PERSIST_SANS_NON_CACHEABLE,
    356       "HTTP/1.1 200 OK\n"
    357       "Foo: 2\n"
    358       "Cache-Control: private, no-cache=\"\"\n",
    359 
    360       "HTTP/1.1 200 OK\n"
    361       "Foo: 2\n"
    362       "Cache-Control: private, no-cache=\"\"\n"
    363     },
    364     // ignore wrong quotes no-cache value
    365     { net::HttpResponseHeaders::PERSIST_SANS_NON_CACHEABLE,
    366       "HTTP/1.1 200 OK\n"
    367       "Foo: 2\n"
    368       "Cache-Control: private, no-cache=\'foo\'\n",
    369 
    370       "HTTP/1.1 200 OK\n"
    371       "Foo: 2\n"
    372       "Cache-Control: private, no-cache=\'foo\'\n"
    373     },
    374     // ignore unterminated quotes no-cache value
    375     { net::HttpResponseHeaders::PERSIST_SANS_NON_CACHEABLE,
    376       "HTTP/1.1 200 OK\n"
    377       "Foo: 2\n"
    378       "Cache-Control: private, no-cache=\"foo\n",
    379 
    380       "HTTP/1.1 200 OK\n"
    381       "Foo: 2\n"
    382       "Cache-Control: private, no-cache=\"foo\n"
    383     },
    384     // accept sloppy LWS
    385     { net::HttpResponseHeaders::PERSIST_SANS_NON_CACHEABLE,
    386       "HTTP/1.1 200 OK\n"
    387       "Foo: 2\n"
    388       "Cache-Control: private, no-cache=\" foo\t, bar\"\n",
    389 
    390       "HTTP/1.1 200 OK\n"
    391       "Cache-Control: private, no-cache=\" foo\t, bar\"\n"
    392     },
    393     // header name appears twice, separated by another header
    394     { net::HttpResponseHeaders::PERSIST_ALL,
    395       "HTTP/1.1 200 OK\n"
    396       "Foo: 1\n"
    397       "Bar: 2\n"
    398       "Foo: 3\n",
    399 
    400       "HTTP/1.1 200 OK\n"
    401       "Foo: 1, 3\n"
    402       "Bar: 2\n"
    403     },
    404     // header name appears twice, separated by another header (type 2)
    405     { net::HttpResponseHeaders::PERSIST_ALL,
    406       "HTTP/1.1 200 OK\n"
    407       "Foo: 1, 3\n"
    408       "Bar: 2\n"
    409       "Foo: 4\n",
    410 
    411       "HTTP/1.1 200 OK\n"
    412       "Foo: 1, 3, 4\n"
    413       "Bar: 2\n"
    414     },
    415     // Test filtering of cookie headers.
    416     { net::HttpResponseHeaders::PERSIST_SANS_COOKIES,
    417       "HTTP/1.1 200 OK\n"
    418       "Set-Cookie: foo=bar; httponly\n"
    419       "Set-Cookie: bar=foo\n"
    420       "Bar: 1\n"
    421       "Set-Cookie2: bar2=foo2\n",
    422 
    423       "HTTP/1.1 200 OK\n"
    424       "Bar: 1\n"
    425     },
    426     // Test LWS at the end of a header.
    427     { net::HttpResponseHeaders::PERSIST_ALL,
    428       "HTTP/1.1 200 OK\n"
    429       "Content-Length: 450   \n"
    430       "Content-Encoding: gzip\n",
    431 
    432       "HTTP/1.1 200 OK\n"
    433       "Content-Length: 450\n"
    434       "Content-Encoding: gzip\n"
    435     },
    436     // Test LWS at the end of a header.
    437     { net::HttpResponseHeaders::PERSIST_RAW,
    438       "HTTP/1.1 200 OK\n"
    439       "Content-Length: 450   \n"
    440       "Content-Encoding: gzip\n",
    441 
    442       "HTTP/1.1 200 OK\n"
    443       "Content-Length: 450\n"
    444       "Content-Encoding: gzip\n"
    445     },
    446   };
    447 
    448   for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
    449     std::string headers = tests[i].raw_headers;
    450     HeadersToRaw(&headers);
    451     scoped_refptr<HttpResponseHeaders> parsed1 =
    452         new HttpResponseHeaders(headers);
    453 
    454     Pickle pickle;
    455     parsed1->Persist(&pickle, tests[i].options);
    456 
    457     void* iter = NULL;
    458     scoped_refptr<HttpResponseHeaders> parsed2 =
    459         new HttpResponseHeaders(pickle, &iter);
    460 
    461     std::string h2;
    462     parsed2->GetNormalizedHeaders(&h2);
    463     EXPECT_EQ(string(tests[i].expected_headers), h2);
    464   }
    465 }
    466 
    467 TEST(HttpResponseHeadersTest, EnumerateHeader_Coalesced) {
    468   // Ensure that commas in quoted strings are not regarded as value separators.
    469   // Ensure that whitespace following a value is trimmed properly
    470   std::string headers =
    471     "HTTP/1.1 200 OK\n"
    472     "Cache-control:private , no-cache=\"set-cookie,server\" \n"
    473     "cache-Control: no-store\n";
    474   HeadersToRaw(&headers);
    475   scoped_refptr<HttpResponseHeaders> parsed = new HttpResponseHeaders(headers);
    476 
    477   void* iter = NULL;
    478   std::string value;
    479   EXPECT_TRUE(parsed->EnumerateHeader(&iter, "cache-control", &value));
    480   EXPECT_EQ("private", value);
    481   EXPECT_TRUE(parsed->EnumerateHeader(&iter, "cache-control", &value));
    482   EXPECT_EQ("no-cache=\"set-cookie,server\"", value);
    483   EXPECT_TRUE(parsed->EnumerateHeader(&iter, "cache-control", &value));
    484   EXPECT_EQ("no-store", value);
    485   EXPECT_FALSE(parsed->EnumerateHeader(&iter, "cache-control", &value));
    486 }
    487 
    488 TEST(HttpResponseHeadersTest, EnumerateHeader_Challenge) {
    489   // Even though WWW-Authenticate has commas, it should not be treated as
    490   // coalesced values.
    491   std::string headers =
    492     "HTTP/1.1 401 OK\n"
    493     "WWW-Authenticate:Digest realm=foobar, nonce=x, domain=y\n"
    494     "WWW-Authenticate:Basic realm=quatar\n";
    495   HeadersToRaw(&headers);
    496   scoped_refptr<HttpResponseHeaders> parsed = new HttpResponseHeaders(headers);
    497 
    498   void* iter = NULL;
    499   std::string value;
    500   EXPECT_TRUE(parsed->EnumerateHeader(&iter, "WWW-Authenticate", &value));
    501   EXPECT_EQ("Digest realm=foobar, nonce=x, domain=y", value);
    502   EXPECT_TRUE(parsed->EnumerateHeader(&iter, "WWW-Authenticate", &value));
    503   EXPECT_EQ("Basic realm=quatar", value);
    504   EXPECT_FALSE(parsed->EnumerateHeader(&iter, "WWW-Authenticate", &value));
    505 }
    506 
    507 TEST(HttpResponseHeadersTest, EnumerateHeader_DateValued) {
    508   // The comma in a date valued header should not be treated as a
    509   // field-value separator
    510   std::string headers =
    511     "HTTP/1.1 200 OK\n"
    512     "Date: Tue, 07 Aug 2007 23:10:55 GMT\n"
    513     "Last-Modified: Wed, 01 Aug 2007 23:23:45 GMT\n";
    514   HeadersToRaw(&headers);
    515   scoped_refptr<HttpResponseHeaders> parsed = new HttpResponseHeaders(headers);
    516 
    517   std::string value;
    518   EXPECT_TRUE(parsed->EnumerateHeader(NULL, "date", &value));
    519   EXPECT_EQ("Tue, 07 Aug 2007 23:10:55 GMT", value);
    520   EXPECT_TRUE(parsed->EnumerateHeader(NULL, "last-modified", &value));
    521   EXPECT_EQ("Wed, 01 Aug 2007 23:23:45 GMT", value);
    522 }
    523 
    524 TEST(HttpResponseHeadersTest, GetMimeType) {
    525   const ContentTypeTestData tests[] = {
    526     { "HTTP/1.1 200 OK\n"
    527         "Content-type: text/html\n",
    528       "text/html", true,
    529       "", false,
    530       "text/html" },
    531     // Multiple content-type headers should give us the last one.
    532     { "HTTP/1.1 200 OK\n"
    533         "Content-type: text/html\n"
    534         "Content-type: text/html\n",
    535       "text/html", true,
    536       "", false,
    537       "text/html, text/html" },
    538     { "HTTP/1.1 200 OK\n"
    539         "Content-type: text/plain\n"
    540         "Content-type: text/html\n"
    541         "Content-type: text/plain\n"
    542         "Content-type: text/html\n",
    543       "text/html", true,
    544       "", false,
    545       "text/plain, text/html, text/plain, text/html" },
    546     // Test charset parsing.
    547     { "HTTP/1.1 200 OK\n"
    548         "Content-type: text/html\n"
    549         "Content-type: text/html; charset=ISO-8859-1\n",
    550       "text/html", true,
    551       "iso-8859-1", true,
    552       "text/html, text/html; charset=ISO-8859-1" },
    553     // Test charset in double quotes.
    554     { "HTTP/1.1 200 OK\n"
    555         "Content-type: text/html\n"
    556         "Content-type: text/html; charset=\"ISO-8859-1\"\n",
    557       "text/html", true,
    558       "iso-8859-1", true,
    559       "text/html, text/html; charset=\"ISO-8859-1\"" },
    560     // If there are multiple matching content-type headers, we carry
    561     // over the charset value.
    562     { "HTTP/1.1 200 OK\n"
    563         "Content-type: text/html;charset=utf-8\n"
    564         "Content-type: text/html\n",
    565       "text/html", true,
    566       "utf-8", true,
    567       "text/html;charset=utf-8, text/html" },
    568     // Test single quotes.
    569     { "HTTP/1.1 200 OK\n"
    570         "Content-type: text/html;charset='utf-8'\n"
    571         "Content-type: text/html\n",
    572       "text/html", true,
    573       "utf-8", true,
    574       "text/html;charset='utf-8', text/html" },
    575     // Last charset wins if matching content-type.
    576     { "HTTP/1.1 200 OK\n"
    577         "Content-type: text/html;charset=utf-8\n"
    578         "Content-type: text/html;charset=iso-8859-1\n",
    579       "text/html", true,
    580       "iso-8859-1", true,
    581       "text/html;charset=utf-8, text/html;charset=iso-8859-1" },
    582     // Charset is ignored if the content types change.
    583     { "HTTP/1.1 200 OK\n"
    584         "Content-type: text/plain;charset=utf-8\n"
    585         "Content-type: text/html\n",
    586       "text/html", true,
    587       "", false,
    588       "text/plain;charset=utf-8, text/html" },
    589     // Empty content-type
    590     { "HTTP/1.1 200 OK\n"
    591         "Content-type: \n",
    592       "", false,
    593       "", false,
    594       "" },
    595     // Emtpy charset
    596     { "HTTP/1.1 200 OK\n"
    597         "Content-type: text/html;charset=\n",
    598       "text/html", true,
    599       "", false,
    600       "text/html;charset=" },
    601     // Multiple charsets, last one wins.
    602     { "HTTP/1.1 200 OK\n"
    603         "Content-type: text/html;charset=utf-8; charset=iso-8859-1\n",
    604       "text/html", true,
    605       "iso-8859-1", true,
    606       "text/html;charset=utf-8; charset=iso-8859-1" },
    607     // Multiple params.
    608     { "HTTP/1.1 200 OK\n"
    609         "Content-type: text/html; foo=utf-8; charset=iso-8859-1\n",
    610       "text/html", true,
    611       "iso-8859-1", true,
    612       "text/html; foo=utf-8; charset=iso-8859-1" },
    613     { "HTTP/1.1 200 OK\n"
    614         "Content-type: text/html ; charset=utf-8 ; bar=iso-8859-1\n",
    615       "text/html", true,
    616       "utf-8", true,
    617       "text/html ; charset=utf-8 ; bar=iso-8859-1" },
    618     // Comma embeded in quotes.
    619     { "HTTP/1.1 200 OK\n"
    620         "Content-type: text/html ; charset='utf-8,text/plain' ;\n",
    621       "text/html", true,
    622       "utf-8,text/plain", true,
    623       "text/html ; charset='utf-8,text/plain' ;" },
    624     // Charset with leading spaces.
    625     { "HTTP/1.1 200 OK\n"
    626         "Content-type: text/html ; charset= 'utf-8' ;\n",
    627       "text/html", true,
    628       "utf-8", true,
    629       "text/html ; charset= 'utf-8' ;" },
    630     // Media type comments in mime-type.
    631     { "HTTP/1.1 200 OK\n"
    632         "Content-type: text/html (html)\n",
    633       "text/html", true,
    634       "", false,
    635       "text/html (html)" },
    636     // Incomplete charset= param
    637     { "HTTP/1.1 200 OK\n"
    638         "Content-type: text/html; char=\n",
    639       "text/html", true,
    640       "", false,
    641       "text/html; char=" },
    642     // Invalid media type: no slash
    643     { "HTTP/1.1 200 OK\n"
    644         "Content-type: texthtml\n",
    645       "", false,
    646       "", false,
    647       "texthtml" },
    648     // Invalid media type: */*
    649     { "HTTP/1.1 200 OK\n"
    650         "Content-type: */*\n",
    651       "", false,
    652       "", false,
    653       "*/*" },
    654   };
    655 
    656   for (size_t i = 0; i < arraysize(tests); ++i) {
    657     string headers(tests[i].raw_headers);
    658     HeadersToRaw(&headers);
    659     scoped_refptr<HttpResponseHeaders> parsed =
    660         new HttpResponseHeaders(headers);
    661 
    662     std::string value;
    663     EXPECT_EQ(tests[i].has_mimetype, parsed->GetMimeType(&value));
    664     EXPECT_EQ(tests[i].mime_type, value);
    665     value.clear();
    666     EXPECT_EQ(tests[i].has_charset, parsed->GetCharset(&value));
    667     EXPECT_EQ(tests[i].charset, value);
    668     EXPECT_TRUE(parsed->GetNormalizedHeader("content-type", &value));
    669     EXPECT_EQ(tests[i].all_content_type, value);
    670   }
    671 }
    672 
    673 TEST(HttpResponseHeadersTest, RequiresValidation) {
    674   const struct {
    675     const char* headers;
    676     bool requires_validation;
    677   } tests[] = {
    678     // no expiry info: expires immediately
    679     { "HTTP/1.1 200 OK\n"
    680       "\n",
    681       true
    682     },
    683     // valid for a little while
    684     { "HTTP/1.1 200 OK\n"
    685       "cache-control: max-age=10000\n"
    686       "\n",
    687       false
    688     },
    689     // expires in the future
    690     { "HTTP/1.1 200 OK\n"
    691       "date: Wed, 28 Nov 2007 00:40:11 GMT\n"
    692       "expires: Wed, 28 Nov 2007 01:00:00 GMT\n"
    693       "\n",
    694       false
    695     },
    696     // expired already
    697     { "HTTP/1.1 200 OK\n"
    698       "date: Wed, 28 Nov 2007 00:40:11 GMT\n"
    699       "expires: Wed, 28 Nov 2007 00:00:00 GMT\n"
    700       "\n",
    701       true
    702     },
    703     // max-age trumps expires
    704     { "HTTP/1.1 200 OK\n"
    705       "date: Wed, 28 Nov 2007 00:40:11 GMT\n"
    706       "expires: Wed, 28 Nov 2007 00:00:00 GMT\n"
    707       "cache-control: max-age=10000\n"
    708       "\n",
    709       false
    710     },
    711     // last-modified heuristic: modified a while ago
    712     { "HTTP/1.1 200 OK\n"
    713       "date: Wed, 28 Nov 2007 00:40:11 GMT\n"
    714       "last-modified: Wed, 27 Nov 2007 08:00:00 GMT\n"
    715       "\n",
    716       false
    717     },
    718     { "HTTP/1.1 203 Non-Authoritative Information\n"
    719       "date: Wed, 28 Nov 2007 00:40:11 GMT\n"
    720       "last-modified: Wed, 27 Nov 2007 08:00:00 GMT\n"
    721       "\n",
    722       false
    723     },
    724     { "HTTP/1.1 206 Partial Content\n"
    725       "date: Wed, 28 Nov 2007 00:40:11 GMT\n"
    726       "last-modified: Wed, 27 Nov 2007 08:00:00 GMT\n"
    727       "\n",
    728       false
    729     },
    730     // last-modified heuristic: modified recently
    731     { "HTTP/1.1 200 OK\n"
    732       "date: Wed, 28 Nov 2007 00:40:11 GMT\n"
    733       "last-modified: Wed, 28 Nov 2007 00:40:10 GMT\n"
    734       "\n",
    735       true
    736     },
    737     { "HTTP/1.1 203 Non-Authoritative Information\n"
    738       "date: Wed, 28 Nov 2007 00:40:11 GMT\n"
    739       "last-modified: Wed, 28 Nov 2007 00:40:10 GMT\n"
    740       "\n",
    741       true
    742     },
    743     { "HTTP/1.1 206 Partial Content\n"
    744       "date: Wed, 28 Nov 2007 00:40:11 GMT\n"
    745       "last-modified: Wed, 28 Nov 2007 00:40:10 GMT\n"
    746       "\n",
    747       true
    748     },
    749     // cached permanent redirect
    750     { "HTTP/1.1 301 Moved Permanently\n"
    751       "\n",
    752       false
    753     },
    754     // cached redirect: not reusable even though by default it would be
    755     { "HTTP/1.1 300 Multiple Choices\n"
    756       "Cache-Control: no-cache\n"
    757       "\n",
    758       true
    759     },
    760     // cached forever by default
    761     { "HTTP/1.1 410 Gone\n"
    762       "\n",
    763       false
    764     },
    765     // cached temporary redirect: not reusable
    766     { "HTTP/1.1 302 Found\n"
    767       "\n",
    768       true
    769     },
    770     // cached temporary redirect: reusable
    771     { "HTTP/1.1 302 Found\n"
    772       "cache-control: max-age=10000\n"
    773       "\n",
    774       false
    775     },
    776     // cache-control: max-age=N overrides expires: date in the past
    777     { "HTTP/1.1 200 OK\n"
    778       "date: Wed, 28 Nov 2007 00:40:11 GMT\n"
    779       "expires: Wed, 28 Nov 2007 00:20:11 GMT\n"
    780       "cache-control: max-age=10000\n"
    781       "\n",
    782       false
    783     },
    784     // cache-control: no-store overrides expires: in the future
    785     { "HTTP/1.1 200 OK\n"
    786       "date: Wed, 28 Nov 2007 00:40:11 GMT\n"
    787       "expires: Wed, 29 Nov 2007 00:40:11 GMT\n"
    788       "cache-control: no-store,private,no-cache=\"foo\"\n"
    789       "\n",
    790       true
    791     },
    792     // pragma: no-cache overrides last-modified heuristic
    793     { "HTTP/1.1 200 OK\n"
    794       "date: Wed, 28 Nov 2007 00:40:11 GMT\n"
    795       "last-modified: Wed, 27 Nov 2007 08:00:00 GMT\n"
    796       "pragma: no-cache\n"
    797       "\n",
    798       true
    799     },
    800     // TODO(darin): add many many more tests here
    801   };
    802   Time request_time, response_time, current_time;
    803   Time::FromString(L"Wed, 28 Nov 2007 00:40:09 GMT", &request_time);
    804   Time::FromString(L"Wed, 28 Nov 2007 00:40:12 GMT", &response_time);
    805   Time::FromString(L"Wed, 28 Nov 2007 00:45:20 GMT", &current_time);
    806 
    807   for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
    808     string headers(tests[i].headers);
    809     HeadersToRaw(&headers);
    810     scoped_refptr<HttpResponseHeaders> parsed =
    811         new HttpResponseHeaders(headers);
    812 
    813     bool requires_validation =
    814         parsed->RequiresValidation(request_time, response_time, current_time);
    815     EXPECT_EQ(tests[i].requires_validation, requires_validation);
    816   }
    817 }
    818 
    819 TEST(HttpResponseHeadersTest, Update) {
    820   const struct {
    821     const char* orig_headers;
    822     const char* new_headers;
    823     const char* expected_headers;
    824   } tests[] = {
    825     { "HTTP/1.1 200 OK\n",
    826 
    827       "HTTP/1/1 304 Not Modified\n"
    828       "connection: keep-alive\n"
    829       "Cache-control: max-age=10000\n",
    830 
    831       "HTTP/1.1 200 OK\n"
    832       "Cache-control: max-age=10000\n"
    833     },
    834     { "HTTP/1.1 200 OK\n"
    835       "Foo: 1\n"
    836       "Cache-control: private\n",
    837 
    838       "HTTP/1/1 304 Not Modified\n"
    839       "connection: keep-alive\n"
    840       "Cache-control: max-age=10000\n",
    841 
    842       "HTTP/1.1 200 OK\n"
    843       "Cache-control: max-age=10000\n"
    844       "Foo: 1\n"
    845     },
    846     { "HTTP/1.1 200 OK\n"
    847       "Foo: 1\n"
    848       "Cache-control: private\n",
    849 
    850       "HTTP/1/1 304 Not Modified\n"
    851       "connection: keep-alive\n"
    852       "Cache-CONTROL: max-age=10000\n",
    853 
    854       "HTTP/1.1 200 OK\n"
    855       "Cache-CONTROL: max-age=10000\n"
    856       "Foo: 1\n"
    857     },
    858     { "HTTP/1.1 200 OK\n"
    859       "Content-Length: 450\n",
    860 
    861       "HTTP/1/1 304 Not Modified\n"
    862       "connection: keep-alive\n"
    863       "Cache-control:      max-age=10001   \n",
    864 
    865       "HTTP/1.1 200 OK\n"
    866       "Cache-control: max-age=10001\n"
    867       "Content-Length: 450\n"
    868     },
    869   };
    870 
    871   for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
    872     string orig_headers(tests[i].orig_headers);
    873     HeadersToRaw(&orig_headers);
    874     scoped_refptr<HttpResponseHeaders> parsed =
    875         new HttpResponseHeaders(orig_headers);
    876 
    877     string new_headers(tests[i].new_headers);
    878     HeadersToRaw(&new_headers);
    879     scoped_refptr<HttpResponseHeaders> new_parsed =
    880         new HttpResponseHeaders(new_headers);
    881 
    882     parsed->Update(*new_parsed);
    883 
    884     string resulting_headers;
    885     parsed->GetNormalizedHeaders(&resulting_headers);
    886     EXPECT_EQ(string(tests[i].expected_headers), resulting_headers);
    887   }
    888 }
    889 
    890 TEST(HttpResponseHeadersTest, EnumerateHeaderLines) {
    891   const struct {
    892     const char* headers;
    893     const char* expected_lines;
    894   } tests[] = {
    895     { "HTTP/1.1 200 OK\n",
    896 
    897       ""
    898     },
    899     { "HTTP/1.1 200 OK\n"
    900       "Foo: 1\n",
    901 
    902       "Foo: 1\n"
    903     },
    904     { "HTTP/1.1 200 OK\n"
    905       "Foo: 1\n"
    906       "Bar: 2\n"
    907       "Foo: 3\n",
    908 
    909       "Foo: 1\nBar: 2\nFoo: 3\n"
    910     },
    911     { "HTTP/1.1 200 OK\n"
    912       "Foo: 1, 2, 3\n",
    913 
    914       "Foo: 1, 2, 3\n"
    915     },
    916   };
    917   for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
    918     string headers(tests[i].headers);
    919     HeadersToRaw(&headers);
    920     scoped_refptr<HttpResponseHeaders> parsed =
    921         new HttpResponseHeaders(headers);
    922 
    923     string name, value, lines;
    924 
    925     void* iter = NULL;
    926     while (parsed->EnumerateHeaderLines(&iter, &name, &value)) {
    927       lines.append(name);
    928       lines.append(": ");
    929       lines.append(value);
    930       lines.append("\n");
    931     }
    932 
    933     EXPECT_EQ(string(tests[i].expected_lines), lines);
    934   }
    935 }
    936 
    937 TEST(HttpResponseHeadersTest, IsRedirect) {
    938   const struct {
    939     const char* headers;
    940     const char* location;
    941     bool is_redirect;
    942   } tests[] = {
    943     { "HTTP/1.1 200 OK\n",
    944       "",
    945       false
    946     },
    947     { "HTTP/1.1 301 Moved\n"
    948       "Location: http://foopy/\n",
    949       "http://foopy/",
    950       true
    951     },
    952     { "HTTP/1.1 301 Moved\n"
    953       "Location: \t \n",
    954       "",
    955       false
    956     },
    957     // we use the first location header as the target of the redirect
    958     { "HTTP/1.1 301 Moved\n"
    959       "Location: http://foo/\n"
    960       "Location: http://bar/\n",
    961       "http://foo/",
    962       true
    963     },
    964     // we use the first _valid_ location header as the target of the redirect
    965     { "HTTP/1.1 301 Moved\n"
    966       "Location: \n"
    967       "Location: http://bar/\n",
    968       "http://bar/",
    969       true
    970     },
    971     // bug 1050541 (location header w/ an unescaped comma)
    972     { "HTTP/1.1 301 Moved\n"
    973       "Location: http://foo/bar,baz.html\n",
    974       "http://foo/bar,baz.html",
    975       true
    976     },
    977     // bug 1224617 (location header w/ non-ASCII bytes)
    978     { "HTTP/1.1 301 Moved\n"
    979       "Location: http://foo/bar?key=\xE4\xF6\xFC\n",
    980       "http://foo/bar?key=%E4%F6%FC",
    981       true
    982     },
    983     // Shift_JIS, Big5, and GBK contain multibyte characters with the trailing
    984     // byte falling in the ASCII range.
    985     { "HTTP/1.1 301 Moved\n"
    986       "Location: http://foo/bar?key=\x81\x5E\xD8\xBF\n",
    987       "http://foo/bar?key=%81^%D8%BF",
    988       true
    989     },
    990     { "HTTP/1.1 301 Moved\n"
    991       "Location: http://foo/bar?key=\x82\x40\xBD\xC4\n",
    992       "http://foo/bar?key=%82@%BD%C4",
    993       true
    994     },
    995     { "HTTP/1.1 301 Moved\n"
    996       "Location: http://foo/bar?key=\x83\x5C\x82\x5D\xCB\xD7\n",
    997       "http://foo/bar?key=%83\\%82]%CB%D7",
    998       true
    999     },
   1000   };
   1001   for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
   1002     string headers(tests[i].headers);
   1003     HeadersToRaw(&headers);
   1004     scoped_refptr<HttpResponseHeaders> parsed =
   1005         new HttpResponseHeaders(headers);
   1006 
   1007     std::string location;
   1008     EXPECT_EQ(parsed->IsRedirect(&location), tests[i].is_redirect);
   1009     EXPECT_EQ(location, tests[i].location);
   1010   }
   1011 }
   1012 
   1013 TEST(HttpResponseHeadersTest, GetContentLength) {
   1014   const struct {
   1015     const char* headers;
   1016     int64 expected_len;
   1017   } tests[] = {
   1018     { "HTTP/1.1 200 OK\n",
   1019       -1
   1020     },
   1021     { "HTTP/1.1 200 OK\n"
   1022       "Content-Length: 10\n",
   1023       10
   1024     },
   1025     { "HTTP/1.1 200 OK\n"
   1026       "Content-Length: \n",
   1027       -1
   1028     },
   1029     { "HTTP/1.1 200 OK\n"
   1030       "Content-Length: abc\n",
   1031       -1
   1032     },
   1033     { "HTTP/1.1 200 OK\n"
   1034       "Content-Length: -10\n",
   1035       -1
   1036     },
   1037     { "HTTP/1.1 200 OK\n"
   1038       "Content-Length:  +10\n",
   1039       -1
   1040     },
   1041     { "HTTP/1.1 200 OK\n"
   1042       "Content-Length: 23xb5\n",
   1043       -1
   1044     },
   1045     { "HTTP/1.1 200 OK\n"
   1046       "Content-Length: 0xA\n",
   1047       -1
   1048     },
   1049     { "HTTP/1.1 200 OK\n"
   1050       "Content-Length: 010\n",
   1051       10
   1052     },
   1053     // Content-Length too big, will overflow an int64
   1054     { "HTTP/1.1 200 OK\n"
   1055       "Content-Length: 40000000000000000000\n",
   1056       -1
   1057     },
   1058     { "HTTP/1.1 200 OK\n"
   1059       "Content-Length:       10\n",
   1060       10
   1061     },
   1062     { "HTTP/1.1 200 OK\n"
   1063       "Content-Length: 10  \n",
   1064       10
   1065     },
   1066     { "HTTP/1.1 200 OK\n"
   1067       "Content-Length: \t10\n",
   1068       10
   1069     },
   1070     { "HTTP/1.1 200 OK\n"
   1071       "Content-Length: \v10\n",
   1072       -1
   1073     },
   1074     { "HTTP/1.1 200 OK\n"
   1075       "Content-Length: \f10\n",
   1076       -1
   1077     },
   1078     { "HTTP/1.1 200 OK\n"
   1079       "cOnTeNt-LENgth: 33\n",
   1080       33
   1081     },
   1082     { "HTTP/1.1 200 OK\n"
   1083       "Content-Length: 34\r\n",
   1084       -1
   1085     },
   1086   };
   1087   for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
   1088     string headers(tests[i].headers);
   1089     HeadersToRaw(&headers);
   1090     scoped_refptr<HttpResponseHeaders> parsed =
   1091         new HttpResponseHeaders(headers);
   1092 
   1093     EXPECT_EQ(tests[i].expected_len, parsed->GetContentLength());
   1094   }
   1095 }
   1096 
   1097 TEST(HttpResponseHeaders, GetContentRange) {
   1098   const struct {
   1099     const char* headers;
   1100     bool expected_return_value;
   1101     int64 expected_first_byte_position;
   1102     int64 expected_last_byte_position;
   1103     int64 expected_instance_size;
   1104   }  tests[] = {
   1105     { "HTTP/1.1 206 Partial Content",
   1106       false,
   1107       -1,
   1108       -1,
   1109       -1
   1110     },
   1111     { "HTTP/1.1 206 Partial Content\n"
   1112       "Content-Range:",
   1113       false,
   1114       -1,
   1115       -1,
   1116       -1
   1117     },
   1118     { "HTTP/1.1 206 Partial Content\n"
   1119       "Content-Range: megabytes 0-10/50",
   1120       false,
   1121       -1,
   1122       -1,
   1123       -1
   1124     },
   1125     { "HTTP/1.1 206 Partial Content\n"
   1126       "Content-Range: 0-10/50",
   1127       false,
   1128       -1,
   1129       -1,
   1130       -1
   1131     },
   1132     { "HTTP/1.1 206 Partial Content\n"
   1133       "Content-Range: Bytes 0-50/51",
   1134       true,
   1135       0,
   1136       50,
   1137       51
   1138     },
   1139     { "HTTP/1.1 206 Partial Content\n"
   1140       "Content-Range: bytes 0-50/51",
   1141       true,
   1142       0,
   1143       50,
   1144       51
   1145     },
   1146     { "HTTP/1.1 206 Partial Content\n"
   1147       "Content-Range: bytes\t0-50/51",
   1148       false,
   1149       -1,
   1150       -1,
   1151       -1
   1152     },
   1153     { "HTTP/1.1 206 Partial Content\n"
   1154       "Content-Range:     bytes 0-50/51",
   1155       true,
   1156       0,
   1157       50,
   1158       51
   1159     },
   1160     { "HTTP/1.1 206 Partial Content\n"
   1161       "Content-Range:     bytes    0    -   50  \t / \t51",
   1162       true,
   1163       0,
   1164       50,
   1165       51
   1166     },
   1167     { "HTTP/1.1 206 Partial Content\n"
   1168       "Content-Range: bytes 0\t-\t50\t/\t51\t",
   1169       true,
   1170       0,
   1171       50,
   1172       51
   1173     },
   1174     { "HTTP/1.1 206 Partial Content\n"
   1175       "Content-Range:   \tbytes\t\t\t 0\t-\t50\t/\t51\t",
   1176       true,
   1177       0,
   1178       50,
   1179       51
   1180     },
   1181     { "HTTP/1.1 206 Partial Content\n"
   1182       "Content-Range: \t   bytes \t  0    -   50   /   5   1",
   1183       false,
   1184       0,
   1185       50,
   1186       -1
   1187     },
   1188     { "HTTP/1.1 206 Partial Content\n"
   1189       "Content-Range: \t   bytes \t  0    -   5 0   /   51",
   1190       false,
   1191       -1,
   1192       -1,
   1193       -1
   1194     },
   1195     { "HTTP/1.1 206 Partial Content\n"
   1196       "Content-Range: bytes 50-0/51",
   1197       false,
   1198       50,
   1199       0,
   1200       -1
   1201     },
   1202     { "HTTP/1.1 416 Requested range not satisfiable\n"
   1203       "Content-Range: bytes * /*",
   1204       false,
   1205       -1,
   1206       -1,
   1207       -1
   1208     },
   1209     { "HTTP/1.1 416 Requested range not satisfiable\n"
   1210       "Content-Range: bytes *   /    *   ",
   1211       false,
   1212       -1,
   1213       -1,
   1214       -1
   1215     },
   1216     { "HTTP/1.1 206 Partial Content\n"
   1217       "Content-Range: bytes 0-50/*",
   1218       false,
   1219       0,
   1220       50,
   1221       -1
   1222     },
   1223     { "HTTP/1.1 206 Partial Content\n"
   1224       "Content-Range: bytes 0-50  /    * ",
   1225       false,
   1226       0,
   1227       50,
   1228       -1
   1229     },
   1230     { "HTTP/1.1 206 Partial Content\n"
   1231       "Content-Range: bytes 0-10000000000/10000000001",
   1232       true,
   1233       0,
   1234       10000000000ll,
   1235       10000000001ll
   1236     },
   1237     { "HTTP/1.1 206 Partial Content\n"
   1238       "Content-Range: bytes 0-10000000000/10000000000",
   1239       false,
   1240       0,
   1241       10000000000ll,
   1242       10000000000ll
   1243     },
   1244     // 64 bits wraparound.
   1245     { "HTTP/1.1 206 Partial Content\n"
   1246       "Content-Range: bytes 0 - 9223372036854775807 / 100",
   1247       false,
   1248       0,
   1249       kint64max,
   1250       100
   1251     },
   1252     // 64 bits wraparound.
   1253     { "HTTP/1.1 206 Partial Content\n"
   1254       "Content-Range: bytes 0 - 100 / -9223372036854775808",
   1255       false,
   1256       0,
   1257       100,
   1258       kint64min
   1259     },
   1260     { "HTTP/1.1 206 Partial Content\n"
   1261       "Content-Range: bytes */50",
   1262       false,
   1263       -1,
   1264       -1,
   1265       50
   1266     },
   1267     { "HTTP/1.1 206 Partial Content\n"
   1268       "Content-Range: bytes 0-50/10",
   1269       false,
   1270       0,
   1271       50,
   1272       10
   1273     },
   1274     { "HTTP/1.1 206 Partial Content\n"
   1275       "Content-Range: bytes 40-50/45",
   1276       false,
   1277       40,
   1278       50,
   1279       45
   1280     },
   1281     { "HTTP/1.1 206 Partial Content\n"
   1282       "Content-Range: bytes 0-50/-10",
   1283       false,
   1284       0,
   1285       50,
   1286       -10
   1287     },
   1288     { "HTTP/1.1 206 Partial Content\n"
   1289       "Content-Range: bytes 0-0/1",
   1290       true,
   1291       0,
   1292       0,
   1293       1
   1294     },
   1295     { "HTTP/1.1 206 Partial Content\n"
   1296       "Content-Range: bytes 0-40000000000000000000/40000000000000000001",
   1297       false,
   1298       -1,
   1299       -1,
   1300       -1
   1301     },
   1302     { "HTTP/1.1 206 Partial Content\n"
   1303       "Content-Range: bytes 1-/100",
   1304       false,
   1305       -1,
   1306       -1,
   1307       -1
   1308     },
   1309     { "HTTP/1.1 206 Partial Content\n"
   1310       "Content-Range: bytes -/100",
   1311       false,
   1312       -1,
   1313       -1,
   1314       -1
   1315     },
   1316     { "HTTP/1.1 206 Partial Content\n"
   1317       "Content-Range: bytes -1/100",
   1318       false,
   1319       -1,
   1320       -1,
   1321       -1
   1322     },
   1323     { "HTTP/1.1 206 Partial Content\n"
   1324       "Content-Range: bytes 0-1233/*",
   1325       false,
   1326       0,
   1327       1233,
   1328       -1
   1329     },
   1330     { "HTTP/1.1 206 Partial Content\n"
   1331       "Content-Range: bytes -123 - -1/100",
   1332       false,
   1333       -1,
   1334       -1,
   1335       -1
   1336     },
   1337   };
   1338   for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
   1339     string headers(tests[i].headers);
   1340     HeadersToRaw(&headers);
   1341     scoped_refptr<HttpResponseHeaders> parsed =
   1342         new HttpResponseHeaders(headers);
   1343 
   1344     int64 first_byte_position;
   1345     int64 last_byte_position;
   1346     int64 instance_size;
   1347     bool return_value = parsed->GetContentRange(&first_byte_position,
   1348                                                 &last_byte_position,
   1349                                                 &instance_size);
   1350     EXPECT_EQ(tests[i].expected_return_value, return_value);
   1351     EXPECT_EQ(tests[i].expected_first_byte_position, first_byte_position);
   1352     EXPECT_EQ(tests[i].expected_last_byte_position, last_byte_position);
   1353     EXPECT_EQ(tests[i].expected_instance_size, instance_size);
   1354   }
   1355 }
   1356 
   1357 TEST(HttpResponseHeadersTest, IsKeepAlive) {
   1358   const struct {
   1359     const char* headers;
   1360     bool expected_keep_alive;
   1361   } tests[] = {
   1362     // The status line fabricated by HttpNetworkTransaction for a 0.9 response.
   1363     // Treated as 0.9.
   1364     { "HTTP/0.9 200 OK",
   1365       false
   1366     },
   1367     // This could come from a broken server.  Treated as 1.0 because it has a
   1368     // header.
   1369     { "HTTP/0.9 200 OK\n"
   1370       "connection: keep-alive\n",
   1371       true
   1372     },
   1373     { "HTTP/1.1 200 OK\n",
   1374       true
   1375     },
   1376     { "HTTP/1.0 200 OK\n",
   1377       false
   1378     },
   1379     { "HTTP/1.0 200 OK\n"
   1380       "connection: close\n",
   1381       false
   1382     },
   1383     { "HTTP/1.0 200 OK\n"
   1384       "connection: keep-alive\n",
   1385       true
   1386     },
   1387     { "HTTP/1.0 200 OK\n"
   1388       "connection: kEeP-AliVe\n",
   1389       true
   1390     },
   1391     { "HTTP/1.0 200 OK\n"
   1392       "connection: keep-aliveX\n",
   1393       false
   1394     },
   1395     { "HTTP/1.1 200 OK\n"
   1396       "connection: close\n",
   1397       false
   1398     },
   1399     { "HTTP/1.1 200 OK\n"
   1400       "connection: keep-alive\n",
   1401       true
   1402     },
   1403     { "HTTP/1.0 200 OK\n"
   1404       "proxy-connection: close\n",
   1405       false
   1406     },
   1407     { "HTTP/1.0 200 OK\n"
   1408       "proxy-connection: keep-alive\n",
   1409       true
   1410     },
   1411     { "HTTP/1.1 200 OK\n"
   1412       "proxy-connection: close\n",
   1413       false
   1414     },
   1415     { "HTTP/1.1 200 OK\n"
   1416       "proxy-connection: keep-alive\n",
   1417       true
   1418     },
   1419   };
   1420   for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
   1421     string headers(tests[i].headers);
   1422     HeadersToRaw(&headers);
   1423     scoped_refptr<HttpResponseHeaders> parsed =
   1424         new HttpResponseHeaders(headers);
   1425 
   1426     EXPECT_EQ(tests[i].expected_keep_alive, parsed->IsKeepAlive());
   1427   }
   1428 }
   1429 
   1430 TEST(HttpResponseHeadersTest, HasStrongValidators) {
   1431   const struct {
   1432     const char* headers;
   1433     bool expected_result;
   1434   } tests[] = {
   1435     { "HTTP/0.9 200 OK",
   1436       false
   1437     },
   1438     { "HTTP/0.9 200 OK\n"
   1439       "Date: Wed, 28 Nov 2007 01:40:10 GMT\n"
   1440       "Last-Modified: Wed, 28 Nov 2007 00:40:10 GMT\n"
   1441       "ETag: \"foo\"\n",
   1442       true
   1443     },
   1444     { "HTTP/1.1 200 OK\n"
   1445       "Date: Wed, 28 Nov 2007 00:41:10 GMT\n"
   1446       "Last-Modified: Wed, 28 Nov 2007 00:40:10 GMT\n",
   1447       true
   1448     },
   1449     { "HTTP/1.1 200 OK\n"
   1450       "Date: Wed, 28 Nov 2007 00:41:09 GMT\n"
   1451       "Last-Modified: Wed, 28 Nov 2007 00:40:10 GMT\n",
   1452       false
   1453     },
   1454     { "HTTP/1.1 200 OK\n"
   1455       "ETag: \"foo\"\n",
   1456       true
   1457     },
   1458     // This is not really a weak etag:
   1459     { "HTTP/1.1 200 OK\n"
   1460       "etag: \"w/foo\"\n",
   1461       true
   1462     },
   1463     // This is a weak etag:
   1464     { "HTTP/1.1 200 OK\n"
   1465       "etag: w/\"foo\"\n",
   1466       false
   1467     },
   1468     { "HTTP/1.1 200 OK\n"
   1469       "etag:    W  /   \"foo\"\n",
   1470       false
   1471     }
   1472   };
   1473   for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
   1474     string headers(tests[i].headers);
   1475     HeadersToRaw(&headers);
   1476     scoped_refptr<HttpResponseHeaders> parsed =
   1477         new HttpResponseHeaders(headers);
   1478 
   1479     EXPECT_EQ(tests[i].expected_result, parsed->HasStrongValidators()) <<
   1480       "Failed test case " << i;
   1481   }
   1482 }
   1483 
   1484 TEST(HttpResponseHeadersTest, GetStatusText) {
   1485   std::string headers("HTTP/1.1 404 Not Found");
   1486   HeadersToRaw(&headers);
   1487   scoped_refptr<HttpResponseHeaders> parsed = new HttpResponseHeaders(headers);
   1488   EXPECT_EQ(std::string("Not Found"), parsed->GetStatusText());
   1489 }
   1490 
   1491 TEST(HttpResponseHeadersTest, GetStatusTextMissing) {
   1492   std::string headers("HTTP/1.1 404");
   1493   HeadersToRaw(&headers);
   1494   scoped_refptr<HttpResponseHeaders> parsed = new HttpResponseHeaders(headers);
   1495   // Since the status line gets normalized, we have OK
   1496   EXPECT_EQ(std::string("OK"), parsed->GetStatusText());
   1497 }
   1498 
   1499 TEST(HttpResponseHeadersTest, GetStatusTextMultiSpace) {
   1500   std::string headers("HTTP/1.0     404     Not   Found");
   1501   HeadersToRaw(&headers);
   1502   scoped_refptr<HttpResponseHeaders> parsed = new HttpResponseHeaders(headers);
   1503   EXPECT_EQ(std::string("Not   Found"), parsed->GetStatusText());
   1504 }
   1505 
   1506 TEST(HttpResponseHeadersTest, GetStatusBadStatusLine) {
   1507   std::string headers("Foo bar.");
   1508   HeadersToRaw(&headers);
   1509   scoped_refptr<HttpResponseHeaders> parsed = new HttpResponseHeaders(headers);
   1510   // The bad status line would have gotten rewritten as
   1511   // HTTP/1.0 200 OK.
   1512   EXPECT_EQ(std::string("OK"), parsed->GetStatusText());
   1513 }
   1514 
   1515 TEST(HttpResponseHeadersTest, AddHeader) {
   1516   const struct {
   1517     const char* orig_headers;
   1518     const char* new_header;
   1519     const char* expected_headers;
   1520   } tests[] = {
   1521     { "HTTP/1.1 200 OK\n"
   1522       "connection: keep-alive\n"
   1523       "Cache-control: max-age=10000\n",
   1524 
   1525       "Content-Length: 450",
   1526 
   1527       "HTTP/1.1 200 OK\n"
   1528       "connection: keep-alive\n"
   1529       "Cache-control: max-age=10000\n"
   1530       "Content-Length: 450\n"
   1531     },
   1532     { "HTTP/1.1 200 OK\n"
   1533       "connection: keep-alive\n"
   1534       "Cache-control: max-age=10000    \n",
   1535 
   1536       "Content-Length: 450  ",
   1537 
   1538       "HTTP/1.1 200 OK\n"
   1539       "connection: keep-alive\n"
   1540       "Cache-control: max-age=10000\n"
   1541       "Content-Length: 450\n"
   1542     },
   1543   };
   1544 
   1545   for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
   1546     string orig_headers(tests[i].orig_headers);
   1547     HeadersToRaw(&orig_headers);
   1548     scoped_refptr<HttpResponseHeaders> parsed =
   1549         new HttpResponseHeaders(orig_headers);
   1550 
   1551     string new_header(tests[i].new_header);
   1552     parsed->AddHeader(new_header);
   1553 
   1554     string resulting_headers;
   1555     parsed->GetNormalizedHeaders(&resulting_headers);
   1556     EXPECT_EQ(string(tests[i].expected_headers), resulting_headers);
   1557   }
   1558 }
   1559 
   1560 TEST(HttpResponseHeadersTest, RemoveHeader) {
   1561   const struct {
   1562     const char* orig_headers;
   1563     const char* to_remove;
   1564     const char* expected_headers;
   1565   } tests[] = {
   1566     { "HTTP/1.1 200 OK\n"
   1567       "connection: keep-alive\n"
   1568       "Cache-control: max-age=10000\n"
   1569       "Content-Length: 450\n",
   1570 
   1571       "Content-Length",
   1572 
   1573       "HTTP/1.1 200 OK\n"
   1574       "connection: keep-alive\n"
   1575       "Cache-control: max-age=10000\n"
   1576     },
   1577     { "HTTP/1.1 200 OK\n"
   1578       "connection: keep-alive  \n"
   1579       "Content-Length  : 450  \n"
   1580       "Cache-control: max-age=10000\n",
   1581 
   1582       "Content-Length",
   1583 
   1584       "HTTP/1.1 200 OK\n"
   1585       "connection: keep-alive\n"
   1586       "Cache-control: max-age=10000\n"
   1587     },
   1588   };
   1589 
   1590   for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
   1591     string orig_headers(tests[i].orig_headers);
   1592     HeadersToRaw(&orig_headers);
   1593     scoped_refptr<HttpResponseHeaders> parsed =
   1594         new HttpResponseHeaders(orig_headers);
   1595 
   1596     string name(tests[i].to_remove);
   1597     parsed->RemoveHeader(name);
   1598 
   1599     string resulting_headers;
   1600     parsed->GetNormalizedHeaders(&resulting_headers);
   1601     EXPECT_EQ(string(tests[i].expected_headers), resulting_headers);
   1602   }
   1603 }
   1604 
   1605 TEST(HttpResponseHeadersTest, ReplaceStatus) {
   1606   const struct {
   1607     const char* orig_headers;
   1608     const char* new_status;
   1609     const char* expected_headers;
   1610   } tests[] = {
   1611     { "HTTP/1.1 206 Partial Content\n"
   1612       "connection: keep-alive\n"
   1613       "Cache-control: max-age=10000\n"
   1614       "Content-Length: 450\n",
   1615 
   1616       "HTTP/1.1 200 OK",
   1617 
   1618       "HTTP/1.1 200 OK\n"
   1619       "connection: keep-alive\n"
   1620       "Cache-control: max-age=10000\n"
   1621       "Content-Length: 450\n"
   1622     },
   1623     { "HTTP/1.1 200 OK\n"
   1624       "connection: keep-alive\n",
   1625 
   1626       "HTTP/1.1 304 Not Modified",
   1627 
   1628       "HTTP/1.1 304 Not Modified\n"
   1629       "connection: keep-alive\n"
   1630     },
   1631     { "HTTP/1.1 200 OK\n"
   1632       "connection: keep-alive  \n"
   1633       "Content-Length  : 450   \n"
   1634       "Cache-control: max-age=10000\n",
   1635 
   1636       "HTTP/1//1 304 Not Modified",
   1637 
   1638       "HTTP/1.0 304 Not Modified\n"
   1639       "connection: keep-alive\n"
   1640       "Content-Length: 450\n"
   1641       "Cache-control: max-age=10000\n"
   1642     },
   1643   };
   1644 
   1645   for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
   1646     string orig_headers(tests[i].orig_headers);
   1647     HeadersToRaw(&orig_headers);
   1648     scoped_refptr<HttpResponseHeaders> parsed =
   1649         new HttpResponseHeaders(orig_headers);
   1650 
   1651     string name(tests[i].new_status);
   1652     parsed->ReplaceStatusLine(name);
   1653 
   1654     string resulting_headers;
   1655     parsed->GetNormalizedHeaders(&resulting_headers);
   1656     EXPECT_EQ(string(tests[i].expected_headers), resulting_headers);
   1657   }
   1658 }
   1659