普通文本  |  1074行  |  28.73 KB

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <algorithm>

#include "base/basictypes.h"
#include "base/strings/string_util.h"
#include "net/http/http_util.h"
#include "testing/gtest/include/gtest/gtest.h"

using net::HttpUtil;

namespace {
class HttpUtilTest : public testing::Test {};
}

TEST(HttpUtilTest, IsSafeHeader) {
  static const char* unsafe_headers[] = {
    "sec-",
    "sEc-",
    "sec-foo",
    "sEc-FoO",
    "proxy-",
    "pRoXy-",
    "proxy-foo",
    "pRoXy-FoO",
    "accept-charset",
    "accept-encoding",
    "access-control-request-headers",
    "access-control-request-method",
    "connection",
    "content-length",
    "cookie",
    "cookie2",
    "content-transfer-encoding",
    "date",
    "expect",
    "host",
    "keep-alive",
    "origin",
    "referer",
    "te",
    "trailer",
    "transfer-encoding",
    "upgrade",
    "user-agent",
    "via",
  };
  for (size_t i = 0; i < arraysize(unsafe_headers); ++i) {
    EXPECT_FALSE(HttpUtil::IsSafeHeader(unsafe_headers[i]))
      << unsafe_headers[i];
    EXPECT_FALSE(HttpUtil::IsSafeHeader(StringToUpperASCII(std::string(
        unsafe_headers[i])))) << unsafe_headers[i];
  }
  static const char* safe_headers[] = {
    "foo",
    "x-",
    "x-foo",
    "content-disposition",
    "update",
    "accept-charseta",
    "accept_charset",
    "accept-encodinga",
    "accept_encoding",
    "access-control-request-headersa",
    "access-control-request-header",
    "access_control_request_header",
    "access-control-request-methoda",
    "access_control_request_method",
    "connectiona",
    "content-lengtha",
    "content_length",
    "cookiea",
    "cookie2a",
    "cookie3",
    "content-transfer-encodinga",
    "content_transfer_encoding",
    "datea",
    "expecta",
    "hosta",
    "keep-alivea",
    "keep_alive",
    "origina",
    "referera",
    "referrer",
    "tea",
    "trailera",
    "transfer-encodinga",
    "transfer_encoding",
    "upgradea",
    "user-agenta",
    "user_agent",
    "viaa",
  };
  for (size_t i = 0; i < arraysize(safe_headers); ++i) {
    EXPECT_TRUE(HttpUtil::IsSafeHeader(safe_headers[i])) << safe_headers[i];
    EXPECT_TRUE(HttpUtil::IsSafeHeader(StringToUpperASCII(std::string(
        safe_headers[i])))) << safe_headers[i];
  }
}

TEST(HttpUtilTest, HasHeader) {
  static const struct {
    const char* headers;
    const char* name;
    bool expected_result;
  } tests[] = {
    { "", "foo", false },
    { "foo\r\nbar", "foo", false },
    { "ffoo: 1", "foo", false },
    { "foo: 1", "foo", true },
    { "foo: 1\r\nbar: 2", "foo", true },
    { "fOO: 1\r\nbar: 2", "foo", true },
    { "g: 0\r\nfoo: 1\r\nbar: 2", "foo", true },
  };
  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
    bool result = HttpUtil::HasHeader(tests[i].headers, tests[i].name);
    EXPECT_EQ(tests[i].expected_result, result);
  }
}

TEST(HttpUtilTest, StripHeaders) {
  static const char* headers =
      "Origin: origin\r\n"
      "Content-Type: text/plain\r\n"
      "Cookies: foo1\r\n"
      "Custom: baz\r\n"
      "COOKIES: foo2\r\n"
      "Server: Apache\r\n"
      "OrIGin: origin2\r\n";

  static const char* header_names[] = {
    "origin", "content-type", "cookies"
  };

  static const char* expected_stripped_headers =
      "Custom: baz\r\n"
      "Server: Apache\r\n";

  EXPECT_EQ(expected_stripped_headers,
            HttpUtil::StripHeaders(headers, header_names,
                                   arraysize(header_names)));
}

TEST(HttpUtilTest, HeadersIterator) {
  std::string headers = "foo: 1\t\r\nbar: hello world\r\nbaz: 3 \r\n";

  HttpUtil::HeadersIterator it(headers.begin(), headers.end(), "\r\n");

  ASSERT_TRUE(it.GetNext());
  EXPECT_EQ(std::string("foo"), it.name());
  EXPECT_EQ(std::string("1"), it.values());

  ASSERT_TRUE(it.GetNext());
  EXPECT_EQ(std::string("bar"), it.name());
  EXPECT_EQ(std::string("hello world"), it.values());

  ASSERT_TRUE(it.GetNext());
  EXPECT_EQ(std::string("baz"), it.name());
  EXPECT_EQ(std::string("3"), it.values());

  EXPECT_FALSE(it.GetNext());
}

