| // Copyright 2016 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 "components/safe_browsing_db/v4_protocol_manager_util.h" |
| |
| #include <vector> |
| |
| #include "base/base64.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/time/time.h" |
| #include "components/safe_browsing_db/v4_test_util.h" |
| #include "net/base/escape.h" |
| #include "net/http/http_request_headers.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using base::Time; |
| using base::TimeDelta; |
| |
| namespace { |
| |
| bool VectorContains(const std::vector<std::string>& data, |
| const std::string& str) { |
| return std::find(data.begin(), data.end(), str) != data.end(); |
| } |
| |
| } // namespace |
| |
| namespace safe_browsing { |
| |
| class V4ProtocolManagerUtilTest : public testing::Test { |
| }; |
| |
| TEST_F(V4ProtocolManagerUtilTest, TestBackOffLogic) { |
| size_t error_count = 0, back_off_multiplier = 1; |
| |
| // 1 error. |
| base::TimeDelta next = V4ProtocolManagerUtil::GetNextBackOffInterval( |
| &error_count, &back_off_multiplier); |
| EXPECT_EQ(1U, error_count); |
| EXPECT_EQ(1U, back_off_multiplier); |
| EXPECT_LE(TimeDelta::FromMinutes(15), next); |
| EXPECT_GE(TimeDelta::FromMinutes(30), next); |
| |
| // 2 errors. |
| next = V4ProtocolManagerUtil::GetNextBackOffInterval(&error_count, |
| &back_off_multiplier); |
| EXPECT_EQ(2U, error_count); |
| EXPECT_EQ(2U, back_off_multiplier); |
| EXPECT_LE(TimeDelta::FromMinutes(30), next); |
| EXPECT_GE(TimeDelta::FromMinutes(60), next); |
| |
| // 3 errors. |
| next = V4ProtocolManagerUtil::GetNextBackOffInterval(&error_count, |
| &back_off_multiplier); |
| EXPECT_EQ(3U, error_count); |
| EXPECT_EQ(4U, back_off_multiplier); |
| EXPECT_LE(TimeDelta::FromMinutes(60), next); |
| EXPECT_GE(TimeDelta::FromMinutes(120), next); |
| |
| // 4 errors. |
| next = V4ProtocolManagerUtil::GetNextBackOffInterval(&error_count, |
| &back_off_multiplier); |
| EXPECT_EQ(4U, error_count); |
| EXPECT_EQ(8U, back_off_multiplier); |
| EXPECT_LE(TimeDelta::FromMinutes(120), next); |
| EXPECT_GE(TimeDelta::FromMinutes(240), next); |
| |
| // 5 errors. |
| next = V4ProtocolManagerUtil::GetNextBackOffInterval(&error_count, |
| &back_off_multiplier); |
| EXPECT_EQ(5U, error_count); |
| EXPECT_EQ(16U, back_off_multiplier); |
| EXPECT_LE(TimeDelta::FromMinutes(240), next); |
| EXPECT_GE(TimeDelta::FromMinutes(480), next); |
| |
| // 6 errors. |
| next = V4ProtocolManagerUtil::GetNextBackOffInterval(&error_count, |
| &back_off_multiplier); |
| EXPECT_EQ(6U, error_count); |
| EXPECT_EQ(32U, back_off_multiplier); |
| EXPECT_LE(TimeDelta::FromMinutes(480), next); |
| EXPECT_GE(TimeDelta::FromMinutes(960), next); |
| |
| // 7 errors. |
| next = V4ProtocolManagerUtil::GetNextBackOffInterval(&error_count, |
| &back_off_multiplier); |
| EXPECT_EQ(7U, error_count); |
| EXPECT_EQ(64U, back_off_multiplier); |
| EXPECT_LE(TimeDelta::FromMinutes(960), next); |
| EXPECT_GE(TimeDelta::FromMinutes(1920), next); |
| |
| // 8 errors, reached max backoff. |
| next = V4ProtocolManagerUtil::GetNextBackOffInterval(&error_count, |
| &back_off_multiplier); |
| EXPECT_EQ(8U, error_count); |
| EXPECT_EQ(128U, back_off_multiplier); |
| EXPECT_EQ(TimeDelta::FromHours(24), next); |
| |
| // 9 errors, reached max backoff and multiplier capped. |
| next = V4ProtocolManagerUtil::GetNextBackOffInterval(&error_count, |
| &back_off_multiplier); |
| EXPECT_EQ(9U, error_count); |
| EXPECT_EQ(128U, back_off_multiplier); |
| EXPECT_EQ(TimeDelta::FromHours(24), next); |
| } |
| |
| TEST_F(V4ProtocolManagerUtilTest, TestGetRequestUrlAndUpdateHeaders) { |
| net::HttpRequestHeaders headers; |
| GURL gurl; |
| V4ProtocolManagerUtil::GetRequestUrlAndHeaders("request_base64", "someMethod", |
| GetTestV4ProtocolConfig(), |
| &gurl, &headers); |
| std::string expectedUrl = |
| "https://safebrowsing.googleapis.com/v4/someMethod?" |
| "$req=request_base64&$ct=application/x-protobuf&key=test_key_param"; |
| EXPECT_EQ(expectedUrl, gurl.spec()); |
| std::string header_value; |
| EXPECT_TRUE(headers.GetHeader("X-HTTP-Method-Override", &header_value)); |
| EXPECT_EQ("POST", header_value); |
| } |
| |
| // Tests that we generate the required host/path combinations for testing |
| // according to the Safe Browsing spec. |
| // See: https://developers.google.com/safe-browsing/v4/urls-hashing |
| TEST_F(V4ProtocolManagerUtilTest, UrlParsing) { |
| std::vector<std::string> hosts, paths; |
| |
| GURL url("http://a.b.c/1/2.html?param=1"); |
| V4ProtocolManagerUtil::GenerateHostsToCheck(url, &hosts); |
| V4ProtocolManagerUtil::GeneratePathsToCheck(url, &paths); |
| EXPECT_EQ(hosts.size(), static_cast<size_t>(2)); |
| EXPECT_EQ(paths.size(), static_cast<size_t>(4)); |
| EXPECT_EQ(hosts[0], "b.c"); |
| EXPECT_EQ(hosts[1], "a.b.c"); |
| |
| EXPECT_TRUE(VectorContains(paths, "/1/2.html?param=1")); |
| EXPECT_TRUE(VectorContains(paths, "/1/2.html")); |
| EXPECT_TRUE(VectorContains(paths, "/1/")); |
| EXPECT_TRUE(VectorContains(paths, "/")); |
| |
| url = GURL("http://a.b.c.d.e.f.g/1.html"); |
| V4ProtocolManagerUtil::GenerateHostsToCheck(url, &hosts); |
| V4ProtocolManagerUtil::GeneratePathsToCheck(url, &paths); |
| EXPECT_EQ(hosts.size(), static_cast<size_t>(5)); |
| EXPECT_EQ(paths.size(), static_cast<size_t>(2)); |
| EXPECT_EQ(hosts[0], "f.g"); |
| EXPECT_EQ(hosts[1], "e.f.g"); |
| EXPECT_EQ(hosts[2], "d.e.f.g"); |
| EXPECT_EQ(hosts[3], "c.d.e.f.g"); |
| EXPECT_EQ(hosts[4], "a.b.c.d.e.f.g"); |
| EXPECT_TRUE(VectorContains(paths, "/1.html")); |
| EXPECT_TRUE(VectorContains(paths, "/")); |
| |
| url = GURL("http://a.b/saw-cgi/eBayISAPI.dll/"); |
| V4ProtocolManagerUtil::GeneratePathsToCheck(url, &paths); |
| EXPECT_EQ(paths.size(), static_cast<size_t>(3)); |
| EXPECT_TRUE(VectorContains(paths, "/saw-cgi/eBayISAPI.dll/")); |
| EXPECT_TRUE(VectorContains(paths, "/saw-cgi/")); |
| EXPECT_TRUE(VectorContains(paths, "/")); |
| } |
| |
| // Tests the url canonicalization according to the Safe Browsing spec. |
| // See: https://developers.google.com/safe-browsing/v4/urls-hashing |
| TEST_F(V4ProtocolManagerUtilTest, CanonicalizeUrl) { |
| struct { |
| const char* input_url; |
| const char* expected_canonicalized_hostname; |
| const char* expected_canonicalized_path; |
| const char* expected_canonicalized_query; |
| } tests[] = { |
| {"http://host/%25%32%35", "host", "/%25", ""}, |
| {"http://host/%25%32%35%25%32%35", "host", "/%25%25", ""}, |
| {"http://host/%2525252525252525", "host", "/%25", ""}, |
| {"http://host/asdf%25%32%35asd", "host", "/asdf%25asd", ""}, |
| {"http://host/%%%25%32%35asd%%", "host", "/%25%25%25asd%25%25", ""}, |
| {"http://host/%%%25%32%35asd%%", "host", "/%25%25%25asd%25%25", ""}, |
| {"http://www.google.com/", "www.google.com", "/", ""}, |
| {"http://%31%36%38%2e%31%38%38%2e%39%39%2e%32%36/%2E%73%65%63%75%72%65/" |
| "%77" |
| "%77%77%2E%65%62%61%79%2E%63%6F%6D/", |
| "168.188.99.26", "/.secure/www.ebay.com/", ""}, |
| {"http://195.127.0.11/uploads/%20%20%20%20/.verify/" |
| ".eBaysecure=updateuserd" |
| "ataxplimnbqmn-xplmvalidateinfoswqpcmlx=hgplmcx/", |
| "195.127.0.11", |
| "/uploads/%20%20%20%20/.verify/" |
| ".eBaysecure=updateuserdataxplimnbqmn-xplmv" |
| "alidateinfoswqpcmlx=hgplmcx/", |
| ""}, |
| {"http://host.com/%257Ea%2521b%2540c%2523d%2524e%25f%255E00%252611%252A" |
| "22%252833%252944_55%252B", |
| "host.com", "/~a!b@c%23d$e%25f^00&11*22(33)44_55+", ""}, |
| {"http://3279880203/blah", "195.127.0.11", "/blah", ""}, |
| {"http://www.google.com/blah/..", "www.google.com", "/", ""}, |
| {"http://www.google.com/blah#fraq", "www.google.com", "/blah", ""}, |
| {"http://www.GOOgle.com/", "www.google.com", "/", ""}, |
| {"http://www.google.com.../", "www.google.com", "/", ""}, |
| {"http://www.google.com/q?", "www.google.com", "/q", ""}, |
| {"http://www.google.com/q?r?", "www.google.com", "/q", "r?"}, |
| {"http://www.google.com/q?r?s", "www.google.com", "/q", "r?s"}, |
| {"http://evil.com/foo#bar#baz", "evil.com", "/foo", ""}, |
| {"http://evil.com/foo;", "evil.com", "/foo;", ""}, |
| {"http://evil.com/foo?bar;", "evil.com", "/foo", "bar;"}, |
| {"http://notrailingslash.com", "notrailingslash.com", "/", ""}, |
| {"http://www.gotaport.com:1234/", "www.gotaport.com", "/", ""}, |
| {" http://www.google.com/ ", "www.google.com", "/", ""}, |
| {"http:// leadingspace.com/", "%20leadingspace.com", "/", ""}, |
| {"http://%20leadingspace.com/", "%20leadingspace.com", "/", ""}, |
| {"https://www.securesite.com/", "www.securesite.com", "/", ""}, |
| {"http://host.com/ab%23cd", "host.com", "/ab%23cd", ""}, |
| {"http://host%3e.com//twoslashes?more//slashes", "host>.com", |
| "/twoslashes", "more//slashes"}, |
| {"http://host.com/abc?val=xyz#anything", "host.com", "/abc", "val=xyz"}, |
| {"http://abc:def@host.com/xyz", "host.com", "/xyz", ""}, |
| {"http://host%3e.com/abc/%2e%2e%2fdef", "host>.com", "/def", ""}, |
| {"http://.......host...com.....//abc/////def%2F%2F%2Fxyz", "host.com", |
| "/abc/def/xyz", ""}, |
| {"ftp://host.com/foo?bar", "host.com", "/foo", "bar"}, |
| {"data:text/html;charset=utf-8,%0D%0A", "", "", ""}, |
| {"javascript:alert()", "", "", ""}, |
| {"mailto:abc@example.com", "", "", ""}, |
| }; |
| for (size_t i = 0; i < arraysize(tests); ++i) { |
| SCOPED_TRACE(base::StringPrintf("Test: %s", tests[i].input_url)); |
| GURL url(tests[i].input_url); |
| |
| std::string canonicalized_hostname; |
| std::string canonicalized_path; |
| std::string canonicalized_query; |
| V4ProtocolManagerUtil::CanonicalizeUrl(url, &canonicalized_hostname, |
| &canonicalized_path, |
| &canonicalized_query); |
| |
| EXPECT_EQ(tests[i].expected_canonicalized_hostname, canonicalized_hostname); |
| EXPECT_EQ(tests[i].expected_canonicalized_path, canonicalized_path); |
| EXPECT_EQ(tests[i].expected_canonicalized_query, canonicalized_query); |
| } |
| } |
| |
| TEST_F(V4ProtocolManagerUtilTest, TestIPAddressToEncodedIPV6) { |
| // To verify the test values, here's the python code: |
| // >> import socket, hashlib, binascii |
| // >> hashlib.sha1(socket.inet_pton(socket.AF_INET6, input)).digest() + |
| // chr(128) |
| // For example: |
| // >>> hashlib.sha1(socket.inet_pton(socket.AF_INET6, |
| // '::ffff:192.168.1.1')).digest() + chr(128) |
| // 'X\xf8\xa1\x17I\xe6Pl\xfd\xdb\xbb\xa0\x0c\x02\x9d#\n|\xe7\xcd\x80' |
| std::vector<std::tuple<bool, std::string, std::string>> test_cases = { |
| std::make_tuple(false, "", ""), |
| std::make_tuple( |
| true, "192.168.1.1", |
| "X\xF8\xA1\x17I\xE6Pl\xFD\xDB\xBB\xA0\f\x2\x9D#\n|\xE7\xCD\x80"), |
| std::make_tuple( |
| true, "::", |
| "\xE1)\xF2|Q\x3\xBC\\\xC4K\xCD\xF0\xA1^\x16\rDPf\xFF\x80")}; |
| for (size_t i = 0; i < test_cases.size(); i++) { |
| DVLOG(1) << "Running case: " << i; |
| bool success = std::get<0>(test_cases[i]); |
| const auto& input = std::get<1>(test_cases[i]); |
| const auto& expected_output = std::get<2>(test_cases[i]); |
| std::string encoded_ip; |
| ASSERT_EQ(success, V4ProtocolManagerUtil::IPAddressToEncodedIPV6Hash( |
| input, &encoded_ip)); |
| if (success) { |
| ASSERT_EQ(expected_output, encoded_ip); |
| } |
| } |
| } |
| |
| } // namespace safe_browsing |