普通文本  |  1659行  |  42.03 KB

// Copyright (c) 2006-2009 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 <algorithm>

#include "base/basictypes.h"
#include "base/pickle.h"
#include "base/time.h"
#include "net/http/http_response_headers.h"
#include "testing/gtest/include/gtest/gtest.h"

using namespace std;
using base::Time;
using net::HttpResponseHeaders;
using net::HttpVersion;

namespace {

struct TestData {
  const char* raw_headers;
  const char* expected_headers;
  int expected_response_code;
  HttpVersion expected_parsed_version;
  HttpVersion expected_version;
};

struct ContentTypeTestData {
  const string raw_headers;
  const string mime_type;
  const bool has_mimetype;
  const string charset;
  const bool has_charset;
  const string all_content_type;
};

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) {
  replace(headers->begin(), headers->end(), '\n', '\0');
  if (!headers->empty())
    *headers += '\0';
}

void TestCommon(const TestData& test) {
  string raw_headers(test.raw_headers);
  HeadersToRaw(&raw_headers);
  string expected_headers(test.expected_headers);

  string headers;
  scoped_refptr<HttpResponseHeaders> parsed =
      new HttpResponseHeaders(raw_headers);
  parsed->GetNormalizedHeaders(&headers);

  // Transform to readable output format (so it's easier to see diffs).
  replace(headers.begin(), headers.end(), ' ', '_');
  replace(headers.begin(), headers.end(), '\n', '\\');
  replace(expected_headers.begin(), expected_headers.end(), ' ', '_');
  replace(expected_headers.begin(), expected_headers.end(), '\n', '\\');

  EXPECT_EQ(expected_headers, headers);

  EXPECT_EQ(test.expected_response_code, parsed->response_code());

  EXPECT_TRUE(test.expected_parsed_version == parsed->GetParsedHttpVersion());
  EXPECT_TRUE(test.expected_version == parsed->GetHttpVersion());
}

} // end namespace

// Check that we normalize headers properly.
TEST(HttpResponseHeadersTest, NormalizeHeadersWhitespace) {
  TestData test = {
    "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, b\n",

    202,
    HttpVersion(1,1),
    HttpVersion(1,1)
  };
  TestCommon(test);
}

// Check that we normalize headers properly (header name is invalid if starts
// with LWS).
TEST(HttpResponseHeadersTest, NormalizeHeadersLeadingWhitespace) {
  TestData test = {
    "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, b\n",

    202,
    HttpVersion(1,1),
    HttpVersion(1,1)
  };
  TestCommon(test);
}

TEST(HttpResponseHeadersTest, BlankHeaders) {
  TestData test = {
    "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",

    200,
    HttpVersion(1,1),
    HttpVersion(1,1)
  };
  TestCommon(test);
}

TEST(HttpResponseHeadersTest, NormalizeHeadersVersion) {
  // Don't believe the http/0.9 version if there are headers!
  TestData test = {
    "hTtP/0.9 201\n"
    "Content-TYPE: text/html; charset=utf-8\n",

    "HTTP/1.0 201 OK\n"
    "Content-TYPE: text/html; charset=utf-8\n",

    201,
    HttpVersion(0,9),
    HttpVersion(1,0)
  };
  TestCommon(test);
}

TEST(HttpResponseHeadersTest, PreserveHttp09) {
  // Accept the HTTP/0.9 version number if there are no headers.
  // This is how HTTP/0.9 responses get constructed from HttpNetworkTransaction.
  TestData test = {
    "hTtP/0.9 200 OK\n",

    "HTTP/0.9 200 OK\n",

    200,
    HttpVersion(0,9),
    HttpVersion(0,9)
  };
  TestCommon(test);
}