TEST(HttpUtilTest, HeadersIterator_MalformedLine) {
  std::string headers = "foo: 1\n: 2\n3\nbar: 4";

  HttpUtil::HeadersIterator it(headers.begin(), headers.end(), "\n");

  ASSERT_TRUE(it.GetNext());
  EXPECT_EQ(std::string("foo"), it.name());
  EXPECT_EQ(std::string("1"), it.values());

  ASSERT_TRUE(it.GetNext());
  EXPECT_EQ(std::string("bar"), it.name());
  EXPECT_EQ(std::string("4"), it.values());

  EXPECT_FALSE(it.GetNext());
}

TEST(HttpUtilTest, HeadersIterator_AdvanceTo) {
  std::string headers = "foo: 1\r\n: 2\r\n3\r\nbar: 4";

  HttpUtil::HeadersIterator it(headers.begin(), headers.end(), "\r\n");
  EXPECT_TRUE(it.AdvanceTo("foo"));
  EXPECT_EQ("foo", it.name());
  EXPECT_TRUE(it.AdvanceTo("bar"));
  EXPECT_EQ("bar", it.name());
  EXPECT_FALSE(it.AdvanceTo("blat"));
  EXPECT_FALSE(it.GetNext());  // should be at end of headers
}

TEST(HttpUtilTest, HeadersIterator_Reset) {
  std::string headers = "foo: 1\r\n: 2\r\n3\r\nbar: 4";
  HttpUtil::HeadersIterator it(headers.begin(), headers.end(), "\r\n");
  // Search past "foo".
  EXPECT_TRUE(it.AdvanceTo("bar"));
  // Now try advancing to "foo".  This time it should fail since the iterator
  // position is past it.
  EXPECT_FALSE(it.AdvanceTo("foo"));
  it.Reset();
  // Now that we reset the iterator position, we should find 'foo'
  EXPECT_TRUE(it.AdvanceTo("foo"));
}

TEST(HttpUtilTest, ValuesIterator) {
  std::string values = " must-revalidate,   no-cache=\"foo, bar\"\t, private ";

  HttpUtil::ValuesIterator it(values.begin(), values.end(), ',');

  ASSERT_TRUE(it.GetNext());
  EXPECT_EQ(std::string("must-revalidate"), it.value());

  ASSERT_TRUE(it.GetNext());
  EXPECT_EQ(std::string("no-cache=\"foo, bar\""), it.value());

  ASSERT_TRUE(it.GetNext());
  EXPECT_EQ(std::string("private"), it.value());

  EXPECT_FALSE(it.GetNext());
}

TEST(HttpUtilTest, ValuesIterator_Blanks) {
  std::string values = " \t ";

  HttpUtil::ValuesIterator it(values.begin(), values.end(), ',');

  EXPECT_FALSE(it.GetNext());
}

TEST(HttpUtilTest, Unquote) {
  // Replace <backslash> " with ".
  EXPECT_STREQ("xyz\"abc", HttpUtil::Unquote("\"xyz\\\"abc\"").c_str());

  // Replace <backslash> <backslash> with <backslash>
  EXPECT_STREQ("xyz\\abc", HttpUtil::Unquote("\"xyz\\\\abc\"").c_str());
  EXPECT_STREQ("xyz\\\\\\abc",
               HttpUtil::Unquote("\"xyz\\\\\\\\\\\\abc\"").c_str());

  // Replace <backslash> X with X
  EXPECT_STREQ("xyzXabc", HttpUtil::Unquote("\"xyz\\Xabc\"").c_str());

  // Act as identity function on unquoted inputs.
  EXPECT_STREQ("X", HttpUtil::Unquote("X").c_str());
  EXPECT_STREQ("\"", HttpUtil::Unquote("\"").c_str());

  // Allow single quotes to act as quote marks.
  // Not part of RFC 2616.
  EXPECT_STREQ("x\"", HttpUtil::Unquote("'x\"'").c_str());
}

TEST(HttpUtilTest, Quote) {
  EXPECT_STREQ("\"xyz\\\"abc\"", HttpUtil::Quote("xyz\"abc").c_str());

  // Replace <backslash> <backslash> with <backslash>
  EXPECT_STREQ("\"xyz\\\\abc\"", HttpUtil::Quote("xyz\\abc").c_str());

  // Replace <backslash> X with X
  EXPECT_STREQ("\"xyzXabc\"", HttpUtil::Quote("xyzXabc").c_str());
}

