| // Copyright (c) 2006-2008 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 "net/http/http_util.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using net::HttpUtil; |
| |
| namespace { |
| class HttpUtilTest : public testing::Test {}; |
| } |
| |
| 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, 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, 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; |
| 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||", |
| }, |
| |
| }; |
| for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); ++i) { |
| int input_len = static_cast<int>(strlen(tests[i].input)); |
| std::string raw = HttpUtil::AssembleRawHeaders(tests[i].input, input_len); |
| std::replace(raw.begin(), raw.end(), '\0', '|'); |
| EXPECT_TRUE(raw == tests[i].expected_result); |
| } |
| } |
| |
| // 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:[email protected]", |
| "http://google.com/", |
| "/" |
| } |
| }; |
| 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(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")); |
| } |
| |
| TEST(HttpUtilTest, GenerateAcceptCharsetHeader) { |
| EXPECT_EQ(std::string("utf-8,*;q=0.5"), |
| HttpUtil::GenerateAcceptCharsetHeader("utf-8")); |
| EXPECT_EQ(std::string("EUC-JP,utf-8;q=0.7,*;q=0.3"), |
| HttpUtil::GenerateAcceptCharsetHeader("EUC-JP")); |
| } |