TEST(HttpResponseHeadersTest, NormalizeHeadersMissingOK) {
  TestData test = {
    "HTTP/1.1 201\n"
    "Content-TYPE: text/html; charset=utf-8\n",

    "HTTP/1.1 201 OK\n"
    "Content-TYPE: text/html; charset=utf-8\n",

    201,
    HttpVersion(1,1),
    HttpVersion(1,1)
  };
  TestCommon(test);
}

TEST(HttpResponseHeadersTest, NormalizeHeadersBadStatus) {
  TestData test = {
    "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",

    200,
    HttpVersion(0,0), // Parse error
    HttpVersion(1,0)
  };
  TestCommon(test);
}

TEST(HttpResponseHeadersTest, NormalizeHeadersEmpty) {
  TestData test = {
    "",

    "HTTP/1.0 200 OK\n",

    200,
    HttpVersion(0,0), // Parse Error
    HttpVersion(1,0)
  };
  TestCommon(test);
}

TEST(HttpResponseHeadersTest, NormalizeHeadersStartWithColon) {
  TestData test = {
    "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",

    202,
    HttpVersion(1,1),
    HttpVersion(1,1)
  };
  TestCommon(test);
}

TEST(HttpResponseHeadersTest, NormalizeHeadersStartWithColonAtEOL) {
  TestData test = {
    "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",

    202,
    HttpVersion(1,1),
    HttpVersion(1,1)
  };
  TestCommon(test);
}

TEST(HttpResponseHeadersTest, NormalizeHeadersOfWhitepace) {
  TestData test = {
    "\n   \n",

    "HTTP/1.0 200 OK\n",

    200,
    HttpVersion(0,0),  // Parse error
    HttpVersion(1,0)
  };
  TestCommon(test);
}

TEST(HttpResponseHeadersTest, RepeatedSetCookie) {
  TestData test = {
    "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, y=2\n",

    200,
    HttpVersion(1,1),
    HttpVersion(1,1)
  };
  TestCommon(test);
}

TEST(HttpResponseHeadersTest, GetNormalizedHeader) {
  std::string headers =
    "HTTP/1.1 200 OK\n"
    "Cache-control: private\n"
    "cache-Control: no-store\n";
  HeadersToRaw(&headers);
  scoped_refptr<HttpResponseHeaders> parsed = new HttpResponseHeaders(headers);

  std::string value;
  EXPECT_TRUE(parsed->GetNormalizedHeader("cache-control", &value));
  EXPECT_EQ("private, no-store", value);
}