TEST(HttpUtilTest, LocateEndOfHeaders) {
  struct {
    const char* input;
    int expected_result;
  } tests[] = {
    { "foo\r\nbar\r\n\r\n", 12 },
    { "foo\nbar\n\n", 9 },
    { "foo\r\nbar\r\n\r\njunk", 12 },
    { "foo\nbar\n\njunk", 9 },
    { "foo\nbar\n\r\njunk", 10 },
    { "foo\nbar\r\n\njunk", 10 },
  };
  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
    int input_len = static_cast<int>(strlen(tests[i].input));
    int eoh = HttpUtil::LocateEndOfHeaders(tests[i].input, input_len);
    EXPECT_EQ(tests[i].expected_result, eoh);
  }
}

TEST(HttpUtilTest, AssembleRawHeaders) {
  struct {
    const char* input;  // with '|' representing '\0'
    const char* expected_result;  // with '\0' changed to '|'
  } tests[] = {
    { "HTTP/1.0 200 OK\r\nFoo: 1\r\nBar: 2\r\n\r\n",
      "HTTP/1.0 200 OK|Foo: 1|Bar: 2||" },

    { "HTTP/1.0 200 OK\nFoo: 1\nBar: 2\n\n",
      "HTTP/1.0 200 OK|Foo: 1|Bar: 2||" },

    // Valid line continuation (single SP).
    {
      "HTTP/1.0 200 OK\n"
      "Foo: 1\n"
      " continuation\n"
      "Bar: 2\n\n",

      "HTTP/1.0 200 OK|"
      "Foo: 1 continuation|"
      "Bar: 2||"
    },

    // Valid line continuation (single HT).
    {
      "HTTP/1.0 200 OK\n"
      "Foo: 1\n"
      "\tcontinuation\n"
      "Bar: 2\n\n",

      "HTTP/1.0 200 OK|"
      "Foo: 1 continuation|"
      "Bar: 2||"
    },

    // Valid line continuation (multiple SP).
    {
      "HTTP/1.0 200 OK\n"
      "Foo: 1\n"
      "   continuation\n"
      "Bar: 2\n\n",

      "HTTP/1.0 200 OK|"
      "Foo: 1 continuation|"
      "Bar: 2||"
    },

    // Valid line continuation (multiple HT).
    {
      "HTTP/1.0 200 OK\n"
      "Foo: 1\n"
      "\t\t\tcontinuation\n"
      "Bar: 2\n\n",

      "HTTP/1.0 200 OK|"
      "Foo: 1 continuation|"
      "Bar: 2||"
    },

    // Valid line continuation (mixed HT, SP).
    {
      "HTTP/1.0 200 OK\n"
      "Foo: 1\n"
      " \t \t continuation\n"
      "Bar: 2\n\n",

      "HTTP/1.0 200 OK|"
      "Foo: 1 continuation|"
      "Bar: 2||"
    },

    // Valid multi-line continuation
    {
      "HTTP/1.0 200 OK\n"
      "Foo: 1\n"
      " continuation1\n"
      "\tcontinuation2\n"
      "  continuation3\n"
      "Bar: 2\n\n",

      "HTTP/1.0 200 OK|"
      "Foo: 1 continuation1 continuation2 continuation3|"
      "Bar: 2||"
    },

    // Continuation of quoted value.
    // This is different from what Firefox does, since it
    // will preserve the LWS.
    {
      "HTTP/1.0 200 OK\n"
      "Etag: \"34534-d3\n"
      "    134q\"\n"
      "Bar: 2\n\n",

      "HTTP/1.0 200 OK|"
      "Etag: \"34534-d3 134q\"|"
      "Bar: 2||"
    },

    // Valid multi-line continuation, full LWS lines
    {
      "HTTP/1.0 200 OK\n"
      "Foo: 1\n"
      "         \n"
      "\t\t\t\t\n"
      "\t  continuation\n"
      "Bar: 2\n\n",

      // One SP per continued line = 3.
      "HTTP/1.0 200 OK|"
      "Foo: 1   continuation|"
      "Bar: 2||"
    },

    // Valid multi-line continuation, all LWS
    {
      "HTTP/1.0 200 OK\n"
      "Foo: 1\n"
      "         \n"
      "\t\t\t\t\n"
      "\t  \n"
      "Bar: 2\n\n",

      // One SP per continued line = 3.
      "HTTP/1.0 200 OK|"
      "Foo: 1   |"
      "Bar: 2||"
    },

    // Valid line continuation (No value bytes in first line).
    {
      "HTTP/1.0 200 OK\n"
      "Foo:\n"
      " value\n"
      "Bar: 2\n\n",

      "HTTP/1.0 200 OK|"
      "Foo: value|"
      "Bar: 2||"
    },

    // Not a line continuation (can't continue status line).
    {
      "HTTP/1.0 200 OK\n"
      " Foo: 1\n"
      "Bar: 2\n\n",

      "HTTP/1.0 200 OK|"
      " Foo: 1|"
      "Bar: 2||"
    },

    // Not a line continuation (can't continue status line).
    {
      "HTTP/1.0\n"
      " 200 OK\n"
      "Foo: 1\n"
      "Bar: 2\n\n",

      "HTTP/1.0|"
      " 200 OK|"
      "Foo: 1|"
      "Bar: 2||"
    },

    // Not a line continuation (can't continue status line).
    {
      "HTTP/1.0 404\n"
      " Not Found\n"
      "Foo: 1\n"
      "Bar: 2\n\n",

      "HTTP/1.0 404|"
      " Not Found|"
      "Foo: 1|"
      "Bar: 2||"
    },

    // Unterminated status line.
    {
      "HTTP/1.0 200 OK",

      "HTTP/1.0 200 OK||"
    },

    // Single terminated, with headers
    {
      "HTTP/1.0 200 OK\n"
      "Foo: 1\n"
      "Bar: 2\n",

      "HTTP/1.0 200 OK|"
      "Foo: 1|"
      "Bar: 2||"
    },

    // Not terminated, with headers
    {
      "HTTP/1.0 200 OK\n"
      "Foo: 1\n"
      "Bar: 2",

      "HTTP/1.0 200 OK|"
      "Foo: 1|"
      "Bar: 2||"
    },

    // Not a line continuation (VT)
    {
      "HTTP/1.0 200 OK\n"
      "Foo: 1\n"
      "\vInvalidContinuation\n"
      "Bar: 2\n\n",

      "HTTP/1.0 200 OK|"
      "Foo: 1|"
      "\vInvalidContinuation|"
      "Bar: 2||"
    },

    // Not a line continuation (formfeed)
    {
      "HTTP/1.0 200 OK\n"
      "Foo: 1\n"
      "\fInvalidContinuation\n"
      "Bar: 2\n\n",

      "HTTP/1.0 200 OK|"
      "Foo: 1|"
      "\fInvalidContinuation|"
      "Bar: 2||"
    },

    // Not a line continuation -- can't continue header names.
    {
      "HTTP/1.0 200 OK\n"
      "Serv\n"
      " er: Apache\n"
      "\tInvalidContinuation\n"
      "Bar: 2\n\n",

      "HTTP/1.0 200 OK|"
      "Serv|"
      " er: Apache|"
      "\tInvalidContinuation|"
      "Bar: 2||"
    },

    // Not a line continuation -- no value to continue.
    {
      "HTTP/1.0 200 OK\n"
      "Foo: 1\n"
      "garbage\n"
      "  not-a-continuation\n"
      "Bar: 2\n\n",

      "HTTP/1.0 200 OK|"
      "Foo: 1|"
      "garbage|"
      "  not-a-continuation|"
      "Bar: 2||",
    },

    // Not a line continuation -- no valid name.
    {
      "HTTP/1.0 200 OK\n"
      ": 1\n"
      "  garbage\n"
      "Bar: 2\n\n",

      "HTTP/1.0 200 OK|"
      ": 1|"
      "  garbage|"
      "Bar: 2||",
    },

    // Not a line continuation -- no valid name (whitespace)
    {
      "HTTP/1.0 200 OK\n"
      "   : 1\n"
      "  garbage\n"
      "Bar: 2\n\n",

      "HTTP/1.0 200 OK|"
      "   : 1|"
      "  garbage|"
      "Bar: 2||",
    },

    // Embed NULLs in the status line. They should not be understood
    // as line separators.
    {
      "HTTP/1.0 200 OK|Bar2:0|Baz2:1\r\nFoo: 1\r\nBar: 2\r\n\r\n",
      "HTTP/1.0 200 OKBar2:0Baz2:1|Foo: 1|Bar: 2||"
    },

    // Embed NULLs in a header line. They should not be understood as
    // line separators.
    {
      "HTTP/1.0 200 OK\nFoo: 1|Foo2: 3\nBar: 2\n\n",
      "HTTP/1.0 200 OK|Foo: 1Foo2: 3|Bar: 2||"
    },
  };
  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
    std::string input = tests[i].input;
    std::replace(input.begin(), input.end(), '|', '\0');
    std::string raw = HttpUtil::AssembleRawHeaders(input.data(), input.size());
    std::replace(raw.begin(), raw.end(), '\0', '|');
    EXPECT_EQ(tests[i].expected_result, raw);
  }
}

