| // 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 "chrome/browser/safe_browsing/client_side_detection_host.h" |
| |
| #include <memory> |
| #include <tuple> |
| #include <utility> |
| |
| #include "base/files/file_path.h" |
| #include "base/macros.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/run_loop.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/synchronization/waitable_event.h" |
| #include "chrome/browser/safe_browsing/browser_feature_extractor.h" |
| #include "chrome/browser/safe_browsing/client_side_detection_service.h" |
| #include "chrome/browser/safe_browsing/safe_browsing_service.h" |
| #include "chrome/browser/safe_browsing/ui_manager.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/test/base/chrome_render_view_host_test_harness.h" |
| #include "chrome/test/base/testing_profile.h" |
| #include "components/safe_browsing/common/safebrowsing_messages.h" |
| #include "components/safe_browsing/proto/csd.pb.h" |
| #include "components/safe_browsing_db/database_manager.h" |
| #include "components/safe_browsing_db/test_database_manager.h" |
| #include "content/public/browser/navigation_entry.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/common/browser_side_navigation_policy.h" |
| #include "content/public/test/mock_render_process_host.h" |
| #include "content/public/test/navigation_simulator.h" |
| #include "content/public/test/test_browser_thread.h" |
| #include "content/public/test/test_renderer_host.h" |
| #include "content/public/test/web_contents_tester.h" |
| #include "ipc/ipc_test_sink.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "url/gurl.h" |
| |
| using ::testing::_; |
| using ::testing::DeleteArg; |
| using ::testing::DoAll; |
| using ::testing::Eq; |
| using ::testing::IsNull; |
| using ::testing::Mock; |
| using ::testing::NiceMock; |
| using ::testing::NotNull; |
| using ::testing::Pointee; |
| using ::testing::Return; |
| using ::testing::SaveArg; |
| using ::testing::SetArgPointee; |
| using ::testing::StrictMock; |
| using content::BrowserThread; |
| using content::RenderFrameHostTester; |
| using content::WebContents; |
| |
| namespace { |
| |
| const bool kFalse = false; |
| const bool kTrue = true; |
| |
| } // namespace |
| |
| namespace safe_browsing { |
| namespace { |
| // This matcher verifies that the client computed verdict |
| // (ClientPhishingRequest) which is passed to SendClientReportPhishingRequest |
| // has the expected fields set. Note: we can't simply compare the protocol |
| // buffer strings because the BrowserFeatureExtractor might add features to the |
| // verdict object before calling SendClientReportPhishingRequest. |
| MATCHER_P(PartiallyEqualVerdict, other, "") { |
| return (other.url() == arg.url() && |
| other.client_score() == arg.client_score() && |
| other.is_phishing() == arg.is_phishing()); |
| } |
| |
| MATCHER_P(PartiallyEqualMalwareVerdict, other, "") { |
| if (other.url() != arg.url() || |
| other.referrer_url() != arg.referrer_url() || |
| other.bad_ip_url_info_size() != arg.bad_ip_url_info_size()) |
| return false; |
| |
| for (int i = 0; i < other.bad_ip_url_info_size(); ++i) { |
| if (other.bad_ip_url_info(i).ip() != arg.bad_ip_url_info(i).ip() || |
| other.bad_ip_url_info(i).url() != arg.bad_ip_url_info(i).url()) |
| return false; |
| } |
| return true; |
| } |
| |
| // Test that the callback is NULL when the verdict is not phishing. |
| MATCHER(CallbackIsNull, "") { |
| return arg.is_null(); |
| } |
| |
| ACTION(QuitUIMessageLoop) { |
| EXPECT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| base::RunLoop::QuitCurrentWhenIdleDeprecated(); |
| } |
| |
| ACTION_P(InvokeDoneCallback, verdict) { |
| std::unique_ptr<ClientPhishingRequest> request(::std::tr1::get<1>(args)); |
| request->CopyFrom(*verdict); |
| ::std::tr1::get<2>(args).Run(true, std::move(request)); |
| } |
| |
| ACTION_P(InvokeMalwareCallback, verdict) { |
| std::unique_ptr<ClientMalwareRequest> request(::std::tr1::get<1>(args)); |
| request->CopyFrom(*verdict); |
| ::std::tr1::get<2>(args).Run(true, std::move(request)); |
| } |
| |
| void EmptyUrlCheckCallback(bool processed) { |
| } |
| |
| class MockClientSideDetectionService : public ClientSideDetectionService { |
| public: |
| MockClientSideDetectionService() : ClientSideDetectionService(NULL) {} |
| virtual ~MockClientSideDetectionService() {} |
| |
| MOCK_METHOD3(SendClientReportPhishingRequest, |
| void(ClientPhishingRequest*, |
| bool, |
| const ClientReportPhishingRequestCallback&)); |
| MOCK_METHOD2(SendClientReportMalwareRequest, |
| void(ClientMalwareRequest*, |
| const ClientReportMalwareRequestCallback&)); |
| MOCK_CONST_METHOD1(IsPrivateIPAddress, bool(const std::string&)); |
| MOCK_METHOD2(GetValidCachedResult, bool(const GURL&, bool*)); |
| MOCK_METHOD1(IsInCache, bool(const GURL&)); |
| MOCK_METHOD0(OverPhishingReportLimit, bool()); |
| MOCK_METHOD0(OverMalwareReportLimit, bool()); |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(MockClientSideDetectionService); |
| }; |
| |
| class MockSafeBrowsingUIManager : public SafeBrowsingUIManager { |
| public: |
| explicit MockSafeBrowsingUIManager(SafeBrowsingService* service) |
| : SafeBrowsingUIManager(service) { } |
| |
| MOCK_METHOD1(DisplayBlockingPage, void(const UnsafeResource& resource)); |
| |
| // Helper function which calls OnBlockingPageComplete for this client |
| // object. |
| void InvokeOnBlockingPageComplete( |
| const security_interstitials::UnsafeResource::UrlCheckCallback& callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| // Note: this will delete the client object in the case of the CsdClient |
| // implementation. |
| if (!callback.is_null()) |
| callback.Run(false); |
| } |
| |
| protected: |
| virtual ~MockSafeBrowsingUIManager() { } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(MockSafeBrowsingUIManager); |
| }; |
| |
| class MockSafeBrowsingDatabaseManager : public TestSafeBrowsingDatabaseManager { |
| public: |
| MockSafeBrowsingDatabaseManager() {} |
| |
| MOCK_METHOD2(CheckCsdWhitelistUrl, |
| AsyncMatch(const GURL&, SafeBrowsingDatabaseManager::Client*)); |
| MOCK_METHOD1(MatchMalwareIP, bool(const std::string& ip_address)); |
| MOCK_METHOD0(IsMalwareKillSwitchOn, bool()); |
| |
| protected: |
| virtual ~MockSafeBrowsingDatabaseManager() {} |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(MockSafeBrowsingDatabaseManager); |
| }; |
| |
| class MockTestingProfile : public TestingProfile { |
| public: |
| MockTestingProfile() {} |
| virtual ~MockTestingProfile() {} |
| |
| MOCK_CONST_METHOD0(IsOffTheRecord, bool()); |
| }; |
| |
| class MockBrowserFeatureExtractor : public BrowserFeatureExtractor { |
| public: |
| explicit MockBrowserFeatureExtractor( |
| WebContents* tab, |
| ClientSideDetectionHost* host) |
| : BrowserFeatureExtractor(tab, host) {} |
| virtual ~MockBrowserFeatureExtractor() {} |
| |
| MOCK_METHOD3(ExtractFeatures, |
| void(const BrowseInfo*, |
| ClientPhishingRequest*, |
| const BrowserFeatureExtractor::DoneCallback&)); |
| |
| MOCK_METHOD3(ExtractMalwareFeatures, |
| void(BrowseInfo*, |
| ClientMalwareRequest*, |
| const BrowserFeatureExtractor::MalwareDoneCallback&)); |
| }; |
| |
| } // namespace |
| |
| class ClientSideDetectionHostTest : public ChromeRenderViewHostTestHarness { |
| public: |
| typedef security_interstitials::UnsafeResource UnsafeResource; |
| |
| void SetUp() override { |
| ChromeRenderViewHostTestHarness::SetUp(); |
| |
| // Inject service classes. |
| csd_service_.reset(new StrictMock<MockClientSideDetectionService>()); |
| database_manager_ = new StrictMock<MockSafeBrowsingDatabaseManager>(); |
| ui_manager_ = new StrictMock<MockSafeBrowsingUIManager>( |
| SafeBrowsingService::CreateSafeBrowsingService()); |
| csd_host_.reset(ClientSideDetectionHost::Create(web_contents())); |
| csd_host_->set_client_side_detection_service(csd_service_.get()); |
| csd_host_->set_safe_browsing_managers(ui_manager_.get(), |
| database_manager_.get()); |
| // We need to create this here since we don't call DidStopLanding in |
| // this test. |
| csd_host_->browse_info_.reset(new BrowseInfo); |
| } |
| |
| void TearDown() override { |
| // Delete the host object on the UI thread and release the |
| // SafeBrowsingService. |
| BrowserThread::DeleteSoon(BrowserThread::UI, FROM_HERE, |
| csd_host_.release()); |
| database_manager_ = NULL; |
| ui_manager_ = NULL; |
| base::RunLoop().RunUntilIdle(); |
| ChromeRenderViewHostTestHarness::TearDown(); |
| } |
| |
| content::BrowserContext* CreateBrowserContext() override { |
| // Set custom profile object so that we can mock calls to IsOffTheRecord. |
| // This needs to happen before we call the parent SetUp() function. We use |
| // a nice mock because other parts of the code are calling IsOffTheRecord. |
| mock_profile_ = new NiceMock<MockTestingProfile>(); |
| return mock_profile_; |
| } |
| |
| void OnPhishingDetectionDone(const std::string& verdict_str) { |
| csd_host_->OnPhishingDetectionDone(verdict_str); |
| } |
| |
| void DidStopLoading() { csd_host_->DidStopLoading(); } |
| |
| void UpdateIPUrlMap(const std::string& ip, const std::string& host) { |
| csd_host_->UpdateIPUrlMap(ip, host, "", "", content::RESOURCE_TYPE_OBJECT); |
| } |
| |
| BrowseInfo* GetBrowseInfo() { |
| return csd_host_->browse_info_.get(); |
| } |
| |
| void ExpectPreClassificationChecks(const GURL& url, |
| const bool* is_private, |
| const bool* is_incognito, |
| const bool* match_csd_whitelist, |
| const bool* malware_killswitch, |
| const bool* get_valid_cached_result, |
| const bool* is_in_cache, |
| const bool* over_phishing_report_limit, |
| const bool* over_malware_report_limit) { |
| if (is_private) { |
| EXPECT_CALL(*csd_service_, IsPrivateIPAddress(_)) |
| .WillOnce(Return(*is_private)); |
| } |
| if (is_incognito) { |
| EXPECT_CALL(*mock_profile_, IsOffTheRecord()) |
| .WillRepeatedly(Return(*is_incognito)); |
| } |
| if (match_csd_whitelist) { |
| EXPECT_CALL(*database_manager_.get(), CheckCsdWhitelistUrl(url, _)) |
| .WillOnce(Return(*match_csd_whitelist ? AsyncMatch::MATCH |
| : AsyncMatch::NO_MATCH)); |
| } |
| if (malware_killswitch) { |
| EXPECT_CALL(*database_manager_.get(), IsMalwareKillSwitchOn()) |
| .WillRepeatedly(Return(*malware_killswitch)); |
| } |
| if (get_valid_cached_result) { |
| EXPECT_CALL(*csd_service_, GetValidCachedResult(url, NotNull())) |
| .WillOnce( |
| DoAll(SetArgPointee<1>(true), Return(*get_valid_cached_result))); |
| } |
| if (is_in_cache) { |
| EXPECT_CALL(*csd_service_, IsInCache(url)).WillOnce(Return(*is_in_cache)); |
| } |
| if (over_phishing_report_limit) { |
| EXPECT_CALL(*csd_service_, OverPhishingReportLimit()) |
| .WillOnce(Return(*over_phishing_report_limit)); |
| } |
| if (over_malware_report_limit) { |
| EXPECT_CALL(*csd_service_, OverMalwareReportLimit()) |
| .WillOnce(Return(*over_malware_report_limit)); |
| } |
| } |
| |
| void WaitAndCheckPreClassificationChecks() { |
| // Wait for CheckCsdWhitelist and CheckCache() to be called if at all. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(Mock::VerifyAndClear(csd_service_.get())); |
| EXPECT_TRUE(Mock::VerifyAndClear(ui_manager_.get())); |
| EXPECT_TRUE(Mock::VerifyAndClear(database_manager_.get())); |
| EXPECT_TRUE(Mock::VerifyAndClear(mock_profile_)); |
| } |
| |
| void SetFeatureExtractor(BrowserFeatureExtractor* extractor) { |
| csd_host_->feature_extractor_.reset(extractor); |
| } |
| |
| void SetRedirectChain(const std::vector<GURL>& redirect_chain) { |
| csd_host_->browse_info_->url_redirects = redirect_chain; |
| } |
| |
| void SetReferrer(const GURL& referrer) { |
| csd_host_->browse_info_->referrer = referrer; |
| } |
| |
| void ExpectShouldClassifyForMalwareResult(bool should_classify) { |
| EXPECT_EQ(should_classify, csd_host_->should_classify_for_malware_); |
| } |
| |
| void ExpectStartPhishingDetection(const GURL* url) { |
| const IPC::Message* msg = process()->sink().GetFirstMessageMatching( |
| SafeBrowsingMsg_StartPhishingDetection::ID); |
| if (url) { |
| ASSERT_TRUE(msg); |
| std::tuple<GURL> actual_url; |
| SafeBrowsingMsg_StartPhishingDetection::Read(msg, &actual_url); |
| EXPECT_EQ(*url, std::get<0>(actual_url)); |
| EXPECT_EQ(main_rfh()->GetRoutingID(), msg->routing_id()); |
| process()->sink().ClearMessages(); |
| } else { |
| ASSERT_FALSE(msg); |
| } |
| } |
| |
| void TestUnsafeResourceCopied(const UnsafeResource& resource) { |
| ASSERT_TRUE(csd_host_->unsafe_resource_.get()); |
| // Test that the resource from OnSafeBrowsingHit notification was copied |
| // into the CSDH. |
| EXPECT_EQ(resource.url, csd_host_->unsafe_resource_->url); |
| EXPECT_EQ(resource.original_url, csd_host_->unsafe_resource_->original_url); |
| EXPECT_EQ(resource.is_subresource, |
| csd_host_->unsafe_resource_->is_subresource); |
| EXPECT_EQ(resource.threat_type, csd_host_->unsafe_resource_->threat_type); |
| EXPECT_TRUE(csd_host_->unsafe_resource_->callback.is_null()); |
| EXPECT_EQ(resource.web_contents_getter.Run(), |
| csd_host_->unsafe_resource_->web_contents_getter.Run()); |
| } |
| |
| void SetUnsafeSubResourceForCurrent(bool expect_unsafe_resource) { |
| UnsafeResource resource; |
| resource.url = GURL("http://www.malware.com/"); |
| resource.original_url = web_contents()->GetURL(); |
| resource.is_subresource = true; |
| resource.threat_type = SB_THREAT_TYPE_URL_MALWARE; |
| resource.callback = base::Bind(&EmptyUrlCheckCallback); |
| resource.callback_thread = |
| BrowserThread::GetTaskRunnerForThread(BrowserThread::IO); |
| resource.web_contents_getter = |
| SafeBrowsingUIManager::UnsafeResource::GetWebContentsGetter( |
| web_contents()->GetRenderProcessHost()->GetID(), |
| web_contents()->GetMainFrame()->GetRoutingID()); |
| csd_host_->OnSafeBrowsingHit(resource); |
| resource.callback.Reset(); |
| ASSERT_EQ(expect_unsafe_resource, csd_host_->DidShowSBInterstitial()); |
| if (expect_unsafe_resource) |
| TestUnsafeResourceCopied(resource); |
| } |
| |
| void NavigateWithSBHitAndCommit(const GURL& url) { |
| // Create a pending navigation. |
| controller().LoadURL( |
| url, content::Referrer(), ui::PAGE_TRANSITION_LINK, std::string()); |
| |
| ASSERT_TRUE(pending_main_rfh()); |
| if (web_contents()->GetMainFrame()->GetProcess()->GetID() == |
| pending_main_rfh()->GetProcess()->GetID()) { |
| EXPECT_NE(web_contents()->GetMainFrame()->GetRoutingID(), |
| pending_main_rfh()->GetRoutingID()); |
| } |
| |
| // Simulate a safebrowsing hit before navigation completes. |
| UnsafeResource resource; |
| resource.url = url; |
| resource.original_url = url; |
| resource.is_subresource = false; |
| resource.threat_type = SB_THREAT_TYPE_URL_MALWARE; |
| resource.callback = base::Bind(&EmptyUrlCheckCallback); |
| resource.callback_thread = |
| BrowserThread::GetTaskRunnerForThread(BrowserThread::IO); |
| resource.web_contents_getter = |
| SafeBrowsingUIManager::UnsafeResource::GetWebContentsGetter( |
| pending_rvh()->GetProcess()->GetID(), |
| pending_main_rfh()->GetRoutingID()); |
| csd_host_->OnSafeBrowsingHit(resource); |
| resource.callback.Reset(); |
| |
| // LoadURL created a navigation entry, now simulate the RenderView sending |
| // a notification that it actually navigated. |
| content::WebContentsTester::For(web_contents())->CommitPendingNavigation(); |
| |
| ASSERT_TRUE(csd_host_->DidShowSBInterstitial()); |
| TestUnsafeResourceCopied(resource); |
| } |
| |
| void NavigateWithoutSBHitAndCommit(const GURL& safe_url) { |
| controller().LoadURL( |
| safe_url, content::Referrer(), ui::PAGE_TRANSITION_LINK, |
| std::string()); |
| |
| ASSERT_TRUE(pending_main_rfh()); |
| if (web_contents()->GetMainFrame()->GetProcess()->GetID() == |
| pending_main_rfh()->GetProcess()->GetID()) { |
| EXPECT_NE(web_contents()->GetMainFrame()->GetRoutingID(), |
| pending_main_rfh()->GetRoutingID()); |
| } |
| |
| content::WebContentsTester::For(web_contents())->CommitPendingNavigation(); |
| ASSERT_FALSE(csd_host_->DidShowSBInterstitial()); |
| } |
| |
| void CheckIPUrlEqual(const std::vector<IPUrlInfo>& expect, |
| const std::vector<IPUrlInfo>& result) { |
| ASSERT_EQ(expect.size(), result.size()); |
| |
| for (unsigned int i = 0; i < expect.size(); ++i) { |
| EXPECT_EQ(expect[i].url, result[i].url); |
| EXPECT_EQ(expect[i].method, result[i].method); |
| EXPECT_EQ(expect[i].referrer, result[i].referrer); |
| EXPECT_EQ(expect[i].resource_type, result[i].resource_type); |
| } |
| } |
| |
| protected: |
| std::unique_ptr<ClientSideDetectionHost> csd_host_; |
| std::unique_ptr<StrictMock<MockClientSideDetectionService>> csd_service_; |
| scoped_refptr<StrictMock<MockSafeBrowsingUIManager> > ui_manager_; |
| scoped_refptr<StrictMock<MockSafeBrowsingDatabaseManager> > database_manager_; |
| MockTestingProfile* mock_profile_; // We don't own this object |
| }; |
| |
| TEST_F(ClientSideDetectionHostTest, OnPhishingDetectionDoneInvalidVerdict) { |
| // Case 0: renderer sends an invalid verdict string that we're unable to |
| // parse. |
| MockBrowserFeatureExtractor* mock_extractor = |
| new StrictMock<MockBrowserFeatureExtractor>( |
| web_contents(), |
| csd_host_.get()); |
| SetFeatureExtractor(mock_extractor); // The host class takes ownership. |
| EXPECT_CALL(*mock_extractor, ExtractFeatures(_, _, _)).Times(0); |
| OnPhishingDetectionDone("Invalid Protocol Buffer"); |
| EXPECT_TRUE(Mock::VerifyAndClear(mock_extractor)); |
| } |
| |
| TEST_F(ClientSideDetectionHostTest, OnPhishingDetectionDoneNotPhishing) { |
| // Case 1: client thinks the page is phishing. The server does not agree. |
| // No interstitial is shown. |
| MockBrowserFeatureExtractor* mock_extractor = |
| new StrictMock<MockBrowserFeatureExtractor>( |
| web_contents(), |
| csd_host_.get()); |
| SetFeatureExtractor(mock_extractor); // The host class takes ownership. |
| |
| ClientSideDetectionService::ClientReportPhishingRequestCallback cb; |
| ClientPhishingRequest verdict; |
| verdict.set_url("http://phishingurl.com/"); |
| verdict.set_client_score(1.0f); |
| verdict.set_is_phishing(true); |
| |
| EXPECT_CALL(*mock_extractor, ExtractFeatures(_, _, _)) |
| .WillOnce(InvokeDoneCallback(&verdict)); |
| EXPECT_CALL(*csd_service_, |
| SendClientReportPhishingRequest( |
| Pointee(PartiallyEqualVerdict(verdict)), _, _)) |
| .WillOnce(DoAll(DeleteArg<0>(), SaveArg<2>(&cb))); |
| OnPhishingDetectionDone(verdict.SerializeAsString()); |
| EXPECT_TRUE(Mock::VerifyAndClear(csd_host_.get())); |
| ASSERT_FALSE(cb.is_null()); |
| |
| // Make sure DisplayBlockingPage is not going to be called. |
| EXPECT_CALL(*ui_manager_.get(), DisplayBlockingPage(_)).Times(0); |
| cb.Run(GURL(verdict.url()), false); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(Mock::VerifyAndClear(ui_manager_.get())); |
| } |
| |
| TEST_F(ClientSideDetectionHostTest, OnPhishingDetectionDoneDisabled) { |
| // Case 2: client thinks the page is phishing and so does the server but |
| // showing the interstitial is disabled => no interstitial is shown. |
| MockBrowserFeatureExtractor* mock_extractor = |
| new StrictMock<MockBrowserFeatureExtractor>( |
| web_contents(), |
| csd_host_.get()); |
| SetFeatureExtractor(mock_extractor); // The host class takes ownership. |
| |
| ClientSideDetectionService::ClientReportPhishingRequestCallback cb; |
| ClientPhishingRequest verdict; |
| verdict.set_url("http://phishingurl.com/"); |
| verdict.set_client_score(1.0f); |
| verdict.set_is_phishing(true); |
| |
| EXPECT_CALL(*mock_extractor, ExtractFeatures(_, _, _)) |
| .WillOnce(InvokeDoneCallback(&verdict)); |
| EXPECT_CALL(*csd_service_, |
| SendClientReportPhishingRequest( |
| Pointee(PartiallyEqualVerdict(verdict)), _, _)) |
| .WillOnce(DoAll(DeleteArg<0>(), SaveArg<2>(&cb))); |
| OnPhishingDetectionDone(verdict.SerializeAsString()); |
| EXPECT_TRUE(Mock::VerifyAndClear(csd_host_.get())); |
| ASSERT_FALSE(cb.is_null()); |
| |
| // Make sure DisplayBlockingPage is not going to be called. |
| EXPECT_CALL(*ui_manager_.get(), DisplayBlockingPage(_)).Times(0); |
| cb.Run(GURL(verdict.url()), false); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(Mock::VerifyAndClear(ui_manager_.get())); |
| } |
| |
| TEST_F(ClientSideDetectionHostTest, OnPhishingDetectionDoneShowInterstitial) { |
| // Case 3: client thinks the page is phishing and so does the server. |
| // We show an interstitial. |
| MockBrowserFeatureExtractor* mock_extractor = |
| new StrictMock<MockBrowserFeatureExtractor>( |
| web_contents(), |
| csd_host_.get()); |
| SetFeatureExtractor(mock_extractor); // The host class takes ownership. |
| |
| ClientSideDetectionService::ClientReportPhishingRequestCallback cb; |
| GURL phishing_url("http://phishingurl.com/"); |
| ClientPhishingRequest verdict; |
| verdict.set_url(phishing_url.spec()); |
| verdict.set_client_score(1.0f); |
| verdict.set_is_phishing(true); |
| |
| EXPECT_CALL(*mock_extractor, ExtractFeatures(_, _, _)) |
| .WillOnce(InvokeDoneCallback(&verdict)); |
| EXPECT_CALL(*csd_service_, |
| SendClientReportPhishingRequest( |
| Pointee(PartiallyEqualVerdict(verdict)), _, _)) |
| .WillOnce(DoAll(DeleteArg<0>(), SaveArg<2>(&cb))); |
| OnPhishingDetectionDone(verdict.SerializeAsString()); |
| EXPECT_TRUE(Mock::VerifyAndClear(csd_host_.get())); |
| EXPECT_TRUE(Mock::VerifyAndClear(csd_service_.get())); |
| ASSERT_FALSE(cb.is_null()); |
| |
| UnsafeResource resource; |
| EXPECT_CALL(*ui_manager_.get(), DisplayBlockingPage(_)) |
| .WillOnce(SaveArg<0>(&resource)); |
| cb.Run(phishing_url, true); |
| |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(Mock::VerifyAndClear(ui_manager_.get())); |
| EXPECT_EQ(phishing_url, resource.url); |
| EXPECT_EQ(phishing_url, resource.original_url); |
| EXPECT_FALSE(resource.is_subresource); |
| EXPECT_EQ(SB_THREAT_TYPE_URL_CLIENT_SIDE_PHISHING, resource.threat_type); |
| EXPECT_EQ(ThreatSource::CLIENT_SIDE_DETECTION, resource.threat_source); |
| EXPECT_EQ(web_contents(), resource.web_contents_getter.Run()); |
| |
| // Make sure the client object will be deleted. |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::BindOnce(&MockSafeBrowsingUIManager::InvokeOnBlockingPageComplete, |
| ui_manager_, resource.callback)); |
| } |
| |
| TEST_F(ClientSideDetectionHostTest, OnPhishingDetectionDoneMultiplePings) { |
| // Case 4 & 5: client thinks a page is phishing then navigates to |
| // another page which is also considered phishing by the client |
| // before the server responds with a verdict. After a while the |
| // server responds for both requests with a phishing verdict. Only |
| // a single interstitial is shown for the second URL. |
| MockBrowserFeatureExtractor* mock_extractor = |
| new StrictMock<MockBrowserFeatureExtractor>( |
| web_contents(), |
| csd_host_.get()); |
| SetFeatureExtractor(mock_extractor); // The host class takes ownership. |
| |
| ClientSideDetectionService::ClientReportPhishingRequestCallback cb; |
| GURL phishing_url("http://phishingurl.com/"); |
| ClientPhishingRequest verdict; |
| verdict.set_url(phishing_url.spec()); |
| verdict.set_client_score(1.0f); |
| verdict.set_is_phishing(true); |
| |
| EXPECT_CALL(*mock_extractor, ExtractFeatures(_, _, _)) |
| .WillOnce(InvokeDoneCallback(&verdict)); |
| EXPECT_CALL(*csd_service_, |
| SendClientReportPhishingRequest( |
| Pointee(PartiallyEqualVerdict(verdict)), _, _)) |
| .WillOnce(DoAll(DeleteArg<0>(), SaveArg<2>(&cb))); |
| OnPhishingDetectionDone(verdict.SerializeAsString()); |
| EXPECT_TRUE(Mock::VerifyAndClear(csd_host_.get())); |
| EXPECT_TRUE(Mock::VerifyAndClear(csd_service_.get())); |
| ASSERT_FALSE(cb.is_null()); |
| |
| // Set this back to a normal browser feature extractor since we're using |
| // NavigateAndCommit() and it's easier to use the real thing than setting up |
| // mock expectations. |
| SetFeatureExtractor(new BrowserFeatureExtractor(web_contents(), |
| csd_host_.get())); |
| GURL other_phishing_url("http://other_phishing_url.com/bla"); |
| ExpectPreClassificationChecks(other_phishing_url, &kFalse, &kFalse, &kFalse, |
| &kFalse, &kFalse, &kFalse, &kFalse, &kFalse); |
| // We navigate away. The callback cb should be revoked. |
| NavigateAndCommit(other_phishing_url); |
| // Wait for the pre-classification checks to finish for other_phishing_url. |
| WaitAndCheckPreClassificationChecks(); |
| |
| ClientSideDetectionService::ClientReportPhishingRequestCallback cb_other; |
| verdict.set_url(other_phishing_url.spec()); |
| verdict.set_client_score(0.8f); |
| EXPECT_CALL(*csd_service_, |
| SendClientReportPhishingRequest( |
| Pointee(PartiallyEqualVerdict(verdict)), _, _)) |
| .WillOnce(DoAll(DeleteArg<0>(), |
| SaveArg<2>(&cb_other), |
| QuitUIMessageLoop())); |
| std::vector<GURL> redirect_chain; |
| redirect_chain.push_back(other_phishing_url); |
| SetRedirectChain(redirect_chain); |
| OnPhishingDetectionDone(verdict.SerializeAsString()); |
| base::RunLoop().Run(); |
| EXPECT_TRUE(Mock::VerifyAndClear(csd_host_.get())); |
| EXPECT_TRUE(Mock::VerifyAndClear(csd_service_.get())); |
| ASSERT_FALSE(cb_other.is_null()); |
| |
| // We expect that the interstitial is shown for the second phishing URL and |
| // not for the first phishing URL. |
| UnsafeResource resource; |
| EXPECT_CALL(*ui_manager_.get(), DisplayBlockingPage(_)) |
| .WillOnce(SaveArg<0>(&resource)); |
| |
| cb.Run(phishing_url, true); // Should have no effect. |
| cb_other.Run(other_phishing_url, true); // Should show interstitial. |
| |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(Mock::VerifyAndClear(ui_manager_.get())); |
| EXPECT_EQ(other_phishing_url, resource.url); |
| EXPECT_EQ(other_phishing_url, resource.original_url); |
| EXPECT_FALSE(resource.is_subresource); |
| EXPECT_EQ(SB_THREAT_TYPE_URL_CLIENT_SIDE_PHISHING, resource.threat_type); |
| EXPECT_EQ(ThreatSource::CLIENT_SIDE_DETECTION, resource.threat_source); |
| EXPECT_EQ(web_contents(), resource.web_contents_getter.Run()); |
| |
| // Make sure the client object will be deleted. |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::BindOnce(&MockSafeBrowsingUIManager::InvokeOnBlockingPageComplete, |
| ui_manager_, resource.callback)); |
| } |
| |
| TEST_F(ClientSideDetectionHostTest, |
| OnPhishingDetectionDoneVerdictNotPhishing) { |
| // Case 6: renderer sends a verdict string that isn't phishing. |
| MockBrowserFeatureExtractor* mock_extractor = |
| new StrictMock<MockBrowserFeatureExtractor>( |
| web_contents(), |
| csd_host_.get()); |
| SetFeatureExtractor(mock_extractor); // The host class takes ownership. |
| |
| ClientPhishingRequest verdict; |
| verdict.set_url("http://not-phishing.com/"); |
| verdict.set_client_score(0.1f); |
| verdict.set_is_phishing(false); |
| |
| EXPECT_CALL(*mock_extractor, ExtractFeatures(_, _, _)).Times(0); |
| OnPhishingDetectionDone(verdict.SerializeAsString()); |
| EXPECT_TRUE(Mock::VerifyAndClear(mock_extractor)); |
| } |
| |
| TEST_F(ClientSideDetectionHostTest, |
| OnPhishingDetectionDoneVerdictNotPhishingButSBMatchSubResource) { |
| // Case 7: renderer sends a verdict string that isn't phishing but the URL |
| // of a subresource was on the regular phishing or malware lists. |
| GURL url("http://not-phishing.com/"); |
| ClientPhishingRequest verdict; |
| verdict.set_url(url.spec()); |
| verdict.set_client_score(0.1f); |
| verdict.set_is_phishing(false); |
| |
| // First we have to navigate to the URL to set the unique page ID. |
| ExpectPreClassificationChecks(url, &kFalse, &kFalse, &kFalse, &kFalse, |
| &kFalse, &kFalse, &kFalse, &kFalse); |
| NavigateAndCommit(url); |
| WaitAndCheckPreClassificationChecks(); |
| SetUnsafeSubResourceForCurrent(true /* expect_unsafe_resource */); |
| |
| EXPECT_CALL(*csd_service_, |
| SendClientReportPhishingRequest( |
| Pointee(PartiallyEqualVerdict(verdict)), _, CallbackIsNull())) |
| .WillOnce(DoAll(DeleteArg<0>(), QuitUIMessageLoop())); |
| std::vector<GURL> redirect_chain; |
| redirect_chain.push_back(url); |
| SetRedirectChain(redirect_chain); |
| OnPhishingDetectionDone(verdict.SerializeAsString()); |
| base::RunLoop().Run(); |
| EXPECT_TRUE(Mock::VerifyAndClear(csd_host_.get())); |
| } |
| |
| TEST_F(ClientSideDetectionHostTest, |
| OnPhishingDetectionDoneVerdictNotPhishingButSBMatchOnNewRVH) { |
| // When navigating to a different host (thus creating a pending RVH) which |
| // matches regular malware list, and after navigation the renderer sends a |
| // verdict string that isn't phishing, we should still send the report. |
| |
| // Do an initial navigation to a safe host. |
| GURL start_url("http://safe.example.com/"); |
| ExpectPreClassificationChecks( |
| start_url, &kFalse, &kFalse, &kFalse, &kFalse, &kFalse, &kFalse, &kFalse, |
| &kFalse); |
| NavigateAndCommit(start_url); |
| WaitAndCheckPreClassificationChecks(); |
| |
| // Now navigate to a different host which will have a malware hit before the |
| // navigation commits. |
| GURL url("http://malware-but-not-phishing.com/"); |
| ClientPhishingRequest verdict; |
| verdict.set_url(url.spec()); |
| verdict.set_client_score(0.1f); |
| verdict.set_is_phishing(false); |
| |
| ExpectPreClassificationChecks(url, &kFalse, &kFalse, &kFalse, &kFalse, |
| &kFalse, &kFalse, &kFalse, &kFalse); |
| NavigateWithSBHitAndCommit(url); |
| WaitAndCheckPreClassificationChecks(); |
| |
| EXPECT_CALL(*csd_service_, |
| SendClientReportPhishingRequest( |
| Pointee(PartiallyEqualVerdict(verdict)), _, CallbackIsNull())) |
| .WillOnce(DoAll(DeleteArg<0>(), QuitUIMessageLoop())); |
| std::vector<GURL> redirect_chain; |
| redirect_chain.push_back(url); |
| SetRedirectChain(redirect_chain); |
| OnPhishingDetectionDone(verdict.SerializeAsString()); |
| base::RunLoop().Run(); |
| EXPECT_TRUE(Mock::VerifyAndClear(csd_host_.get())); |
| |
| ExpectPreClassificationChecks(start_url, &kFalse, &kFalse, &kFalse, &kFalse, |
| &kFalse, &kFalse, &kFalse, &kFalse); |
| NavigateWithoutSBHitAndCommit(start_url); |
| WaitAndCheckPreClassificationChecks(); |
| } |
| |
| TEST_F( |
| ClientSideDetectionHostTest, |
| OnPhishingDetectionDoneVerdictNotPhishingButSBMatchOnSubresourceWhileNavPending) { |
| // When a malware hit happens on a committed page while a slow pending load is |
| // in progress, the csd report should be sent for the committed page. |
| |
| // Do an initial navigation to a safe host. |
| GURL start_url("http://safe.example.com/"); |
| ExpectPreClassificationChecks( |
| start_url, &kFalse, &kFalse, &kFalse, &kFalse, &kFalse, &kFalse, &kFalse, |
| &kFalse); |
| NavigateAndCommit(start_url); |
| WaitAndCheckPreClassificationChecks(); |
| |
| // Now navigate to a different host which does not have a SB hit. |
| GURL url("http://not-malware-not-phishing-but-malware-subresource.com/"); |
| ClientPhishingRequest verdict; |
| verdict.set_url(url.spec()); |
| verdict.set_client_score(0.1f); |
| verdict.set_is_phishing(false); |
| |
| ExpectPreClassificationChecks(url, &kFalse, &kFalse, &kFalse, &kFalse, |
| &kFalse, &kFalse, &kFalse, &kFalse); |
| NavigateWithoutSBHitAndCommit(url); |
| |
| // Simulate a subresource malware hit on committed page. |
| SetUnsafeSubResourceForCurrent(true /* expect_unsafe_resource */); |
| |
| // Create a pending navigation, but don't commit it. |
| GURL pending_url("http://slow.example.com/"); |
| auto pending_navigation = |
| content::NavigationSimulator::CreateBrowserInitiated(pending_url, |
| web_contents()); |
| pending_navigation->Start(); |
| |
| WaitAndCheckPreClassificationChecks(); |
| |
| // Even though we have a pending navigation, the DidShowSBInterstitial check |
| // should apply to the committed navigation, so we should get a report even |
| // though the verdict has is_phishing = false. |
| EXPECT_CALL(*csd_service_, |
| SendClientReportPhishingRequest( |
| Pointee(PartiallyEqualVerdict(verdict)), _, CallbackIsNull())) |
| .WillOnce(DoAll(DeleteArg<0>(), QuitUIMessageLoop())); |
| std::vector<GURL> redirect_chain; |
| redirect_chain.push_back(url); |
| SetRedirectChain(redirect_chain); |
| OnPhishingDetectionDone(verdict.SerializeAsString()); |
| base::RunLoop().Run(); |
| EXPECT_TRUE(Mock::VerifyAndClear(csd_host_.get())); |
| } |
| |
| TEST_F(ClientSideDetectionHostTest, SafeBrowsingHitOnFreshTab) { |
| // A fresh WebContents should not have any NavigationEntries yet. (See |
| // https://crbug.com/524208.) |
| EXPECT_EQ(nullptr, controller().GetLastCommittedEntry()); |
| EXPECT_EQ(nullptr, controller().GetPendingEntry()); |
| |
| // Simulate a subresource malware hit (this could happen if the WebContents |
| // was created with window.open, and had content injected into it). |
| SetUnsafeSubResourceForCurrent(false /* expect_unsafe_resource */); |
| |
| // If the test didn't crash, we're good. (Nothing else to test, since there |
| // was no DidNavigateMainFrame, CSD won't do anything with this hit.) |
| } |
| |
| TEST_F(ClientSideDetectionHostTest, |
| DidStopLoadingShowMalwareInterstitial) { |
| // Case 9: client thinks the page match malware IP and so does the server. |
| // We show an sub-resource malware interstitial. |
| MockBrowserFeatureExtractor* mock_extractor = |
| new StrictMock<MockBrowserFeatureExtractor>( |
| web_contents(), |
| csd_host_.get()); |
| SetFeatureExtractor(mock_extractor); // The host class takes ownership. |
| |
| GURL malware_landing_url("http://malware.com/"); |
| GURL malware_ip_url("http://badip.com"); |
| ClientMalwareRequest malware_verdict; |
| malware_verdict.set_url("http://malware.com/"); |
| ClientMalwareRequest::UrlInfo* badipurl = |
| malware_verdict.add_bad_ip_url_info(); |
| badipurl->set_ip("1.2.3.4"); |
| badipurl->set_url("http://badip.com"); |
| |
| ExpectPreClassificationChecks(GURL(malware_verdict.url()), &kFalse, &kFalse, |
| &kFalse, &kFalse, &kFalse, &kFalse, &kFalse, |
| &kFalse); |
| NavigateAndCommit(GURL(malware_verdict.url())); |
| WaitAndCheckPreClassificationChecks(); |
| |
| ClientSideDetectionService::ClientReportMalwareRequestCallback cb; |
| EXPECT_CALL(*mock_extractor, ExtractMalwareFeatures(_, _, _)) |
| .WillOnce(InvokeMalwareCallback(&malware_verdict)); |
| EXPECT_CALL(*csd_service_, |
| SendClientReportMalwareRequest( |
| Pointee(PartiallyEqualMalwareVerdict(malware_verdict)), _)) |
| .WillOnce(DoAll(DeleteArg<0>(), SaveArg<1>(&cb))); |
| DidStopLoading(); |
| EXPECT_TRUE(Mock::VerifyAndClear(csd_host_.get())); |
| EXPECT_TRUE(Mock::VerifyAndClear(csd_service_.get())); |
| ASSERT_FALSE(cb.is_null()); |
| |
| UnsafeResource resource; |
| EXPECT_CALL(*ui_manager_.get(), DisplayBlockingPage(_)) |
| .WillOnce(SaveArg<0>(&resource)); |
| cb.Run(malware_landing_url, malware_ip_url, true); |
| |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_TRUE(Mock::VerifyAndClear(ui_manager_.get())); |
| EXPECT_EQ(malware_ip_url, resource.url); |
| EXPECT_EQ(malware_landing_url, resource.original_url); |
| EXPECT_TRUE(resource.is_subresource); |
| EXPECT_EQ(SB_THREAT_TYPE_URL_CLIENT_SIDE_MALWARE, resource.threat_type); |
| EXPECT_EQ(ThreatSource::CLIENT_SIDE_DETECTION, resource.threat_source); |
| EXPECT_EQ(web_contents(), resource.web_contents_getter.Run()); |
| |
| // Make sure the client object will be deleted. |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::BindOnce(&MockSafeBrowsingUIManager::InvokeOnBlockingPageComplete, |
| ui_manager_, resource.callback)); |
| } |
| |
| TEST_F(ClientSideDetectionHostTest, UpdateIPUrlMap) { |
| BrowseInfo* browse_info = GetBrowseInfo(); |
| |
| // Empty IP or host are skipped |
| UpdateIPUrlMap("250.10.10.10", std::string()); |
| ASSERT_EQ(0U, browse_info->ips.size()); |
| UpdateIPUrlMap(std::string(), "http://google.com/a"); |
| ASSERT_EQ(0U, browse_info->ips.size()); |
| UpdateIPUrlMap(std::string(), std::string()); |
| ASSERT_EQ(0U, browse_info->ips.size()); |
| |
| std::vector<IPUrlInfo> expected_urls; |
| for (int i = 0; i < 20; i++) { |
| std::string url = base::StringPrintf("http://%d.com/", i); |
| expected_urls.push_back( |
| IPUrlInfo(url, "", "", content::RESOURCE_TYPE_OBJECT)); |
| UpdateIPUrlMap("250.10.10.10", url); |
| } |
| ASSERT_EQ(1U, browse_info->ips.size()); |
| ASSERT_EQ(20U, browse_info->ips["250.10.10.10"].size()); |
| CheckIPUrlEqual(expected_urls, |
| browse_info->ips["250.10.10.10"]); |
| |
| // Add more urls for this ip, it exceeds max limit and won't be added |
| UpdateIPUrlMap("250.10.10.10", "http://21.com/"); |
| ASSERT_EQ(1U, browse_info->ips.size()); |
| ASSERT_EQ(20U, browse_info->ips["250.10.10.10"].size()); |
| CheckIPUrlEqual(expected_urls, |
| browse_info->ips["250.10.10.10"]); |
| |
| // Add 199 more IPs |
| for (int i = 0; i < 199; i++) { |
| std::string ip = base::StringPrintf("%d.%d.%d.256", i, i, i); |
| expected_urls.clear(); |
| expected_urls.push_back( |
| IPUrlInfo("test.com/", "", "", content::RESOURCE_TYPE_OBJECT)); |
| UpdateIPUrlMap(ip, "test.com/"); |
| ASSERT_EQ(1U, browse_info->ips[ip].size()); |
| CheckIPUrlEqual(expected_urls, |
| browse_info->ips[ip]); |
| } |
| ASSERT_EQ(200U, browse_info->ips.size()); |
| |
| // Exceeding max ip limit 200, these won't be added |
| UpdateIPUrlMap("250.250.250.250", "goo.com/"); |
| UpdateIPUrlMap("250.250.250.250", "bar.com/"); |
| UpdateIPUrlMap("250.250.0.250", "foo.com/"); |
| ASSERT_EQ(200U, browse_info->ips.size()); |
| |
| // Add url to existing IPs succeed |
| UpdateIPUrlMap("100.100.100.256", "more.com/"); |
| ASSERT_EQ(2U, browse_info->ips["100.100.100.256"].size()); |
| expected_urls.clear(); |
| expected_urls.push_back( |
| IPUrlInfo("test.com/", "", "", content::RESOURCE_TYPE_OBJECT)); |
| expected_urls.push_back( |
| IPUrlInfo("more.com/", "", "", content::RESOURCE_TYPE_OBJECT)); |
| CheckIPUrlEqual(expected_urls, |
| browse_info->ips["100.100.100.256"]); |
| } |
| |
| TEST_F(ClientSideDetectionHostTest, NavigationCancelsShouldClassifyUrl) { |
| if (content::IsBrowserSideNavigationEnabled()) { |
| // PlzNavigate: this test doesn't work because it makes assumption about how |
| // the message loop is run, and those assumptions are wrong when properly |
| // simulating a navigation with browser-side navigations. |
| // TODO(clamy): Fix the test and re-enable for PlzNavigate. See |
| // crbug.com/753357. |
| return; |
| } |
| // Test that canceling pending should classify requests works as expected. |
| |
| GURL first_url("http://first.phishy.url.com"); |
| GURL second_url("http://second.url.com/"); |
| // The first few checks are done synchronously so check that they have been |
| // done for the first URL, while the second URL has all the checks done. We |
| // need to manually set up the IsPrivateIPAddress mock since if the same mock |
| // expectation is specified twice, gmock will only use the last instance of |
| // it, meaning the first will never be matched. |
| EXPECT_CALL(*csd_service_, IsPrivateIPAddress(_)) |
| .WillOnce(Return(false)) |
| .WillOnce(Return(false)); |
| ExpectPreClassificationChecks(first_url, NULL, &kFalse, &kFalse, &kFalse, |
| NULL, NULL, NULL, NULL); |
| ExpectPreClassificationChecks(second_url, NULL, &kFalse, &kFalse, &kFalse, |
| &kFalse, &kFalse, &kFalse, &kFalse); |
| |
| NavigateAndCommit(first_url); |
| // Don't flush the message loop, as we want to navigate to a different |
| // url before the final pre-classification checks are run. |
| NavigateAndCommit(second_url); |
| WaitAndCheckPreClassificationChecks(); |
| } |
| |
| TEST_F(ClientSideDetectionHostTest, TestPreClassificationCheckPass) { |
| // Navigate the tab to a page. We should see a StartPhishingDetection IPC. |
| GURL url("http://host.com/"); |
| ExpectPreClassificationChecks(url, &kFalse, &kFalse, &kFalse, &kFalse, |
| &kFalse, &kFalse, &kFalse, &kFalse); |
| NavigateAndCommit(url); |
| WaitAndCheckPreClassificationChecks(); |
| |
| ExpectStartPhishingDetection(&url); |
| ExpectShouldClassifyForMalwareResult(true); |
| } |
| |
| TEST_F(ClientSideDetectionHostTest, |
| TestPreClassificationCheckInPageNavigation) { |
| GURL url("http://host.com/"); |
| ExpectPreClassificationChecks(url, &kFalse, &kFalse, &kFalse, &kFalse, |
| &kFalse, &kFalse, &kFalse, &kFalse); |
| NavigateAndCommit(url); |
| WaitAndCheckPreClassificationChecks(); |
| |
| ExpectStartPhishingDetection(&url); |
| ExpectShouldClassifyForMalwareResult(true); |
| |
| // Now try an in-page navigation. This should not trigger an IPC. |
| EXPECT_CALL(*csd_service_, IsPrivateIPAddress(_)).Times(0); |
| GURL inpage("http://host.com/#foo"); |
| ExpectPreClassificationChecks(inpage, NULL, NULL, NULL, NULL, NULL, NULL, |
| NULL, NULL); |
| NavigateAndCommit(inpage); |
| WaitAndCheckPreClassificationChecks(); |
| |
| ExpectStartPhishingDetection(NULL); |
| ExpectShouldClassifyForMalwareResult(true); |
| } |
| |
| TEST_F(ClientSideDetectionHostTest, TestPreClassificationCheckXHTML) { |
| // Check that XHTML is supported, in addition to the default HTML type. |
| GURL url("http://host.com/xhtml"); |
| RenderFrameHostTester::For(web_contents()->GetMainFrame())-> |
| SetContentsMimeType("application/xhtml+xml"); |
| ExpectPreClassificationChecks(url, &kFalse, &kFalse, &kFalse, &kFalse, |
| &kFalse, &kFalse, &kFalse, &kFalse); |
| NavigateAndCommit(url); |
| WaitAndCheckPreClassificationChecks(); |
| |
| ExpectStartPhishingDetection(&url); |
| ExpectShouldClassifyForMalwareResult(true); |
| } |
| |
| TEST_F(ClientSideDetectionHostTest, TestPreClassificationCheckTwoNavigations) { |
| // Navigate to two hosts, which should cause two IPCs. |
| GURL url1("http://host1.com/"); |
| ExpectPreClassificationChecks(url1, &kFalse, &kFalse, &kFalse, &kFalse, |
| &kFalse, &kFalse, &kFalse, &kFalse); |
| NavigateAndCommit(url1); |
| WaitAndCheckPreClassificationChecks(); |
| |
| ExpectStartPhishingDetection(&url1); |
| ExpectShouldClassifyForMalwareResult(true); |
| |
| GURL url2("http://host2.com/"); |
| ExpectPreClassificationChecks(url2, &kFalse, &kFalse, &kFalse, &kFalse, |
| &kFalse, &kFalse, &kFalse, &kFalse); |
| NavigateAndCommit(url2); |
| WaitAndCheckPreClassificationChecks(); |
| |
| ExpectStartPhishingDetection(&url2); |
| ExpectShouldClassifyForMalwareResult(true); |
| } |
| |
| TEST_F(ClientSideDetectionHostTest, TestPreClassificationCheckMimeType) { |
| // If the mime type is not one that we support, no IPC should be triggered |
| // but all pre-classification checks should run because we might classify |
| // other mime types for malware. |
| // Note: for this test to work correctly, the new URL must be on the |
| // same domain as the previous URL, otherwise it will create a new |
| // RenderFrameHost that won't have the mime type set. |
| GURL url("http://host2.com/image.jpg"); |
| RenderFrameHostTester::For(web_contents()->GetMainFrame())-> |
| SetContentsMimeType("image/jpeg"); |
| ExpectPreClassificationChecks(url, &kFalse, &kFalse, &kFalse, &kFalse, |
| &kFalse, &kFalse, &kFalse, &kFalse); |
| NavigateAndCommit(url); |
| WaitAndCheckPreClassificationChecks(); |
| |
| ExpectStartPhishingDetection(NULL); |
| ExpectShouldClassifyForMalwareResult(true); |
| } |
| |
| TEST_F(ClientSideDetectionHostTest, |
| TestPreClassificationCheckPrivateIpAddress) { |
| // If IsPrivateIPAddress returns true, no IPC should be triggered. |
| GURL url("http://host3.com/"); |
| ExpectPreClassificationChecks(url, &kTrue, &kFalse, NULL, NULL, NULL, NULL, |
| NULL, NULL); |
| NavigateAndCommit(url); |
| WaitAndCheckPreClassificationChecks(); |
| const IPC::Message* msg = process()->sink().GetFirstMessageMatching( |
| SafeBrowsingMsg_StartPhishingDetection::ID); |
| ASSERT_FALSE(msg); |
| ExpectShouldClassifyForMalwareResult(false); |
| } |
| |
| TEST_F(ClientSideDetectionHostTest, TestPreClassificationCheckIncognito) { |
| // If the tab is incognito there should be no IPC. Also, we shouldn't |
| // even check the csd-whitelist. |
| GURL url("http://host4.com/"); |
| ExpectPreClassificationChecks(url, &kFalse, &kTrue, NULL, NULL, NULL, NULL, |
| NULL, NULL); |
| NavigateAndCommit(url); |
| WaitAndCheckPreClassificationChecks(); |
| |
| ExpectStartPhishingDetection(NULL); |
| ExpectShouldClassifyForMalwareResult(false); |
| } |
| |
| TEST_F(ClientSideDetectionHostTest, TestPreClassificationCheckCsdWhitelist) { |
| // If the URL is on the csd whitelist no phishing IPC should be sent |
| // but we should classify the URL for malware. |
| GURL url("http://host5.com/"); |
| ExpectPreClassificationChecks(url, &kFalse, &kFalse, &kTrue, &kFalse, &kFalse, |
| &kFalse, &kFalse, &kFalse); |
| NavigateAndCommit(url); |
| WaitAndCheckPreClassificationChecks(); |
| |
| ExpectStartPhishingDetection(NULL); |
| ExpectShouldClassifyForMalwareResult(true); |
| } |
| |
| TEST_F(ClientSideDetectionHostTest, |
| TestPreClassificationCheckMalwareKillSwitch) { |
| // If the malware killswitch is on we shouldn't classify the page for malware. |
| GURL url("http://host5.com/kill-switch"); |
| ExpectPreClassificationChecks(url, &kFalse, &kFalse, &kFalse, &kTrue, &kFalse, |
| &kFalse, &kFalse, &kFalse); |
| NavigateAndCommit(url); |
| WaitAndCheckPreClassificationChecks(); |
| |
| ExpectStartPhishingDetection(&url); |
| ExpectShouldClassifyForMalwareResult(false); |
| } |
| |
| TEST_F(ClientSideDetectionHostTest, |
| TestPreClassificationCheckKillswitchAndCsdWhitelist) { |
| // If both the malware kill-swtich is on and the URL is on the csd whitelist, |
| // we will leave pre-classification checks early. |
| GURL url("http://host5.com/kill-switch-and-whitelisted"); |
| ExpectPreClassificationChecks(url, &kFalse, &kFalse, &kTrue, &kTrue, NULL, |
| NULL, NULL, NULL); |
| NavigateAndCommit(url); |
| WaitAndCheckPreClassificationChecks(); |
| |
| ExpectStartPhishingDetection(NULL); |
| ExpectShouldClassifyForMalwareResult(false); |
| } |
| |
| TEST_F(ClientSideDetectionHostTest, TestPreClassificationCheckInvalidCache) { |
| // If item is in the cache but it isn't valid, we will classify regardless |
| // of whether we are over the reporting limit. |
| GURL url("http://host6.com/"); |
| ExpectPreClassificationChecks(url, &kFalse, &kFalse, &kFalse, &kFalse, |
| &kFalse, &kTrue, NULL, &kFalse); |
| |
| NavigateAndCommit(url); |
| WaitAndCheckPreClassificationChecks(); |
| |
| ExpectStartPhishingDetection(&url); |
| ExpectShouldClassifyForMalwareResult(true); |
| } |
| |
| TEST_F(ClientSideDetectionHostTest, |
| TestPreClassificationCheckOverPhishingReportingLimit) { |
| // If the url isn't in the cache and we are over the reporting limit, we |
| // don't do classification. |
| GURL url("http://host7.com/"); |
| ExpectPreClassificationChecks(url, &kFalse, &kFalse, &kFalse, &kFalse, |
| &kFalse, &kFalse, &kTrue, &kFalse); |
| NavigateAndCommit(url); |
| WaitAndCheckPreClassificationChecks(); |
| |
| ExpectStartPhishingDetection(NULL); |
| ExpectShouldClassifyForMalwareResult(true); |
| } |
| |
| TEST_F(ClientSideDetectionHostTest, |
| TestPreClassificationCheckOverMalwareReportingLimit) { |
| GURL url("http://host.com/"); |
| ExpectPreClassificationChecks(url, &kFalse, &kFalse, &kFalse, &kFalse, |
| &kFalse, &kFalse, &kFalse, &kTrue); |
| NavigateAndCommit(url); |
| WaitAndCheckPreClassificationChecks(); |
| |
| ExpectStartPhishingDetection(&url); |
| ExpectShouldClassifyForMalwareResult(false); |
| } |
| |
| TEST_F(ClientSideDetectionHostTest, |
| TestPreClassificationCheckOverBothReportingLimits) { |
| GURL url("http://host.com/"); |
| ExpectPreClassificationChecks(url, &kFalse, &kFalse, &kFalse, &kFalse, |
| &kFalse, &kFalse, &kTrue, &kTrue); |
| NavigateAndCommit(url); |
| WaitAndCheckPreClassificationChecks(); |
| |
| ExpectStartPhishingDetection(NULL); |
| ExpectShouldClassifyForMalwareResult(false); |
| } |
| |
| TEST_F(ClientSideDetectionHostTest, TestPreClassificationCheckHttpsUrl) { |
| GURL url("https://host.com/"); |
| ExpectPreClassificationChecks(url, &kFalse, &kFalse, &kFalse, &kFalse, |
| &kFalse, &kFalse, &kFalse, &kFalse); |
| NavigateAndCommit(url); |
| WaitAndCheckPreClassificationChecks(); |
| |
| ExpectStartPhishingDetection(&url); |
| ExpectShouldClassifyForMalwareResult(true); |
| } |
| |
| TEST_F(ClientSideDetectionHostTest, |
| TestPreClassificationCheckNoneHttpOrHttpsUrl) { |
| GURL url("file://host.com/"); |
| ExpectPreClassificationChecks(url, &kFalse, &kFalse, &kFalse, &kFalse, |
| &kFalse, &kFalse, &kFalse, &kFalse); |
| NavigateAndCommit(url); |
| WaitAndCheckPreClassificationChecks(); |
| |
| ExpectStartPhishingDetection(NULL); |
| ExpectShouldClassifyForMalwareResult(true); |
| } |
| |
| TEST_F(ClientSideDetectionHostTest, TestPreClassificationCheckValidCached) { |
| // If result is cached, we will try and display the blocking page directly |
| // with no start classification message. |
| GURL url("http://host8.com/"); |
| ExpectPreClassificationChecks(url, &kFalse, &kFalse, &kFalse, &kFalse, &kTrue, |
| &kFalse, &kFalse, &kFalse); |
| |
| UnsafeResource resource; |
| EXPECT_CALL(*ui_manager_.get(), DisplayBlockingPage(_)) |
| .WillOnce(SaveArg<0>(&resource)); |
| |
| NavigateAndCommit(url); |
| WaitAndCheckPreClassificationChecks(); |
| EXPECT_EQ(url, resource.url); |
| EXPECT_EQ(url, resource.original_url); |
| |
| ExpectStartPhishingDetection(NULL); |
| |
| // Showing a phishing warning will invalidate all the weak pointers which |
| // means we will not extract malware features. |
| ExpectShouldClassifyForMalwareResult(false); |
| } |
| } // namespace safe_browsing |