TEST(HttpResponseHeadersTest, Persist) {
  const struct {
    net::HttpResponseHeaders::PersistOptions options;
    const char* raw_headers;
    const char* expected_headers;
  } tests[] = {
    { net::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, no-store\n"
    },
    { net::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"
    },
    { net::HttpResponseHeaders::PERSIST_SANS_NON_CACHEABLE |
      net::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"
    },
    { net::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
    { net::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
    { net::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
    { net::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
    { net::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
    { net::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
    { net::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
    { net::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, 3\n"
      "Bar: 2\n"
    },
    // header name appears twice, separated by another header (type 2)
    { net::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, 4\n"
      "Bar: 2\n"
    },
    // Test filtering of cookie headers.
    { net::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"
    },
    // Test LWS at the end of a header.
    { net::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.
    { net::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"
    },
  };

  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
    std::string headers = tests[i].raw_headers;
    HeadersToRaw(&headers);
    scoped_refptr<HttpResponseHeaders> parsed1 =
        new HttpResponseHeaders(headers);

    Pickle pickle;
    parsed1->Persist(&pickle, tests[i].options);

    void* iter = NULL;
    scoped_refptr<HttpResponseHeaders> parsed2 =
        new HttpResponseHeaders(pickle, &iter);

    std::string h2;
    parsed2->GetNormalizedHeaders(&h2);
    EXPECT_EQ(string(tests[i].expected_headers), h2);
  }
}

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);

  void* iter = NULL;
  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);

  void* iter = NULL;
  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, GetMimeType) {
  const ContentTypeTestData 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" },
    // Test single quotes.
    { "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" },
    // Last 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, last one wins.
    { "HTTP/1.1 200 OK\n"
        "Content-type: text/html;charset=utf-8; charset=iso-8859-1\n",
      "text/html", true,
      "iso-8859-1", 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,
      "*/*" },
  };

  for (size_t i = 0; i < arraysize(tests); ++i) {
    string headers(tests[i].raw_headers);
    HeadersToRaw(&headers);
    scoped_refptr<HttpResponseHeaders> parsed =
        new HttpResponseHeaders(headers);

    std::string value;
    EXPECT_EQ(tests[i].has_mimetype, parsed->GetMimeType(&value));
    EXPECT_EQ(tests[i].mime_type, value);
    value.clear();
    EXPECT_EQ(tests[i].has_charset, parsed->GetCharset(&value));
    EXPECT_EQ(tests[i].charset, value);
    EXPECT_TRUE(parsed->GetNormalizedHeader("content-type", &value));
    EXPECT_EQ(tests[i].all_content_type, value);
  }
}

TEST(HttpResponseHeadersTest, RequiresValidation) {
  const struct {
    const char* headers;
    bool requires_validation;
  } tests[] = {
    // no expiry info: expires immediately
    { "HTTP/1.1 200 OK\n"
      "\n",
      true
    },
    // valid for a little while
    { "HTTP/1.1 200 OK\n"
      "cache-control: max-age=10000\n"
      "\n",
      false
    },
    // 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",
      false
    },
    // expired already
    { "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",
      true
    },
    // 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",
      false
    },
    // 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",
      false
    },
    { "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",
      false
    },
    { "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",
      false
    },
    // 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",
      true
    },
    { "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",
      true
    },
    { "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",
      true
    },
    // cached permanent redirect
    { "HTTP/1.1 301 Moved Permanently\n"
      "\n",
      false
    },
    // cached redirect: not reusable even though by default it would be
    { "HTTP/1.1 300 Multiple Choices\n"
      "Cache-Control: no-cache\n"
      "\n",
      true
    },
    // cached forever by default
    { "HTTP/1.1 410 Gone\n"
      "\n",
      false
    },
    // cached temporary redirect: not reusable
    { "HTTP/1.1 302 Found\n"
      "\n",
      true
    },
    // cached temporary redirect: reusable
    { "HTTP/1.1 302 Found\n"
      "cache-control: max-age=10000\n"
      "\n",
      false
    },
    // 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",
      false
    },
    // 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",
      true
    },
    // 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",
      true
    },
    // TODO(darin): add many many more tests here
  };
  Time request_time, response_time, current_time;
  Time::FromString(L"Wed, 28 Nov 2007 00:40:09 GMT", &request_time);
  Time::FromString(L"Wed, 28 Nov 2007 00:40:12 GMT", &response_time);
  Time::FromString(L"Wed, 28 Nov 2007 00:45:20 GMT", &current_time);

  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
    string headers(tests[i].headers);
    HeadersToRaw(&headers);
    scoped_refptr<HttpResponseHeaders> parsed =
        new HttpResponseHeaders(headers);

    bool requires_validation =
        parsed->RequiresValidation(request_time, response_time, current_time);
    EXPECT_EQ(tests[i].requires_validation, requires_validation);
  }
}

TEST(HttpResponseHeadersTest, Update) {
  const struct {
    const char* orig_headers;
    const char* new_headers;
    const char* expected_headers;
  } 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"
    },
  };

  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
    string orig_headers(tests[i].orig_headers);
    HeadersToRaw(&orig_headers);
    scoped_refptr<HttpResponseHeaders> parsed =
        new HttpResponseHeaders(orig_headers);

    string new_headers(tests[i].new_headers);
    HeadersToRaw(&new_headers);
    scoped_refptr<HttpResponseHeaders> new_parsed =
        new HttpResponseHeaders(new_headers);

    parsed->Update(*new_parsed);

    string resulting_headers;
    parsed->GetNormalizedHeaders(&resulting_headers);
    EXPECT_EQ(string(tests[i].expected_headers), resulting_headers);
  }
}