// Test SpecForRequest() and PathForRequest().
TEST(HttpUtilTest, RequestUrlSanitize) {
  struct {
    const char* url;
    const char* expected_spec;
    const char* expected_path;
  } tests[] = {
    { // Check that #hash is removed.
      "http://www.google.com:78/foobar?query=1#hash",
      "http://www.google.com:78/foobar?query=1",
      "/foobar?query=1"
    },
    { // The reference may itself contain # -- strip all of it.
      "http://192.168.0.1?query=1#hash#10#11#13#14",
      "http://192.168.0.1/?query=1",
      "/?query=1"
    },
    { // Strip username/password.
      "http://user:pass@google.com",
      "http://google.com/",
      "/"
    },
    { // https scheme
      "https://www.google.com:78/foobar?query=1#hash",
      "https://www.google.com:78/foobar?query=1",
      "/foobar?query=1"
    },
    { // WebSocket's ws scheme
      "ws://www.google.com:78/foobar?query=1#hash",
      "ws://www.google.com:78/foobar?query=1",
      "/foobar?query=1"
    },
    { // WebSocket's wss scheme
      "wss://www.google.com:78/foobar?query=1#hash",
      "wss://www.google.com:78/foobar?query=1",
      "/foobar?query=1"
    }
  };
  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
    GURL url(GURL(tests[i].url));
    std::string expected_spec(tests[i].expected_spec);
    std::string expected_path(tests[i].expected_path);

    EXPECT_EQ(expected_spec, HttpUtil::SpecForRequest(url));
    EXPECT_EQ(expected_path, HttpUtil::PathForRequest(url));
  }
}

