|  | // 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" | 
|  |  | 
|  | namespace net { | 
|  |  | 
|  | namespace { | 
|  | class HttpUtilTest : public testing::Test {}; | 
|  | } | 
|  |  | 
|  | TEST(HttpUtilTest, IsSafeHeader) { | 
|  | static const char* const 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* const 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* const headers; | 
|  | const char* const 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(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* const 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* const header_names[] = { | 
|  | "origin", "content-type", "cookies" | 
|  | }; | 
|  |  | 
|  | static const char* const 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* const 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(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* const input;  // with '|' representing '\0' | 
|  | const char* const 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(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* const url; | 
|  | const char* const expected_spec; | 
|  | const char* const 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(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* const content_type; | 
|  | const char* const expected_mime_type; | 
|  | const char* const expected_charset; | 
|  | const bool expected_had_charset; | 
|  | const char* const 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(tests); ++i) { | 
|  | std::string mime_type; | 
|  | std::string charset; | 
|  | bool had_charset = false; | 
|  | std::string boundary; | 
|  | 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* const 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(tests); ++i) { | 
|  | std::vector<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()); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST(HttpUtilTest, ParseRetryAfterHeader) { | 
|  | base::Time::Exploded now_exploded = { 2014, 11, -1, 5, 22, 39, 30, 0 }; | 
|  | base::Time now = base::Time::FromUTCExploded(now_exploded); | 
|  |  | 
|  | base::Time::Exploded later_exploded = { 2015, 1, -1, 1, 12, 34, 56, 0 }; | 
|  | base::Time later = base::Time::FromUTCExploded(later_exploded); | 
|  |  | 
|  | const struct { | 
|  | const char* retry_after_string; | 
|  | bool expected_return_value; | 
|  | base::TimeDelta expected_retry_after; | 
|  | } tests[] = { | 
|  | { "", false, base::TimeDelta() }, | 
|  | { "-3", false, base::TimeDelta() }, | 
|  | { "-2", false, base::TimeDelta() }, | 
|  | { "-1", false, base::TimeDelta() }, | 
|  | { "0", true, base::TimeDelta::FromSeconds(0) }, | 
|  | { "1", true, base::TimeDelta::FromSeconds(1) }, | 
|  | { "2", true, base::TimeDelta::FromSeconds(2) }, | 
|  | { "3", true, base::TimeDelta::FromSeconds(3) }, | 
|  | { "60", true, base::TimeDelta::FromSeconds(60) }, | 
|  | { "3600", true, base::TimeDelta::FromSeconds(3600) }, | 
|  | { "86400", true, base::TimeDelta::FromSeconds(86400) }, | 
|  | { "Thu, 1 Jan 2015 12:34:56 GMT", true, later - now }, | 
|  | { "Mon, 1 Jan 1900 12:34:56 GMT", false, base::TimeDelta() } | 
|  | }; | 
|  |  | 
|  | for (size_t i = 0; i < arraysize(tests); ++i) { | 
|  | base::TimeDelta retry_after; | 
|  | bool return_value = HttpUtil::ParseRetryAfterHeader( | 
|  | tests[i].retry_after_string, now, &retry_after); | 
|  | EXPECT_EQ(tests[i].expected_return_value, return_value) | 
|  | << "Test case " << i << ": expected " << tests[i].expected_return_value | 
|  | << " but got " << return_value << "."; | 
|  | if (tests[i].expected_return_value && return_value) { | 
|  | EXPECT_EQ(tests[i].expected_retry_after, retry_after) | 
|  | << "Test case " << i << ": expected " | 
|  | << tests[i].expected_retry_after.InSeconds() << "s but got " | 
|  | << retry_after.InSeconds() << "s."; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | 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()); | 
|  | } | 
|  |  | 
|  | }  // 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())); | 
|  | } | 
|  |  | 
|  | }  // namespace net |