| // Copyright (c) 2011 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 <map> |
| #include <queue> |
| #include <string> |
| |
| #include "base/callback.h" |
| #include "base/logging.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "base/message_loop.h" |
| #include "base/task.h" |
| #include "base/time.h" |
| #include "chrome/browser/safe_browsing/client_side_detection_service.h" |
| #include "chrome/common/safe_browsing/client_model.pb.h" |
| #include "chrome/common/safe_browsing/csd.pb.h" |
| #include "content/browser/browser_thread.h" |
| #include "content/common/net/url_fetcher.h" |
| #include "content/test/test_url_fetcher_factory.h" |
| #include "crypto/sha2.h" |
| #include "googleurl/src/gurl.h" |
| #include "net/url_request/url_request_status.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using ::testing::_; |
| using ::testing::Invoke; |
| using ::testing::Mock; |
| using ::testing::StrictMock; |
| |
| namespace safe_browsing { |
| namespace { |
| class MockClientSideDetectionService : public ClientSideDetectionService { |
| public: |
| MockClientSideDetectionService() : ClientSideDetectionService(NULL) {} |
| virtual ~MockClientSideDetectionService() {} |
| |
| MOCK_METHOD1(EndFetchModel, void(ClientModelStatus)); |
| MOCK_METHOD1(ScheduleFetchModel, void(int64)); |
| |
| void Schedule(int64) { |
| // Ignore the delay when testing. |
| StartFetchModel(); |
| } |
| |
| void Disable(int) { |
| // Ignore the status. |
| SetEnabledAndRefreshState(false); |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(MockClientSideDetectionService); |
| }; |
| |
| ACTION(QuitCurrentMessageLoop) { |
| MessageLoop::current()->Quit(); |
| } |
| |
| } // namespace |
| |
| class ClientSideDetectionServiceTest : public testing::Test { |
| protected: |
| virtual void SetUp() { |
| file_thread_.reset(new BrowserThread(BrowserThread::FILE, &msg_loop_)); |
| |
| factory_.reset(new FakeURLFetcherFactory()); |
| |
| browser_thread_.reset(new BrowserThread(BrowserThread::UI, &msg_loop_)); |
| } |
| |
| virtual void TearDown() { |
| msg_loop_.RunAllPending(); |
| csd_service_.reset(); |
| file_thread_.reset(); |
| browser_thread_.reset(); |
| } |
| |
| bool SendClientReportPhishingRequest(const GURL& phishing_url, |
| float score) { |
| ClientPhishingRequest* request = new ClientPhishingRequest(); |
| request->set_url(phishing_url.spec()); |
| request->set_client_score(score); |
| request->set_is_phishing(true); // client thinks the URL is phishing. |
| csd_service_->SendClientReportPhishingRequest( |
| request, |
| NewCallback(this, &ClientSideDetectionServiceTest::SendRequestDone)); |
| phishing_url_ = phishing_url; |
| msg_loop_.Run(); // Waits until callback is called. |
| return is_phishing_; |
| } |
| |
| void SetModelFetchResponse(std::string response_data, bool success) { |
| factory_->SetFakeResponse(ClientSideDetectionService::kClientModelUrl, |
| response_data, success); |
| } |
| |
| void SetClientReportPhishingResponse(std::string response_data, |
| bool success) { |
| factory_->SetFakeResponse( |
| ClientSideDetectionService::kClientReportPhishingUrl, |
| response_data, success); |
| } |
| |
| int GetNumReports() { |
| return csd_service_->GetNumReports(); |
| } |
| |
| std::queue<base::Time>& GetPhishingReportTimes() { |
| return csd_service_->phishing_report_times_; |
| } |
| |
| void SetCache(const GURL& gurl, bool is_phishing, base::Time time) { |
| csd_service_->cache_[gurl] = |
| make_linked_ptr(new ClientSideDetectionService::CacheState(is_phishing, |
| time)); |
| } |
| |
| void TestCache() { |
| ClientSideDetectionService::PhishingCache& cache = csd_service_->cache_; |
| base::Time now = base::Time::Now(); |
| base::Time time = now - ClientSideDetectionService::kNegativeCacheInterval + |
| base::TimeDelta::FromMinutes(5); |
| cache[GURL("http://first.url.com/")] = |
| make_linked_ptr(new ClientSideDetectionService::CacheState(false, |
| time)); |
| |
| time = now - ClientSideDetectionService::kNegativeCacheInterval - |
| base::TimeDelta::FromHours(1); |
| cache[GURL("http://second.url.com/")] = |
| make_linked_ptr(new ClientSideDetectionService::CacheState(false, |
| time)); |
| |
| time = now - ClientSideDetectionService::kPositiveCacheInterval - |
| base::TimeDelta::FromMinutes(5); |
| cache[GURL("http://third.url.com/")] = |
| make_linked_ptr(new ClientSideDetectionService::CacheState(true, time)); |
| |
| time = now - ClientSideDetectionService::kPositiveCacheInterval + |
| base::TimeDelta::FromMinutes(5); |
| cache[GURL("http://fourth.url.com/")] = |
| make_linked_ptr(new ClientSideDetectionService::CacheState(true, time)); |
| |
| csd_service_->UpdateCache(); |
| |
| // 3 elements should be in the cache, the first, third, and fourth. |
| EXPECT_EQ(3U, cache.size()); |
| EXPECT_TRUE(cache.find(GURL("http://first.url.com/")) != cache.end()); |
| EXPECT_TRUE(cache.find(GURL("http://third.url.com/")) != cache.end()); |
| EXPECT_TRUE(cache.find(GURL("http://fourth.url.com/")) != cache.end()); |
| |
| // While 3 elements remain, only the first and the fourth are actually |
| // valid. |
| bool is_phishing; |
| EXPECT_TRUE(csd_service_->GetValidCachedResult( |
| GURL("http://first.url.com"), &is_phishing)); |
| EXPECT_FALSE(is_phishing); |
| EXPECT_FALSE(csd_service_->GetValidCachedResult( |
| GURL("http://third.url.com"), &is_phishing)); |
| EXPECT_TRUE(csd_service_->GetValidCachedResult( |
| GURL("http://fourth.url.com"), &is_phishing)); |
| EXPECT_TRUE(is_phishing); |
| } |
| |
| void AddFeature(const std::string& name, double value, |
| ClientPhishingRequest* request) { |
| ClientPhishingRequest_Feature* feature = request->add_feature_map(); |
| feature->set_name(name); |
| feature->set_value(value); |
| } |
| |
| void AddNonModelFeature(const std::string& name, double value, |
| ClientPhishingRequest* request) { |
| ClientPhishingRequest_Feature* feature = |
| request->add_non_model_feature_map(); |
| feature->set_name(name); |
| feature->set_value(value); |
| } |
| |
| protected: |
| scoped_ptr<ClientSideDetectionService> csd_service_; |
| scoped_ptr<FakeURLFetcherFactory> factory_; |
| MessageLoop msg_loop_; |
| |
| private: |
| void SendRequestDone(GURL phishing_url, bool is_phishing) { |
| ASSERT_EQ(phishing_url, phishing_url_); |
| is_phishing_ = is_phishing; |
| msg_loop_.Quit(); |
| } |
| |
| scoped_ptr<BrowserThread> browser_thread_; |
| scoped_ptr<BrowserThread> file_thread_; |
| |
| GURL phishing_url_; |
| bool is_phishing_; |
| }; |
| |
| TEST_F(ClientSideDetectionServiceTest, FetchModelTest) { |
| // We don't want to use a real service class here because we can't call |
| // the real EndFetchModel. It would reschedule a reload which might |
| // make the test flaky. |
| MockClientSideDetectionService service; |
| EXPECT_CALL(service, ScheduleFetchModel(_)).Times(1); |
| service.SetEnabledAndRefreshState(true); |
| |
| // The model fetch failed. |
| SetModelFetchResponse("blamodel", false /* failure */); |
| EXPECT_CALL(service, EndFetchModel( |
| ClientSideDetectionService::MODEL_FETCH_FAILED)) |
| .WillOnce(QuitCurrentMessageLoop()); |
| service.StartFetchModel(); |
| msg_loop_.Run(); // EndFetchModel will quit the message loop. |
| Mock::VerifyAndClearExpectations(&service); |
| |
| // Empty model file. |
| SetModelFetchResponse("", true /* success */); |
| EXPECT_CALL(service, EndFetchModel( |
| ClientSideDetectionService::MODEL_EMPTY)) |
| .WillOnce(QuitCurrentMessageLoop()); |
| service.StartFetchModel(); |
| msg_loop_.Run(); // EndFetchModel will quit the message loop. |
| Mock::VerifyAndClearExpectations(&service); |
| |
| // Model is too large. |
| SetModelFetchResponse( |
| std::string(ClientSideDetectionService::kMaxModelSizeBytes + 1, 'x'), |
| true /* success */); |
| EXPECT_CALL(service, EndFetchModel( |
| ClientSideDetectionService::MODEL_TOO_LARGE)) |
| .WillOnce(QuitCurrentMessageLoop()); |
| service.StartFetchModel(); |
| msg_loop_.Run(); // EndFetchModel will quit the message loop. |
| Mock::VerifyAndClearExpectations(&service); |
| |
| // Unable to parse the model file. |
| SetModelFetchResponse("Invalid model file", true /* success */); |
| EXPECT_CALL(service, EndFetchModel( |
| ClientSideDetectionService::MODEL_PARSE_ERROR)) |
| .WillOnce(QuitCurrentMessageLoop()); |
| service.StartFetchModel(); |
| msg_loop_.Run(); // EndFetchModel will quit the message loop. |
| Mock::VerifyAndClearExpectations(&service); |
| |
| // Model that is missing some required fields (missing the version field). |
| ClientSideModel model; |
| model.set_max_words_per_term(4); |
| SetModelFetchResponse(model.SerializePartialAsString(), true /* success */); |
| EXPECT_CALL(service, EndFetchModel( |
| ClientSideDetectionService::MODEL_MISSING_FIELDS)) |
| .WillOnce(QuitCurrentMessageLoop()); |
| service.StartFetchModel(); |
| msg_loop_.Run(); // EndFetchModel will quit the message loop. |
| Mock::VerifyAndClearExpectations(&service); |
| |
| // Model that points to hashes that don't exist. |
| model.set_version(10); |
| model.add_hashes("bla"); |
| model.add_page_term(1); // Should be 0 instead of 1. |
| SetModelFetchResponse(model.SerializePartialAsString(), true /* success */); |
| EXPECT_CALL(service, EndFetchModel( |
| ClientSideDetectionService::MODEL_BAD_HASH_IDS)) |
| .WillOnce(QuitCurrentMessageLoop()); |
| service.StartFetchModel(); |
| msg_loop_.Run(); // EndFetchModel will quit the message loop. |
| Mock::VerifyAndClearExpectations(&service); |
| model.set_page_term(0, 0); |
| |
| // Model version number is wrong. |
| model.set_version(-1); |
| SetModelFetchResponse(model.SerializeAsString(), true /* success */); |
| EXPECT_CALL(service, EndFetchModel( |
| ClientSideDetectionService::MODEL_INVALID_VERSION_NUMBER)) |
| .WillOnce(QuitCurrentMessageLoop()); |
| service.StartFetchModel(); |
| msg_loop_.Run(); // EndFetchModel will quit the message loop. |
| Mock::VerifyAndClearExpectations(&service); |
| |
| // Normal model. |
| model.set_version(10); |
| SetModelFetchResponse(model.SerializeAsString(), true /* success */); |
| EXPECT_CALL(service, EndFetchModel( |
| ClientSideDetectionService::MODEL_SUCCESS)) |
| .WillOnce(QuitCurrentMessageLoop()); |
| service.StartFetchModel(); |
| msg_loop_.Run(); // EndFetchModel will quit the message loop. |
| Mock::VerifyAndClearExpectations(&service); |
| |
| // Model version number is decreasing. Set the model version number of the |
| // model that is currently loaded in the service object to 11. |
| service.model_.reset(new ClientSideModel(model)); |
| service.model_->set_version(11); |
| SetModelFetchResponse(model.SerializeAsString(), true /* success */); |
| EXPECT_CALL(service, EndFetchModel( |
| ClientSideDetectionService::MODEL_INVALID_VERSION_NUMBER)) |
| .WillOnce(QuitCurrentMessageLoop()); |
| service.StartFetchModel(); |
| msg_loop_.Run(); // EndFetchModel will quit the message loop. |
| Mock::VerifyAndClearExpectations(&service); |
| |
| // Model version hasn't changed since the last reload. |
| service.model_->set_version(10); |
| SetModelFetchResponse(model.SerializeAsString(), true /* success */); |
| EXPECT_CALL(service, EndFetchModel( |
| ClientSideDetectionService::MODEL_NOT_CHANGED)) |
| .WillOnce(QuitCurrentMessageLoop()); |
| service.StartFetchModel(); |
| msg_loop_.Run(); // EndFetchModel will quit the message loop. |
| Mock::VerifyAndClearExpectations(&service); |
| } |
| |
| TEST_F(ClientSideDetectionServiceTest, ServiceObjectDeletedBeforeCallbackDone) { |
| SetModelFetchResponse("bogus model", true /* success */); |
| csd_service_.reset(ClientSideDetectionService::Create(NULL)); |
| csd_service_->SetEnabledAndRefreshState(true); |
| EXPECT_TRUE(csd_service_.get() != NULL); |
| // We delete the client-side detection service class even though the callbacks |
| // haven't run yet. |
| csd_service_.reset(); |
| // Waiting for the callbacks to run should not crash even if the service |
| // object is gone. |
| msg_loop_.RunAllPending(); |
| } |
| |
| TEST_F(ClientSideDetectionServiceTest, SendClientReportPhishingRequest) { |
| SetModelFetchResponse("bogus model", true /* success */); |
| csd_service_.reset(ClientSideDetectionService::Create(NULL)); |
| csd_service_->SetEnabledAndRefreshState(true); |
| |
| GURL url("http://a.com/"); |
| float score = 0.4f; // Some random client score. |
| |
| base::Time before = base::Time::Now(); |
| |
| // Invalid response body from the server. |
| SetClientReportPhishingResponse("invalid proto response", true /* success */); |
| EXPECT_FALSE(SendClientReportPhishingRequest(url, score)); |
| |
| // Normal behavior. |
| ClientPhishingResponse response; |
| response.set_phishy(true); |
| SetClientReportPhishingResponse(response.SerializeAsString(), |
| true /* success */); |
| EXPECT_TRUE(SendClientReportPhishingRequest(url, score)); |
| |
| // This request will fail |
| GURL second_url("http://b.com/"); |
| response.set_phishy(false); |
| SetClientReportPhishingResponse(response.SerializeAsString(), |
| false /* success */); |
| EXPECT_FALSE(SendClientReportPhishingRequest(second_url, score)); |
| |
| // This is a false positive. |
| response.set_phishy(true); |
| response.add_whitelist_expression("c.com/a.html"); |
| SetClientReportPhishingResponse(response.SerializeAsString(), |
| true /* success */); |
| GURL third_url("http://c.com/"); |
| EXPECT_FALSE(SendClientReportPhishingRequest(third_url, score)); |
| |
| base::Time after = base::Time::Now(); |
| |
| // Check that we have recorded all 4 requests within the correct time range. |
| std::queue<base::Time>& report_times = GetPhishingReportTimes(); |
| EXPECT_EQ(4U, report_times.size()); |
| while (!report_times.empty()) { |
| base::Time time = report_times.back(); |
| report_times.pop(); |
| EXPECT_LE(before, time); |
| EXPECT_GE(after, time); |
| } |
| |
| // Only the first url should be in the cache. |
| bool is_phishing; |
| EXPECT_TRUE(csd_service_->IsInCache(url)); |
| EXPECT_TRUE(csd_service_->GetValidCachedResult(url, &is_phishing)); |
| EXPECT_TRUE(is_phishing); |
| EXPECT_FALSE(csd_service_->IsInCache(second_url)); |
| } |
| |
| TEST_F(ClientSideDetectionServiceTest, GetNumReportTest) { |
| SetModelFetchResponse("bogus model", true /* success */); |
| csd_service_.reset(ClientSideDetectionService::Create(NULL)); |
| |
| std::queue<base::Time>& report_times = GetPhishingReportTimes(); |
| base::Time now = base::Time::Now(); |
| base::TimeDelta twenty_five_hours = base::TimeDelta::FromHours(25); |
| report_times.push(now - twenty_five_hours); |
| report_times.push(now - twenty_five_hours); |
| report_times.push(now); |
| report_times.push(now); |
| |
| EXPECT_EQ(2, GetNumReports()); |
| } |
| |
| TEST_F(ClientSideDetectionServiceTest, CacheTest) { |
| SetModelFetchResponse("bogus model", true /* success */); |
| csd_service_.reset(ClientSideDetectionService::Create(NULL)); |
| |
| TestCache(); |
| } |
| |
| TEST_F(ClientSideDetectionServiceTest, IsPrivateIPAddress) { |
| SetModelFetchResponse("bogus model", true /* success */); |
| csd_service_.reset(ClientSideDetectionService::Create(NULL)); |
| |
| EXPECT_TRUE(csd_service_->IsPrivateIPAddress("10.1.2.3")); |
| EXPECT_TRUE(csd_service_->IsPrivateIPAddress("127.0.0.1")); |
| EXPECT_TRUE(csd_service_->IsPrivateIPAddress("172.24.3.4")); |
| EXPECT_TRUE(csd_service_->IsPrivateIPAddress("192.168.1.1")); |
| EXPECT_TRUE(csd_service_->IsPrivateIPAddress("fc00::")); |
| EXPECT_TRUE(csd_service_->IsPrivateIPAddress("fec0::")); |
| EXPECT_TRUE(csd_service_->IsPrivateIPAddress("fec0:1:2::3")); |
| EXPECT_TRUE(csd_service_->IsPrivateIPAddress("::1")); |
| |
| EXPECT_FALSE(csd_service_->IsPrivateIPAddress("1.2.3.4")); |
| EXPECT_FALSE(csd_service_->IsPrivateIPAddress("200.1.1.1")); |
| EXPECT_FALSE(csd_service_->IsPrivateIPAddress("2001:0db8:ac10:fe01::")); |
| |
| // If the address can't be parsed, the default is true. |
| EXPECT_TRUE(csd_service_->IsPrivateIPAddress("blah")); |
| } |
| |
| TEST_F(ClientSideDetectionServiceTest, SetBadSubnets) { |
| ClientSideModel model; |
| ClientSideDetectionService::BadSubnetMap bad_subnets; |
| ClientSideDetectionService::SetBadSubnets(model, &bad_subnets); |
| EXPECT_EQ(0U, bad_subnets.size()); |
| |
| // Bad subnets are skipped. |
| ClientSideModel::IPSubnet* subnet = model.add_bad_subnet(); |
| subnet->set_prefix(std::string(crypto::kSHA256Length, '.')); |
| subnet->set_size(130); // Invalid size. |
| |
| subnet = model.add_bad_subnet(); |
| subnet->set_prefix(std::string(crypto::kSHA256Length, '.')); |
| subnet->set_size(-1); // Invalid size. |
| |
| subnet = model.add_bad_subnet(); |
| subnet->set_prefix(std::string(16, '.')); // Invalid len. |
| subnet->set_size(64); |
| |
| ClientSideDetectionService::SetBadSubnets(model, &bad_subnets); |
| EXPECT_EQ(0U, bad_subnets.size()); |
| |
| subnet = model.add_bad_subnet(); |
| subnet->set_prefix(std::string(crypto::kSHA256Length, '.')); |
| subnet->set_size(64); |
| |
| subnet = model.add_bad_subnet(); |
| subnet->set_prefix(std::string(crypto::kSHA256Length, ',')); |
| subnet->set_size(64); |
| |
| subnet = model.add_bad_subnet(); |
| subnet->set_prefix(std::string(crypto::kSHA256Length, '.')); |
| subnet->set_size(128); |
| |
| subnet = model.add_bad_subnet(); |
| subnet->set_prefix(std::string(crypto::kSHA256Length, '.')); |
| subnet->set_size(100); |
| |
| ClientSideDetectionService::SetBadSubnets(model, &bad_subnets); |
| EXPECT_EQ(3U, bad_subnets.size()); |
| ClientSideDetectionService::BadSubnetMap::const_iterator it; |
| std::string mask = std::string(8, '\xFF') + std::string(8, '\x00'); |
| EXPECT_TRUE(bad_subnets.count(mask)); |
| EXPECT_TRUE(bad_subnets[mask].count(std::string(crypto::kSHA256Length, '.'))); |
| EXPECT_TRUE(bad_subnets[mask].count(std::string(crypto::kSHA256Length, ','))); |
| |
| mask = std::string(16, '\xFF'); |
| EXPECT_TRUE(bad_subnets.count(mask)); |
| EXPECT_TRUE(bad_subnets[mask].count(std::string(crypto::kSHA256Length, '.'))); |
| |
| mask = std::string(12, '\xFF') + "\xF0" + std::string(3, '\x00'); |
| EXPECT_TRUE(bad_subnets.count(mask)); |
| EXPECT_TRUE(bad_subnets[mask].count(std::string(crypto::kSHA256Length, '.'))); |
| } |
| |
| TEST_F(ClientSideDetectionServiceTest, IsBadIpAddress) { |
| ClientSideModel model; |
| // IPv6 exact match for: 2620:0:1000:3103:21a:a0ff:fe10:786e. |
| ClientSideModel::IPSubnet* subnet = model.add_bad_subnet(); |
| subnet->set_prefix(crypto::SHA256HashString(std::string( |
| "\x26\x20\x00\x00\x10\x00\x31\x03\x02\x1a\xa0\xff\xfe\x10\x78\x6e", 16))); |
| subnet->set_size(128); |
| |
| // IPv6 prefix match for: fe80::21a:a0ff:fe10:786e/64. |
| subnet = model.add_bad_subnet(); |
| subnet->set_prefix(crypto::SHA256HashString(std::string( |
| "\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 16))); |
| subnet->set_size(64); |
| |
| // IPv4 exact match for ::ffff:192.0.2.128. |
| subnet = model.add_bad_subnet(); |
| subnet->set_prefix(crypto::SHA256HashString(std::string( |
| "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xc0\x00\x02\x80", 16))); |
| subnet->set_size(128); |
| |
| // IPv4 prefix match (/8) for ::ffff:192.1.1.0. |
| subnet = model.add_bad_subnet(); |
| subnet->set_prefix(crypto::SHA256HashString(std::string( |
| "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xc0\x01\x01\x00", 16))); |
| subnet->set_size(120); |
| |
| // IPv4 prefix match (/9) for ::ffff:192.1.122.0. |
| subnet = model.add_bad_subnet(); |
| subnet->set_prefix(crypto::SHA256HashString(std::string( |
| "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xc0\x01\x7a\x00", 16))); |
| subnet->set_size(119); |
| |
| // IPv4 prefix match (/15) for ::ffff:192.1.128.0. |
| subnet = model.add_bad_subnet(); |
| subnet->set_prefix(crypto::SHA256HashString(std::string( |
| "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xc0\x01\x80\x00", 16))); |
| subnet->set_size(113); |
| |
| csd_service_.reset(ClientSideDetectionService::Create(NULL)); |
| ClientSideDetectionService::SetBadSubnets( |
| model, &(csd_service_->bad_subnets_)); |
| EXPECT_FALSE(csd_service_->IsBadIpAddress("blabla")); |
| EXPECT_FALSE(csd_service_->IsBadIpAddress("")); |
| |
| EXPECT_TRUE(csd_service_->IsBadIpAddress( |
| "2620:0:1000:3103:21a:a0ff:fe10:786e")); |
| EXPECT_FALSE(csd_service_->IsBadIpAddress( |
| "2620:0:1000:3103:21a:a0ff:fe10:786f")); |
| |
| EXPECT_TRUE(csd_service_->IsBadIpAddress("fe80::21a:a0ff:fe10:786e")); |
| EXPECT_TRUE(csd_service_->IsBadIpAddress("fe80::31a:a0ff:fe10:786e")); |
| EXPECT_TRUE(csd_service_->IsBadIpAddress("fe80::21a:a0ff:fe10:786f")); |
| EXPECT_FALSE(csd_service_->IsBadIpAddress("fe81::21a:a0ff:fe10:786e")); |
| |
| EXPECT_TRUE(csd_service_->IsBadIpAddress("192.0.2.128")); |
| EXPECT_TRUE(csd_service_->IsBadIpAddress("::ffff:192.0.2.128")); |
| EXPECT_FALSE(csd_service_->IsBadIpAddress("192.0.2.129")); |
| |
| EXPECT_TRUE(csd_service_->IsBadIpAddress("192.1.1.0")); |
| EXPECT_TRUE(csd_service_->IsBadIpAddress("192.1.1.255")); |
| EXPECT_TRUE(csd_service_->IsBadIpAddress("192.1.1.10")); |
| EXPECT_TRUE(csd_service_->IsBadIpAddress("::ffff:192.1.1.2")); |
| |
| EXPECT_FALSE(csd_service_->IsBadIpAddress("192.1.121.255")); |
| EXPECT_TRUE(csd_service_->IsBadIpAddress("192.1.122.0")); |
| EXPECT_TRUE(csd_service_->IsBadIpAddress("::ffff:192.1.122.1")); |
| EXPECT_TRUE(csd_service_->IsBadIpAddress("192.1.122.255")); |
| EXPECT_TRUE(csd_service_->IsBadIpAddress("192.1.123.0")); |
| EXPECT_TRUE(csd_service_->IsBadIpAddress("192.1.123.255")); |
| EXPECT_FALSE(csd_service_->IsBadIpAddress("192.1.124.0")); |
| |
| EXPECT_FALSE(csd_service_->IsBadIpAddress("192.1.127.255")); |
| EXPECT_TRUE(csd_service_->IsBadIpAddress("192.1.128.0")); |
| EXPECT_TRUE(csd_service_->IsBadIpAddress("::ffff:192.1.128.1")); |
| EXPECT_TRUE(csd_service_->IsBadIpAddress("192.1.128.255")); |
| EXPECT_TRUE(csd_service_->IsBadIpAddress("192.1.255.0")); |
| EXPECT_TRUE(csd_service_->IsBadIpAddress("192.1.255.255")); |
| EXPECT_FALSE(csd_service_->IsBadIpAddress("192.2.0.0")); |
| } |
| |
| TEST_F(ClientSideDetectionServiceTest, ModelHasValidHashIds) { |
| ClientSideModel model; |
| EXPECT_TRUE(ClientSideDetectionService::ModelHasValidHashIds(model)); |
| model.add_hashes("bla"); |
| EXPECT_TRUE(ClientSideDetectionService::ModelHasValidHashIds(model)); |
| model.add_page_term(0); |
| EXPECT_TRUE(ClientSideDetectionService::ModelHasValidHashIds(model)); |
| |
| model.add_page_term(-1); |
| EXPECT_FALSE(ClientSideDetectionService::ModelHasValidHashIds(model)); |
| model.set_page_term(1, 1); |
| EXPECT_FALSE(ClientSideDetectionService::ModelHasValidHashIds(model)); |
| model.set_page_term(1, 0); |
| EXPECT_TRUE(ClientSideDetectionService::ModelHasValidHashIds(model)); |
| |
| // Test bad rules. |
| model.add_hashes("blu"); |
| ClientSideModel::Rule* rule = model.add_rule(); |
| rule->add_feature(0); |
| rule->add_feature(1); |
| rule->set_weight(0.1f); |
| EXPECT_TRUE(ClientSideDetectionService::ModelHasValidHashIds(model)); |
| |
| rule = model.add_rule(); |
| rule->add_feature(0); |
| rule->add_feature(1); |
| rule->add_feature(-1); |
| rule->set_weight(0.2f); |
| EXPECT_FALSE(ClientSideDetectionService::ModelHasValidHashIds(model)); |
| |
| rule->set_feature(2, 2); |
| EXPECT_FALSE(ClientSideDetectionService::ModelHasValidHashIds(model)); |
| |
| rule->set_feature(2, 1); |
| EXPECT_TRUE(ClientSideDetectionService::ModelHasValidHashIds(model)); |
| } |
| |
| TEST_F(ClientSideDetectionServiceTest, SetEnabledAndRefreshState) { |
| // Check that the model isn't downloaded until the service is enabled. |
| csd_service_.reset(ClientSideDetectionService::Create(NULL)); |
| EXPECT_FALSE(csd_service_->enabled()); |
| EXPECT_TRUE(csd_service_->model_fetcher_.get() == NULL); |
| |
| // Use a MockClientSideDetectionService for the rest of the test, to avoid |
| // the scheduling delay. |
| MockClientSideDetectionService* service = |
| new StrictMock<MockClientSideDetectionService>(); |
| csd_service_.reset(service); |
| EXPECT_FALSE(csd_service_->enabled()); |
| EXPECT_TRUE(csd_service_->model_fetcher_.get() == NULL); |
| // No calls expected yet. |
| Mock::VerifyAndClearExpectations(service); |
| |
| ClientSideModel model; |
| model.set_version(10); |
| model.set_max_words_per_term(4); |
| SetModelFetchResponse(model.SerializeAsString(), true /* success */); |
| EXPECT_CALL(*service, ScheduleFetchModel(_)) |
| .WillOnce(Invoke(service, &MockClientSideDetectionService::Schedule)); |
| EXPECT_CALL(*service, EndFetchModel( |
| ClientSideDetectionService::MODEL_SUCCESS)) |
| .WillOnce(QuitCurrentMessageLoop()); |
| csd_service_->SetEnabledAndRefreshState(true); |
| EXPECT_TRUE(csd_service_->model_fetcher_.get() != NULL); |
| msg_loop_.Run(); // EndFetchModel will quit the message loop. |
| Mock::VerifyAndClearExpectations(service); |
| |
| // Check that enabling again doesn't request the model. |
| csd_service_->SetEnabledAndRefreshState(true); |
| // No calls expected. |
| Mock::VerifyAndClearExpectations(service); |
| |
| // Check that disabling the service cancels pending requests. |
| EXPECT_CALL(*service, ScheduleFetchModel(_)) |
| .WillOnce(Invoke(service, &MockClientSideDetectionService::Schedule)); |
| csd_service_->SetEnabledAndRefreshState(false); |
| csd_service_->SetEnabledAndRefreshState(true); |
| Mock::VerifyAndClearExpectations(service); |
| EXPECT_TRUE(csd_service_->model_fetcher_.get() != NULL); |
| csd_service_->SetEnabledAndRefreshState(false); |
| EXPECT_TRUE(csd_service_->model_fetcher_.get() == NULL); |
| msg_loop_.RunAllPending(); |
| // No calls expected. |
| Mock::VerifyAndClearExpectations(service); |
| |
| // Requests always return false when the service is disabled. |
| ClientPhishingResponse response; |
| response.set_phishy(true); |
| SetClientReportPhishingResponse(response.SerializeAsString(), |
| true /* success */); |
| EXPECT_FALSE(SendClientReportPhishingRequest(GURL("http://a.com/"), 0.4f)); |
| |
| // Pending requests also return false if the service is disabled before they |
| // report back. |
| EXPECT_CALL(*service, ScheduleFetchModel(_)) |
| .WillOnce(Invoke(service, &MockClientSideDetectionService::Schedule)); |
| EXPECT_CALL(*service, EndFetchModel( |
| ClientSideDetectionService::MODEL_NOT_CHANGED)) |
| .WillOnce(Invoke(service, &MockClientSideDetectionService::Disable)); |
| csd_service_->SetEnabledAndRefreshState(true); |
| EXPECT_FALSE(SendClientReportPhishingRequest(GURL("http://a.com/"), 0.4f)); |
| Mock::VerifyAndClearExpectations(service); |
| } |
| |
| TEST_F(ClientSideDetectionServiceTest, IsFalsePositiveResponse) { |
| GURL url("http://www.google.com/"); |
| ClientPhishingResponse response; |
| |
| // If the response is not phishing is should never be a false positive. |
| response.set_phishy(false); |
| response.add_whitelist_expression("www.google.com/"); |
| EXPECT_FALSE(ClientSideDetectionService::IsFalsePositiveResponse( |
| url, response)); |
| |
| // If there are no entries in the whitelist it should always return false. |
| response.clear_whitelist_expression(); |
| response.set_phishy(true); |
| EXPECT_FALSE(ClientSideDetectionService::IsFalsePositiveResponse( |
| url, response)); |
| |
| // If the URL doesn't match any whitelist entries it whould return false. |
| response.add_whitelist_expression("www.yahoo.com/"); |
| EXPECT_FALSE(ClientSideDetectionService::IsFalsePositiveResponse( |
| url, response)); |
| |
| // If the URL matches the whitelist it should return true. |
| response.add_whitelist_expression("google.com/"); |
| EXPECT_TRUE(ClientSideDetectionService::IsFalsePositiveResponse( |
| url, response)); |
| |
| // If an entry in the whitelist matches the URL it should return true. |
| response.clear_whitelist_expression(); |
| response.add_whitelist_expression("www.google.com/a/b.html"); |
| EXPECT_TRUE(ClientSideDetectionService::IsFalsePositiveResponse( |
| url, response)); |
| } |
| } // namespace safe_browsing |