TEST(HttpResponseHeadersTest, EnumerateHeaderLines) {
  const struct {
    const char* headers;
    const char* expected_lines;
  } 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"
    },
  };
  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
    string headers(tests[i].headers);
    HeadersToRaw(&headers);
    scoped_refptr<HttpResponseHeaders> parsed =
        new HttpResponseHeaders(headers);

    string name, value, lines;

    void* iter = NULL;
    while (parsed->EnumerateHeaderLines(&iter, &name, &value)) {
      lines.append(name);
      lines.append(": ");
      lines.append(value);
      lines.append("\n");
    }

    EXPECT_EQ(string(tests[i].expected_lines), lines);
  }
}

TEST(HttpResponseHeadersTest, IsRedirect) {
  const struct {
    const char* headers;
    const char* location;
    bool 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 w/ 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 w/ 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
    },
  };
  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
    string headers(tests[i].headers);
    HeadersToRaw(&headers);
    scoped_refptr<HttpResponseHeaders> parsed =
        new HttpResponseHeaders(headers);

    std::string location;
    EXPECT_EQ(parsed->IsRedirect(&location), tests[i].is_redirect);
    EXPECT_EQ(location, tests[i].location);
  }
}

TEST(HttpResponseHeadersTest, GetContentLength) {
  const struct {
    const char* headers;
    int64 expected_len;
  } 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
    { "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
    },
  };
  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
    string headers(tests[i].headers);
    HeadersToRaw(&headers);
    scoped_refptr<HttpResponseHeaders> parsed =
        new HttpResponseHeaders(headers);

    EXPECT_EQ(tests[i].expected_len, parsed->GetContentLength());
  }
}