// Test SpecForRequest() for "ftp" scheme.
TEST(HttpUtilTest, SpecForRequestForUrlWithFtpScheme) {
  GURL ftp_url("ftp://user:pass@google.com/pub/chromium/");
  EXPECT_EQ("ftp://google.com/pub/chromium/",
            HttpUtil::SpecForRequest(ftp_url));
}

TEST(HttpUtilTest, GenerateAcceptLanguageHeader) {
  EXPECT_EQ(std::string("en-US,fr;q=0.8,de;q=0.6"),
            HttpUtil::GenerateAcceptLanguageHeader("en-US,fr,de"));
  EXPECT_EQ(std::string("en-US,fr;q=0.8,de;q=0.6,ko;q=0.4,zh-CN;q=0.2,"
                        "ja;q=0.2"),
            HttpUtil::GenerateAcceptLanguageHeader("en-US,fr,de,ko,zh-CN,ja"));
}

// HttpResponseHeadersTest.GetMimeType also tests ParseContentType.
TEST(HttpUtilTest, ParseContentType) {
  const struct {
    const char* content_type;
    const char* expected_mime_type;
    const char* expected_charset;
    const bool expected_had_charset;
    const char* expected_boundary;
  } tests[] = {
    { "text/html; charset=utf-8",
      "text/html",
      "utf-8",
      true,
      ""
    },
    { "text/html; charset =utf-8",
      "text/html",
      "utf-8",
      true,
      ""
    },
    { "text/html; charset= utf-8",
      "text/html",
      "utf-8",
      true,
      ""
    },
    { "text/html; charset=utf-8 ",
      "text/html",
      "utf-8",
      true,
      ""
    },
    { "text/html; boundary=\"WebKit-ada-df-dsf-adsfadsfs\"",
      "text/html",
      "",
      false,
      "\"WebKit-ada-df-dsf-adsfadsfs\""
    },
    { "text/html; boundary =\"WebKit-ada-df-dsf-adsfadsfs\"",
      "text/html",
      "",
      false,
      "\"WebKit-ada-df-dsf-adsfadsfs\""
    },
    { "text/html; boundary= \"WebKit-ada-df-dsf-adsfadsfs\"",
      "text/html",
      "",
      false,
      "\"WebKit-ada-df-dsf-adsfadsfs\""
    },
    { "text/html; boundary= \"WebKit-ada-df-dsf-adsfadsfs\"   ",
      "text/html",
      "",
      false,
      "\"WebKit-ada-df-dsf-adsfadsfs\""
    },
    { "text/html; boundary=\"WebKit-ada-df-dsf-adsfadsfs  \"",
      "text/html",
      "",
      false,
      "\"WebKit-ada-df-dsf-adsfadsfs  \""
    },
    { "text/html; boundary=WebKit-ada-df-dsf-adsfadsfs",
      "text/html",
      "",
      false,
      "WebKit-ada-df-dsf-adsfadsfs"
    },
    // TODO(abarth): Add more interesting test cases.
  };
  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
    std::string mime_type;
    std::string charset;
    bool had_charset = false;
    std::string boundary;
    net::HttpUtil::ParseContentType(tests[i].content_type, &mime_type,
                                    &charset, &had_charset, &boundary);
    EXPECT_EQ(tests[i].expected_mime_type, mime_type) << "i=" << i;
    EXPECT_EQ(tests[i].expected_charset, charset) << "i=" << i;
    EXPECT_EQ(tests[i].expected_had_charset, had_charset) << "i=" << i;
    EXPECT_EQ(tests[i].expected_boundary, boundary) << "i=" << i;
  }
}

TEST(HttpUtilTest, ParseRanges) {
  const struct {
    const char* headers;
    bool expected_return_value;
    size_t expected_ranges_size;
    const struct {
      int64 expected_first_byte_position;
      int64 expected_last_byte_position;
      int64 expected_suffix_length;
    } expected_ranges[10];
  } tests[] = {
    { "Range: bytes=0-10",
      true,
      1,
      { {0, 10, -1}, }
    },
    { "Range: bytes=10-0",
      false,
      0,
      {}
    },
    { "Range: BytES=0-10",
      true,
      1,
      { {0, 10, -1}, }
    },
    { "Range: megabytes=0-10",
      false,
      0,
      {}
    },
    { "Range: bytes0-10",
      false,
      0,
      {}
    },
    { "Range: bytes=0-0,0-10,10-20,100-200,100-,-200",
      true,
      6,
      { {0, 0, -1},
        {0, 10, -1},
        {10, 20, -1},
        {100, 200, -1},
        {100, -1, -1},
        {-1, -1, 200},
      }
    },
    { "Range: bytes=0-10\r\n"
      "Range: bytes=0-10,10-20,100-200,100-,-200",
      true,
      1,
      { {0, 10, -1}
      }
    },
    { "Range: bytes=",
      false,
      0,
      {}
    },
    { "Range: bytes=-",
      false,
      0,
      {}
    },
    { "Range: bytes=0-10-",
      false,
      0,
      {}
    },
    { "Range: bytes=-0-10",
      false,
      0,
      {}
    },
    { "Range: bytes =0-10\r\n",
      true,
      1,
      { {0, 10, -1}
      }
    },
    { "Range: bytes=  0-10      \r\n",
      true,
      1,
      { {0, 10, -1}
      }
    },
    { "Range: bytes  =   0  -   10      \r\n",
      true,
      1,
      { {0, 10, -1}
      }
    },
    { "Range: bytes=   0-1   0\r\n",
      false,
      0,
      {}
    },
    { "Range: bytes=   0-     -10\r\n",
      false,
      0,
      {}
    },
    { "Range: bytes=   0  -  1   ,   10 -20,   100- 200 ,  100-,  -200 \r\n",
      true,
      5,
      { {0, 1, -1},
        {10, 20, -1},
        {100, 200, -1},
        {100, -1, -1},
        {-1, -1, 200},
      }
    },
  };

  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) {
    std::vector<net::HttpByteRange> ranges;
    bool return_value = HttpUtil::ParseRanges(std::string(tests[i].headers),
                                              &ranges);
    EXPECT_EQ(tests[i].expected_return_value, return_value);
    if (return_value) {
      EXPECT_EQ(tests[i].expected_ranges_size, ranges.size());
      for (size_t j = 0; j < ranges.size(); ++j) {
        EXPECT_EQ(tests[i].expected_ranges[j].expected_first_byte_position,
                  ranges[j].first_byte_position());
        EXPECT_EQ(tests[i].expected_ranges[j].expected_last_byte_position,
                  ranges[j].last_byte_position());
        EXPECT_EQ(tests[i].expected_ranges[j].expected_suffix_length,
                  ranges[j].suffix_length());
      }
    }
  }
}