TEST(HttpResponseHeaders, GetContentRange) {
  const struct {
    const char* headers;
    bool expected_return_value;
    int64 expected_first_byte_position;
    int64 expected_last_byte_position;
    int64 expected_instance_size;
  }  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: megabytes 0-10/50",
      false,
      -1,
      -1,
      -1
    },
    { "HTTP/1.1 206 Partial Content\n"
      "Content-Range: 0-10/50",
      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 0-50/51",
      true,
      0,
      50,
      51
    },
    { "HTTP/1.1 206 Partial Content\n"
      "Content-Range: bytes\t0-50/51",
      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    0    -   50  \t / \t51",
      true,
      0,
      50,
      51
    },
    { "HTTP/1.1 206 Partial Content\n"
      "Content-Range: bytes 0\t-\t50\t/\t51\t",
      true,
      0,
      50,
      51
    },
    { "HTTP/1.1 206 Partial Content\n"
      "Content-Range:   \tbytes\t\t\t 0\t-\t50\t/\t51\t",
      true,
      0,
      50,
      51
    },
    { "HTTP/1.1 206 Partial Content\n"
      "Content-Range: \t   bytes \t  0    -   50   /   5   1",
      false,
      0,
      50,
      -1
    },
    { "HTTP/1.1 206 Partial Content\n"
      "Content-Range: \t   bytes \t  0    -   5 0   /   51",
      false,
      -1,
      -1,
      -1
    },
    { "HTTP/1.1 206 Partial Content\n"
      "Content-Range: bytes 50-0/51",
      false,
      50,
      0,
      -1
    },
    { "HTTP/1.1 416 Requested range not satisfiable\n"
      "Content-Range: bytes * /*",
      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,
      0,
      50,
      -1
    },
    { "HTTP/1.1 206 Partial Content\n"
      "Content-Range: bytes 0-50  /    * ",
      false,
      0,
      50,
      -1
    },
    { "HTTP/1.1 206 Partial Content\n"
      "Content-Range: bytes 0-10000000000/10000000001",
      true,
      0,
      10000000000ll,
      10000000001ll
    },
    { "HTTP/1.1 206 Partial Content\n"
      "Content-Range: bytes 0-10000000000/10000000000",
      false,
      0,
      10000000000ll,
      10000000000ll
    },
    // 64 bits wraparound.
    { "HTTP/1.1 206 Partial Content\n"
      "Content-Range: bytes 0 - 9223372036854775807 / 100",
      false,
      0,
      kint64max,
      100
    },
    // 64 bits wraparound.
    { "HTTP/1.1 206 Partial Content\n"
      "Content-Range: bytes 0 - 100 / -9223372036854775808",
      false,
      0,
      100,
      kint64min
    },
    { "HTTP/1.1 206 Partial Content\n"
      "Content-Range: bytes */50",
      false,
      -1,
      -1,
      50
    },
    { "HTTP/1.1 206 Partial Content\n"
      "Content-Range: bytes 0-50/10",
      false,
      0,
      50,
      10
    },
    { "HTTP/1.1 206 Partial Content\n"
      "Content-Range: bytes 40-50/45",
      false,
      40,
      50,
      45
    },
    { "HTTP/1.1 206 Partial Content\n"
      "Content-Range: bytes 0-50/-10",
      false,
      0,
      50,
      -10
    },
    { "HTTP/1.1 206 Partial Content\n"
      "Content-Range: bytes 0-0/1",
      true,
      0,
      0,
      1
    },
    { "HTTP/1.1 206 Partial Content\n"
      "Content-Range: bytes 0-40000000000000000000/40000000000000000001",
      false,
      -1,
      -1,
      -1
    },
    { "HTTP/1.1 206 Partial Content\n"
      "Content-Range: bytes 1-/100",
      false,
      -1,
      -1,
      -1
    },
    { "HTTP/1.1 206 Partial Content\n"
      "Content-Range: bytes -/100",
      false,
      -1,
      -1,
      -1
    },
    { "HTTP/1.1 206 Partial Content\n"
      "Content-Range: bytes -1/100",
      false,
      -1,
      -1,
      -1
    },
    { "HTTP/1.1 206 Partial Content\n"
      "Content-Range: bytes 0-1233/*",
      false,
      0,
      1233,
      -1
    },
    { "HTTP/1.1 206 Partial Content\n"
      "Content-Range: bytes -123 - -1/100",
      false,
      -1,
      -1,
      -1
    },
  };
  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
    string headers(tests[i].headers);
    HeadersToRaw(&headers);
    scoped_refptr<HttpResponseHeaders> parsed =
        new HttpResponseHeaders(headers);

    int64 first_byte_position;
    int64 last_byte_position;
    int64 instance_size;
    bool return_value = parsed->GetContentRange(&first_byte_position,
                                                &last_byte_position,
                                                &instance_size);
    EXPECT_EQ(tests[i].expected_return_value, return_value);
    EXPECT_EQ(tests[i].expected_first_byte_position, first_byte_position);
    EXPECT_EQ(tests[i].expected_last_byte_position, last_byte_position);
    EXPECT_EQ(tests[i].expected_instance_size, instance_size);
  }
}

TEST(HttpResponseHeadersTest, IsKeepAlive) {
  const struct {
    const char* headers;
    bool expected_keep_alive;
  } 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
    },
  };
  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
    string headers(tests[i].headers);
    HeadersToRaw(&headers);
    scoped_refptr<HttpResponseHeaders> parsed =
        new HttpResponseHeaders(headers);

    EXPECT_EQ(tests[i].expected_keep_alive, parsed->IsKeepAlive());
  }
}

TEST(HttpResponseHeadersTest, HasStrongValidators) {
  const struct {
    const char* headers;
    bool expected_result;
  } tests[] = {
    { "HTTP/0.9 200 OK",
      false
    },
    { "HTTP/0.9 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
    }
  };
  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
    string headers(tests[i].headers);
    HeadersToRaw(&headers);
    scoped_refptr<HttpResponseHeaders> parsed =
        new HttpResponseHeaders(headers);

    EXPECT_EQ(tests[i].expected_result, parsed->HasStrongValidators()) <<
      "Failed test case " << i;
  }
}

TEST(HttpResponseHeadersTest, GetStatusText) {
  std::string headers("HTTP/1.1 404 Not Found");
  HeadersToRaw(&headers);
  scoped_refptr<HttpResponseHeaders> parsed = new HttpResponseHeaders(headers);
  EXPECT_EQ(std::string("Not Found"), parsed->GetStatusText());
}

TEST(HttpResponseHeadersTest, GetStatusTextMissing) {
  std::string headers("HTTP/1.1 404");
  HeadersToRaw(&headers);
  scoped_refptr<HttpResponseHeaders> parsed = new HttpResponseHeaders(headers);
  // Since the status line gets normalized, we have OK
  EXPECT_EQ(std::string("OK"), parsed->GetStatusText());
}

TEST(HttpResponseHeadersTest, GetStatusTextMultiSpace) {
  std::string headers("HTTP/1.0     404     Not   Found");
  HeadersToRaw(&headers);
  scoped_refptr<HttpResponseHeaders> parsed = new HttpResponseHeaders(headers);
  EXPECT_EQ(std::string("Not   Found"), parsed->GetStatusText());
}

TEST(HttpResponseHeadersTest, GetStatusBadStatusLine) {
  std::string headers("Foo bar.");
  HeadersToRaw(&headers);
  scoped_refptr<HttpResponseHeaders> parsed = new HttpResponseHeaders(headers);
  // The bad status line would have gotten rewritten as
  // HTTP/1.0 200 OK.
  EXPECT_EQ(std::string("OK"), parsed->GetStatusText());
}

TEST(HttpResponseHeadersTest, AddHeader) {
  const struct {
    const char* orig_headers;
    const char* new_header;
    const char* expected_headers;
  } 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"
    },
  };

  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
    string orig_headers(tests[i].orig_headers);
    HeadersToRaw(&orig_headers);
    scoped_refptr<HttpResponseHeaders> parsed =
        new HttpResponseHeaders(orig_headers);

    string new_header(tests[i].new_header);
    parsed->AddHeader(new_header);

    string resulting_headers;
    parsed->GetNormalizedHeaders(&resulting_headers);
    EXPECT_EQ(string(tests[i].expected_headers), resulting_headers);
  }
}

TEST(HttpResponseHeadersTest, RemoveHeader) {
  const struct {
    const char* orig_headers;
    const char* to_remove;
    const char* expected_headers;
  } 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"
    },
  };

  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
    string orig_headers(tests[i].orig_headers);
    HeadersToRaw(&orig_headers);
    scoped_refptr<HttpResponseHeaders> parsed =
        new HttpResponseHeaders(orig_headers);

    string name(tests[i].to_remove);
    parsed->RemoveHeader(name);

    string resulting_headers;
    parsed->GetNormalizedHeaders(&resulting_headers);
    EXPECT_EQ(string(tests[i].expected_headers), resulting_headers);
  }
}

TEST(HttpResponseHeadersTest, ReplaceStatus) {
  const struct {
    const char* orig_headers;
    const char* new_status;
    const char* expected_headers;
  } 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"
    },
  };

  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
    string orig_headers(tests[i].orig_headers);
    HeadersToRaw(&orig_headers);
    scoped_refptr<HttpResponseHeaders> parsed =
        new HttpResponseHeaders(orig_headers);

    string name(tests[i].new_status);
    parsed->ReplaceStatusLine(name);

    string resulting_headers;
    parsed->GetNormalizedHeaders(&resulting_headers);
    EXPECT_EQ(string(tests[i].expected_headers), resulting_headers);
  }
}