namespace {
void CheckCurrentNameValuePair(HttpUtil::NameValuePairsIterator* parser,
                               bool expect_valid,
                               std::string expected_name,
                               std::string expected_value) {
  ASSERT_EQ(expect_valid, parser->valid());
  if (!expect_valid) {
    return;
  }

  // Let's make sure that these never change (i.e., when a quoted value is
  // unquoted, it should be cached on the first calls and not regenerated
  // later).
  std::string::const_iterator first_value_begin = parser->value_begin();
  std::string::const_iterator first_value_end = parser->value_end();

  ASSERT_EQ(expected_name, std::string(parser->name_begin(),
                                       parser->name_end()));
  ASSERT_EQ(expected_name, parser->name());
  ASSERT_EQ(expected_value, std::string(parser->value_begin(),
                                        parser->value_end()));
  ASSERT_EQ(expected_value, parser->value());

  // Make sure they didn't/don't change.
  ASSERT_TRUE(first_value_begin == parser->value_begin());
  ASSERT_TRUE(first_value_end == parser->value_end());
}

void CheckNextNameValuePair(HttpUtil::NameValuePairsIterator* parser,
                            bool expect_next,
                            bool expect_valid,
                            std::string expected_name,
                            std::string expected_value) {
  ASSERT_EQ(expect_next, parser->GetNext());
  ASSERT_EQ(expect_valid, parser->valid());
  if (!expect_next || !expect_valid) {
    return;
  }

  CheckCurrentNameValuePair(parser,
                            expect_valid,
                            expected_name,
                            expected_value);
}

void CheckInvalidNameValuePair(std::string valid_part,
                               std::string invalid_part) {
  std::string whole_string = valid_part + invalid_part;

  HttpUtil::NameValuePairsIterator valid_parser(valid_part.begin(),
                                                valid_part.end(),
                                                ';');
  HttpUtil::NameValuePairsIterator invalid_parser(whole_string.begin(),
                                                  whole_string.end(),
                                                  ';');

  ASSERT_TRUE(valid_parser.valid());
  ASSERT_TRUE(invalid_parser.valid());

  // Both parsers should return all the same values until "valid_parser" is
  // exhausted.
  while (valid_parser.GetNext()) {
    ASSERT_TRUE(invalid_parser.GetNext());
    ASSERT_TRUE(valid_parser.valid());
    ASSERT_TRUE(invalid_parser.valid());
    ASSERT_EQ(valid_parser.name(), invalid_parser.name());
    ASSERT_EQ(valid_parser.value(), invalid_parser.value());
  }

  // valid_parser is exhausted and remains 'valid'
  ASSERT_TRUE(valid_parser.valid());

  // invalid_parser's corresponding call to GetNext also returns false...
  ASSERT_FALSE(invalid_parser.GetNext());
  // ...but the parser is in an invalid state.
  ASSERT_FALSE(invalid_parser.valid());
}

}  // anonymous namespace

TEST(HttpUtilTest, NameValuePairsIteratorCopyAndAssign) {
  std::string data = "alpha='\\'a\\''; beta=\" b \"; cappa='c;'; delta=\"d\"";
  HttpUtil::NameValuePairsIterator parser_a(data.begin(), data.end(), ';');

  EXPECT_TRUE(parser_a.valid());
  ASSERT_NO_FATAL_FAILURE(
      CheckNextNameValuePair(&parser_a, true, true, "alpha", "'a'"));

  HttpUtil::NameValuePairsIterator parser_b(parser_a);
  // a and b now point to same location
  ASSERT_NO_FATAL_FAILURE(
      CheckCurrentNameValuePair(&parser_b, true, "alpha", "'a'"));
  ASSERT_NO_FATAL_FAILURE(
      CheckCurrentNameValuePair(&parser_a, true, "alpha", "'a'"));

  // advance a, no effect on b
  ASSERT_NO_FATAL_FAILURE(
      CheckNextNameValuePair(&parser_a, true, true, "beta", " b "));
  ASSERT_NO_FATAL_FAILURE(
      CheckCurrentNameValuePair(&parser_b, true, "alpha", "'a'"));

  // assign b the current state of a, no effect on a
  parser_b = parser_a;
  ASSERT_NO_FATAL_FAILURE(
      CheckCurrentNameValuePair(&parser_b, true, "beta", " b "));
  ASSERT_NO_FATAL_FAILURE(
      CheckCurrentNameValuePair(&parser_a, true, "beta", " b "));

  // advance b, no effect on a
  ASSERT_NO_FATAL_FAILURE(
      CheckNextNameValuePair(&parser_b, true, true, "cappa", "c;"));
  ASSERT_NO_FATAL_FAILURE(
      CheckCurrentNameValuePair(&parser_a, true, "beta", " b "));
}

TEST(HttpUtilTest, NameValuePairsIteratorEmptyInput) {
  std::string data;
  HttpUtil::NameValuePairsIterator parser(data.begin(), data.end(), ';');

  EXPECT_TRUE(parser.valid());
  ASSERT_NO_FATAL_FAILURE(CheckNextNameValuePair(
      &parser, false, true, std::string(), std::string()));
}

TEST(HttpUtilTest, NameValuePairsIterator) {
  std::string data = "alpha=1; beta= 2 ;cappa =' 3; ';"
                     "delta= \" \\\"4\\\" \"; e= \" '5'\"; e=6;"
                     "f='\\'\\h\\e\\l\\l\\o\\ \\w\\o\\r\\l\\d\\'';"
                     "g=''; h='hello'";
  HttpUtil::NameValuePairsIterator parser(data.begin(), data.end(), ';');
  EXPECT_TRUE(parser.valid());

  ASSERT_NO_FATAL_FAILURE(
      CheckNextNameValuePair(&parser, true, true, "alpha", "1"));
  ASSERT_NO_FATAL_FAILURE(
      CheckNextNameValuePair(&parser, true, true, "beta", "2"));
  ASSERT_NO_FATAL_FAILURE(
      CheckNextNameValuePair(&parser, true, true, "cappa", " 3; "));
  ASSERT_NO_FATAL_FAILURE(
      CheckNextNameValuePair(&parser, true, true, "delta", " \"4\" "));
  ASSERT_NO_FATAL_FAILURE(
      CheckNextNameValuePair(&parser, true, true, "e", " '5'"));
  ASSERT_NO_FATAL_FAILURE(
      CheckNextNameValuePair(&parser, true, true, "e", "6"));
  ASSERT_NO_FATAL_FAILURE(
      CheckNextNameValuePair(&parser, true, true, "f", "'hello world'"));
  ASSERT_NO_FATAL_FAILURE(
      CheckNextNameValuePair(&parser, true, true, "g", std::string()));
  ASSERT_NO_FATAL_FAILURE(
      CheckNextNameValuePair(&parser, true, true, "h", "hello"));
  ASSERT_NO_FATAL_FAILURE(CheckNextNameValuePair(
      &parser, false, true, std::string(), std::string()));
}

TEST(HttpUtilTest, NameValuePairsIteratorIllegalInputs) {
  ASSERT_NO_FATAL_FAILURE(CheckInvalidNameValuePair("alpha=1", "; beta"));
  ASSERT_NO_FATAL_FAILURE(CheckInvalidNameValuePair(std::string(), "beta"));

  ASSERT_NO_FATAL_FAILURE(CheckInvalidNameValuePair("alpha=1", "; 'beta'=2"));
  ASSERT_NO_FATAL_FAILURE(CheckInvalidNameValuePair(std::string(), "'beta'=2"));
  ASSERT_NO_FATAL_FAILURE(CheckInvalidNameValuePair("alpha=1", ";beta="));
  ASSERT_NO_FATAL_FAILURE(CheckInvalidNameValuePair("alpha=1",
                                                    ";beta=;cappa=2"));

  // According to the spec this is an error, but it doesn't seem appropriate to
  // change our behaviour to be less permissive at this time.
  // See NameValuePairsIteratorExtraSeparators test
  // ASSERT_NO_FATAL_FAILURE(CheckInvalidNameValuePair("alpha=1", ";; beta=2"));
}

// If we are going to support extra separators against the spec, let's just make
// sure they work rationally.
TEST(HttpUtilTest, NameValuePairsIteratorExtraSeparators) {
  std::string data = " ; ;;alpha=1; ;; ; beta= 2;cappa=3;;; ; ";
  HttpUtil::NameValuePairsIterator parser(data.begin(), data.end(), ';');
  EXPECT_TRUE(parser.valid());

  ASSERT_NO_FATAL_FAILURE(
      CheckNextNameValuePair(&parser, true, true, "alpha", "1"));
  ASSERT_NO_FATAL_FAILURE(
      CheckNextNameValuePair(&parser, true, true, "beta", "2"));
  ASSERT_NO_FATAL_FAILURE(
      CheckNextNameValuePair(&parser, true, true, "cappa", "3"));
  ASSERT_NO_FATAL_FAILURE(CheckNextNameValuePair(
      &parser, false, true, std::string(), std::string()));
}

// See comments on the implementation of NameValuePairsIterator::GetNext
// regarding this derogation from the spec.
TEST(HttpUtilTest, NameValuePairsIteratorMissingEndQuote) {
  std::string data = "name='value";
  HttpUtil::NameValuePairsIterator parser(data.begin(), data.end(), ';');
  EXPECT_TRUE(parser.valid());

  ASSERT_NO_FATAL_FAILURE(
      CheckNextNameValuePair(&parser, true, true, "name", "value"));
  ASSERT_NO_FATAL_FAILURE(CheckNextNameValuePair(
      &parser, false, true, std::string(), std::string()));
}