Home | History | Annotate | Download | only in safe_browsing
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "base/files/file_path.h"
      6 #include "base/memory/ref_counted.h"
      7 #include "base/memory/scoped_ptr.h"
      8 #include "base/run_loop.h"
      9 #include "base/strings/stringprintf.h"
     10 #include "base/synchronization/waitable_event.h"
     11 #include "chrome/browser/safe_browsing/browser_feature_extractor.h"
     12 #include "chrome/browser/safe_browsing/client_side_detection_host.h"
     13 #include "chrome/browser/safe_browsing/client_side_detection_service.h"
     14 #include "chrome/browser/safe_browsing/database_manager.h"
     15 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
     16 #include "chrome/browser/safe_browsing/ui_manager.h"
     17 #include "chrome/common/chrome_switches.h"
     18 #include "chrome/common/safe_browsing/csd.pb.h"
     19 #include "chrome/common/safe_browsing/safebrowsing_messages.h"
     20 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
     21 #include "chrome/test/base/testing_profile.h"
     22 #include "content/public/browser/navigation_entry.h"
     23 #include "content/public/browser/web_contents.h"
     24 #include "content/public/test/mock_render_process_host.h"
     25 #include "content/public/test/test_browser_thread.h"
     26 #include "content/public/test/test_renderer_host.h"
     27 #include "content/public/test/web_contents_tester.h"
     28 #include "ipc/ipc_test_sink.h"
     29 #include "testing/gmock/include/gmock/gmock.h"
     30 #include "testing/gtest/include/gtest/gtest.h"
     31 #include "url/gurl.h"
     32 
     33 using ::testing::_;
     34 using ::testing::DeleteArg;
     35 using ::testing::DoAll;
     36 using ::testing::Eq;
     37 using ::testing::IsNull;
     38 using ::testing::Mock;
     39 using ::testing::NiceMock;
     40 using ::testing::NotNull;
     41 using ::testing::Pointee;
     42 using ::testing::Return;
     43 using ::testing::SaveArg;
     44 using ::testing::SetArgumentPointee;
     45 using ::testing::StrictMock;
     46 using content::BrowserThread;
     47 using content::RenderViewHostTester;
     48 using content::WebContents;
     49 
     50 namespace {
     51 const bool kFalse = false;
     52 const bool kTrue = true;
     53 }
     54 
     55 namespace safe_browsing {
     56 namespace {
     57 // This matcher verifies that the client computed verdict
     58 // (ClientPhishingRequest) which is passed to SendClientReportPhishingRequest
     59 // has the expected fields set.  Note: we can't simply compare the protocol
     60 // buffer strings because the BrowserFeatureExtractor might add features to the
     61 // verdict object before calling SendClientReportPhishingRequest.
     62 MATCHER_P(PartiallyEqualVerdict, other, "") {
     63   return (other.url() == arg.url() &&
     64           other.client_score() == arg.client_score() &&
     65           other.is_phishing() == arg.is_phishing());
     66 }
     67 
     68 MATCHER_P(PartiallyEqualMalwareVerdict, other, "") {
     69   if (other.url() != arg.url() ||
     70       other.referrer_url() != arg.referrer_url() ||
     71       other.bad_ip_url_info_size() != arg.bad_ip_url_info_size())
     72     return false;
     73 
     74   for (int i = 0; i < other.bad_ip_url_info_size(); ++i) {
     75     if (other.bad_ip_url_info(i).ip() != arg.bad_ip_url_info(i).ip() ||
     76         other.bad_ip_url_info(i).url() != arg.bad_ip_url_info(i).url())
     77     return false;
     78   }
     79   return true;
     80 }
     81 
     82 // Test that the callback is NULL when the verdict is not phishing.
     83 MATCHER(CallbackIsNull, "") {
     84   return arg.is_null();
     85 }
     86 
     87 ACTION(QuitUIMessageLoop) {
     88   EXPECT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::UI));
     89   base::MessageLoopForUI::current()->Quit();
     90 }
     91 
     92 // It's kind of insane that InvokeArgument doesn't work with callbacks, but it
     93 // doesn't seem like it.
     94 ACTION_TEMPLATE(InvokeCallbackArgument,
     95                 HAS_1_TEMPLATE_PARAMS(int, k),
     96                 AND_2_VALUE_PARAMS(p0, p1)) {
     97   ::std::tr1::get<k>(args).Run(p0, p1);
     98 }
     99 
    100 ACTION_P(InvokeMalwareCallback, verdict) {
    101   scoped_ptr<ClientMalwareRequest> request(::std::tr1::get<1>(args));
    102   request->CopyFrom(*verdict);
    103   ::std::tr1::get<2>(args).Run(true, request.Pass());
    104 }
    105 
    106 void EmptyUrlCheckCallback(bool processed) {
    107 }
    108 
    109 class MockClientSideDetectionService : public ClientSideDetectionService {
    110  public:
    111   MockClientSideDetectionService() : ClientSideDetectionService(NULL) {}
    112   virtual ~MockClientSideDetectionService() {};
    113 
    114   MOCK_METHOD2(SendClientReportPhishingRequest,
    115                void(ClientPhishingRequest*,
    116                     const ClientReportPhishingRequestCallback&));
    117   MOCK_METHOD2(SendClientReportMalwareRequest,
    118                void(ClientMalwareRequest*,
    119                     const ClientReportMalwareRequestCallback&));
    120   MOCK_CONST_METHOD1(IsPrivateIPAddress, bool(const std::string&));
    121   MOCK_METHOD2(GetValidCachedResult, bool(const GURL&, bool*));
    122   MOCK_METHOD1(IsInCache, bool(const GURL&));
    123   MOCK_METHOD0(OverPhishingReportLimit, bool());
    124 
    125  private:
    126   DISALLOW_COPY_AND_ASSIGN(MockClientSideDetectionService);
    127 };
    128 
    129 class MockSafeBrowsingUIManager : public SafeBrowsingUIManager {
    130  public:
    131   explicit MockSafeBrowsingUIManager(SafeBrowsingService* service)
    132       : SafeBrowsingUIManager(service) { }
    133 
    134   MOCK_METHOD1(DoDisplayBlockingPage, void(const UnsafeResource& resource));
    135 
    136   // Helper function which calls OnBlockingPageComplete for this client
    137   // object.
    138   void InvokeOnBlockingPageComplete(const UrlCheckCallback& callback) {
    139     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    140     // Note: this will delete the client object in the case of the CsdClient
    141     // implementation.
    142     if (!callback.is_null())
    143       callback.Run(false);
    144   }
    145 
    146  protected:
    147   virtual ~MockSafeBrowsingUIManager() { }
    148 
    149  private:
    150   DISALLOW_COPY_AND_ASSIGN(MockSafeBrowsingUIManager);
    151 };
    152 
    153 class MockSafeBrowsingDatabaseManager : public SafeBrowsingDatabaseManager {
    154  public:
    155   explicit MockSafeBrowsingDatabaseManager(SafeBrowsingService* service)
    156       : SafeBrowsingDatabaseManager(service) { }
    157 
    158   MOCK_METHOD1(MatchCsdWhitelistUrl, bool(const GURL&));
    159   MOCK_METHOD1(MatchMalwareIP, bool(const std::string& ip_address));
    160 
    161  protected:
    162   virtual ~MockSafeBrowsingDatabaseManager() {}
    163 
    164  private:
    165   DISALLOW_COPY_AND_ASSIGN(MockSafeBrowsingDatabaseManager);
    166 };
    167 
    168 class MockTestingProfile : public TestingProfile {
    169  public:
    170   MockTestingProfile() {}
    171   virtual ~MockTestingProfile() {}
    172 
    173   MOCK_CONST_METHOD0(IsOffTheRecord, bool());
    174 };
    175 
    176 class MockBrowserFeatureExtractor : public BrowserFeatureExtractor {
    177  public:
    178   explicit MockBrowserFeatureExtractor(
    179       WebContents* tab,
    180       ClientSideDetectionHost* host)
    181       : BrowserFeatureExtractor(tab, host) {}
    182   virtual ~MockBrowserFeatureExtractor() {}
    183 
    184   MOCK_METHOD3(ExtractFeatures,
    185                void(const BrowseInfo*,
    186                     ClientPhishingRequest*,
    187                     const BrowserFeatureExtractor::DoneCallback&));
    188 
    189   MOCK_METHOD3(ExtractMalwareFeatures,
    190                void(BrowseInfo*,
    191                     ClientMalwareRequest*,
    192                     const BrowserFeatureExtractor::MalwareDoneCallback&));
    193 };
    194 
    195 }  // namespace
    196 
    197 class ClientSideDetectionHostTest : public ChromeRenderViewHostTestHarness {
    198  public:
    199   typedef SafeBrowsingUIManager::UnsafeResource UnsafeResource;
    200 
    201   virtual void SetUp() {
    202     ChromeRenderViewHostTestHarness::SetUp();
    203 
    204     // Inject service classes.
    205     csd_service_.reset(new StrictMock<MockClientSideDetectionService>());
    206     // Only used for initializing mock objects.
    207     SafeBrowsingService* sb_service =
    208         SafeBrowsingService::CreateSafeBrowsingService();
    209     database_manager_ =
    210         new StrictMock<MockSafeBrowsingDatabaseManager>(sb_service);
    211     ui_manager_ = new StrictMock<MockSafeBrowsingUIManager>(sb_service);
    212     csd_host_.reset(safe_browsing::ClientSideDetectionHost::Create(
    213         web_contents()));
    214     csd_host_->set_client_side_detection_service(csd_service_.get());
    215     csd_host_->set_safe_browsing_managers(ui_manager_.get(),
    216                                           database_manager_.get());
    217     // We need to create this here since we don't call
    218     // DidNavigateMainFramePostCommit in this test.
    219     csd_host_->browse_info_.reset(new BrowseInfo);
    220 
    221     // By default this is set to false. Turn it on as if we are in canary or
    222     // dev channel
    223     csd_host_->malware_report_enabled_ = true;
    224   }
    225 
    226   virtual void TearDown() {
    227     // Delete the host object on the UI thread and release the
    228     // SafeBrowsingService.
    229     BrowserThread::DeleteSoon(BrowserThread::UI, FROM_HERE,
    230                               csd_host_.release());
    231     database_manager_ = NULL;
    232     ui_manager_ = NULL;
    233     base::RunLoop().RunUntilIdle();
    234     ChromeRenderViewHostTestHarness::TearDown();
    235   }
    236 
    237   virtual content::BrowserContext* CreateBrowserContext() OVERRIDE {
    238     // Set custom profile object so that we can mock calls to IsOffTheRecord.
    239     // This needs to happen before we call the parent SetUp() function.  We use
    240     // a nice mock because other parts of the code are calling IsOffTheRecord.
    241     mock_profile_ = new NiceMock<MockTestingProfile>();
    242     return mock_profile_;
    243   }
    244 
    245   void OnPhishingDetectionDone(const std::string& verdict_str) {
    246     csd_host_->OnPhishingDetectionDone(verdict_str);
    247   }
    248 
    249   void UpdateIPUrlMap(const std::string& ip, const std::string& host) {
    250     csd_host_->UpdateIPUrlMap(ip, host, "", "", ResourceType::OBJECT);
    251   }
    252 
    253   BrowseInfo* GetBrowseInfo() {
    254     return csd_host_->browse_info_.get();
    255   }
    256 
    257   void ExpectPreClassificationChecks(const GURL& url,
    258                                      const bool* is_private,
    259                                      const bool* is_incognito,
    260                                      const bool* match_csd_whitelist,
    261                                      const bool* get_valid_cached_result,
    262                                      const bool* is_in_cache,
    263                                      const bool* over_report_limit) {
    264     if (is_private) {
    265       EXPECT_CALL(*csd_service_, IsPrivateIPAddress(_))
    266           .WillOnce(Return(*is_private));
    267     }
    268     if (is_incognito) {
    269       EXPECT_CALL(*mock_profile_, IsOffTheRecord())
    270           .WillRepeatedly(Return(*is_incognito));
    271     }
    272     if (match_csd_whitelist) {
    273       EXPECT_CALL(*database_manager_.get(), MatchCsdWhitelistUrl(url))
    274           .WillOnce(Return(*match_csd_whitelist));
    275     }
    276     if (get_valid_cached_result) {
    277       EXPECT_CALL(*csd_service_, GetValidCachedResult(url, NotNull()))
    278           .WillOnce(DoAll(SetArgumentPointee<1>(true),
    279                           Return(*get_valid_cached_result)));
    280     }
    281     if (is_in_cache) {
    282       EXPECT_CALL(*csd_service_, IsInCache(url)).WillOnce(Return(*is_in_cache));
    283     }
    284     if (over_report_limit) {
    285       EXPECT_CALL(*csd_service_, OverPhishingReportLimit())
    286           .WillOnce(Return(*over_report_limit));
    287     }
    288   }
    289 
    290   void WaitAndCheckPreClassificationChecks() {
    291     // Wait for CheckCsdWhitelist and CheckCache() to be called if at all.
    292     base::RunLoop().RunUntilIdle();
    293     EXPECT_TRUE(Mock::VerifyAndClear(csd_service_.get()));
    294     EXPECT_TRUE(Mock::VerifyAndClear(ui_manager_.get()));
    295     EXPECT_TRUE(Mock::VerifyAndClear(database_manager_.get()));
    296     EXPECT_TRUE(Mock::VerifyAndClear(mock_profile_));
    297   }
    298 
    299   void SetFeatureExtractor(BrowserFeatureExtractor* extractor) {
    300     csd_host_->feature_extractor_.reset(extractor);
    301   }
    302 
    303   void SetRedirectChain(const std::vector<GURL>& redirect_chain) {
    304     csd_host_->browse_info_->url_redirects = redirect_chain;
    305   }
    306 
    307   void SetReferrer(const GURL& referrer) {
    308     csd_host_->browse_info_->referrer = referrer;
    309   }
    310 
    311   void TestUnsafeResourceCopied(const UnsafeResource& resource) {
    312     ASSERT_TRUE(csd_host_->unsafe_resource_.get());
    313     // Test that the resource from OnSafeBrowsingHit notification was copied
    314     // into the CSDH.
    315     EXPECT_EQ(resource.url, csd_host_->unsafe_resource_->url);
    316     EXPECT_EQ(resource.original_url, csd_host_->unsafe_resource_->original_url);
    317     EXPECT_EQ(resource.is_subresource,
    318               csd_host_->unsafe_resource_->is_subresource);
    319     EXPECT_EQ(resource.threat_type, csd_host_->unsafe_resource_->threat_type);
    320     EXPECT_TRUE(csd_host_->unsafe_resource_->callback.is_null());
    321     EXPECT_EQ(resource.render_process_host_id,
    322               csd_host_->unsafe_resource_->render_process_host_id);
    323     EXPECT_EQ(resource.render_view_id,
    324               csd_host_->unsafe_resource_->render_view_id);
    325   }
    326 
    327   void SetUnsafeSubResourceForCurrent() {
    328     UnsafeResource resource;
    329     resource.url = GURL("http://www.malware.com/");
    330     resource.original_url = web_contents()->GetURL();
    331     resource.is_subresource = true;
    332     resource.threat_type = SB_THREAT_TYPE_URL_MALWARE;
    333     resource.callback = base::Bind(&EmptyUrlCheckCallback);
    334     resource.render_process_host_id = web_contents()->GetRenderProcessHost()->
    335         GetID();
    336     resource.render_view_id =
    337         web_contents()->GetRenderViewHost()->GetRoutingID();
    338     ASSERT_FALSE(csd_host_->DidPageReceiveSafeBrowsingMatch());
    339     csd_host_->OnSafeBrowsingMatch(resource);
    340     ASSERT_TRUE(csd_host_->DidPageReceiveSafeBrowsingMatch());
    341     csd_host_->OnSafeBrowsingHit(resource);
    342     ASSERT_TRUE(csd_host_->DidPageReceiveSafeBrowsingMatch());
    343     resource.callback.Reset();
    344     ASSERT_TRUE(csd_host_->DidShowSBInterstitial());
    345     TestUnsafeResourceCopied(resource);
    346   }
    347 
    348   void NavigateWithSBHitAndCommit(const GURL& url) {
    349     // Create a pending navigation.
    350     controller().LoadURL(
    351         url, content::Referrer(), content::PAGE_TRANSITION_LINK, std::string());
    352 
    353     ASSERT_TRUE(pending_rvh());
    354     if (web_contents()->GetRenderViewHost()->GetProcess()->GetID() ==
    355         pending_rvh()->GetProcess()->GetID()) {
    356       EXPECT_NE(web_contents()->GetRenderViewHost()->GetRoutingID(),
    357                 pending_rvh()->GetRoutingID());
    358     }
    359 
    360     // Simulate a safebrowsing hit before navigation completes.
    361     UnsafeResource resource;
    362     resource.url = url;
    363     resource.original_url = url;
    364     resource.is_subresource = false;
    365     resource.threat_type = SB_THREAT_TYPE_URL_MALWARE;
    366     resource.callback = base::Bind(&EmptyUrlCheckCallback);
    367     resource.render_process_host_id = pending_rvh()->GetProcess()->GetID();
    368     resource.render_view_id = pending_rvh()->GetRoutingID();
    369     csd_host_->OnSafeBrowsingMatch(resource);
    370     csd_host_->OnSafeBrowsingHit(resource);
    371     resource.callback.Reset();
    372 
    373     // LoadURL created a navigation entry, now simulate the RenderView sending
    374     // a notification that it actually navigated.
    375     content::WebContentsTester::For(web_contents())->CommitPendingNavigation();
    376 
    377     ASSERT_TRUE(csd_host_->DidPageReceiveSafeBrowsingMatch());
    378     ASSERT_TRUE(csd_host_->DidShowSBInterstitial());
    379     TestUnsafeResourceCopied(resource);
    380   }
    381 
    382   void CheckIPUrlEqual(const std::vector<IPUrlInfo>& expect,
    383                        const std::vector<IPUrlInfo>& result) {
    384     ASSERT_EQ(expect.size(), result.size());
    385 
    386     for (unsigned int i = 0; i < expect.size(); ++i) {
    387       EXPECT_EQ(expect[i].url, result[i].url);
    388       EXPECT_EQ(expect[i].method, result[i].method);
    389       EXPECT_EQ(expect[i].referrer, result[i].referrer);
    390       EXPECT_EQ(expect[i].resource_type, result[i].resource_type);
    391     }
    392   }
    393 
    394  protected:
    395   scoped_ptr<ClientSideDetectionHost> csd_host_;
    396   scoped_ptr<StrictMock<MockClientSideDetectionService> > csd_service_;
    397   scoped_refptr<StrictMock<MockSafeBrowsingUIManager> > ui_manager_;
    398   scoped_refptr<StrictMock<MockSafeBrowsingDatabaseManager> > database_manager_;
    399   MockTestingProfile* mock_profile_;  // We don't own this object
    400 };
    401 
    402 TEST_F(ClientSideDetectionHostTest, OnPhishingDetectionDoneInvalidVerdict) {
    403   // Case 0: renderer sends an invalid verdict string that we're unable to
    404   // parse.
    405   MockBrowserFeatureExtractor* mock_extractor =
    406       new StrictMock<MockBrowserFeatureExtractor>(
    407           web_contents(),
    408           csd_host_.get());
    409   SetFeatureExtractor(mock_extractor);  // The host class takes ownership.
    410   EXPECT_CALL(*mock_extractor, ExtractFeatures(_, _, _)).Times(0);
    411   OnPhishingDetectionDone("Invalid Protocol Buffer");
    412   EXPECT_TRUE(Mock::VerifyAndClear(mock_extractor));
    413 }
    414 
    415 TEST_F(ClientSideDetectionHostTest, OnPhishingDetectionDoneNotPhishing) {
    416   // Case 1: client thinks the page is phishing.  The server does not agree.
    417   // No interstitial is shown.
    418   MockBrowserFeatureExtractor* mock_extractor =
    419       new StrictMock<MockBrowserFeatureExtractor>(
    420           web_contents(),
    421           csd_host_.get());
    422   SetFeatureExtractor(mock_extractor);  // The host class takes ownership.
    423 
    424   ClientSideDetectionService::ClientReportPhishingRequestCallback cb;
    425   ClientPhishingRequest verdict;
    426   verdict.set_url("http://phishingurl.com/");
    427   verdict.set_client_score(1.0f);
    428   verdict.set_is_phishing(true);
    429 
    430   ClientMalwareRequest malware_verdict;
    431   malware_verdict.set_url(verdict.url());
    432   EXPECT_CALL(*mock_extractor, ExtractFeatures(_, _, _))
    433       .WillOnce(DoAll(DeleteArg<1>(),
    434                       InvokeCallbackArgument<2>(true, &verdict)));
    435   EXPECT_CALL(*mock_extractor, ExtractMalwareFeatures(_, _, _))
    436       .WillOnce(InvokeMalwareCallback(&malware_verdict));
    437   EXPECT_CALL(*csd_service_,
    438               SendClientReportPhishingRequest(
    439                   Pointee(PartiallyEqualVerdict(verdict)), _))
    440       .WillOnce(SaveArg<1>(&cb));
    441   OnPhishingDetectionDone(verdict.SerializeAsString());
    442   EXPECT_TRUE(Mock::VerifyAndClear(csd_host_.get()));
    443   ASSERT_FALSE(cb.is_null());
    444 
    445   // Make sure DoDisplayBlockingPage is not going to be called.
    446   EXPECT_CALL(*ui_manager_.get(), DoDisplayBlockingPage(_)).Times(0);
    447   cb.Run(GURL(verdict.url()), false);
    448   base::RunLoop().RunUntilIdle();
    449   EXPECT_TRUE(Mock::VerifyAndClear(ui_manager_.get()));
    450 }
    451 
    452 TEST_F(ClientSideDetectionHostTest, OnPhishingDetectionDoneDisabled) {
    453   // Case 2: client thinks the page is phishing and so does the server but
    454   // showing the interstitial is disabled => no interstitial is shown.
    455   MockBrowserFeatureExtractor* mock_extractor =
    456       new StrictMock<MockBrowserFeatureExtractor>(
    457           web_contents(),
    458           csd_host_.get());
    459   SetFeatureExtractor(mock_extractor);  // The host class takes ownership.
    460 
    461   ClientSideDetectionService::ClientReportPhishingRequestCallback cb;
    462   ClientPhishingRequest verdict;
    463   verdict.set_url("http://phishingurl.com/");
    464   verdict.set_client_score(1.0f);
    465   verdict.set_is_phishing(true);
    466 
    467   EXPECT_CALL(*mock_extractor, ExtractFeatures(_, _, _))
    468       .WillOnce(DoAll(DeleteArg<1>(),
    469                       InvokeCallbackArgument<2>(true, &verdict)));
    470   EXPECT_CALL(*csd_service_,
    471               SendClientReportPhishingRequest(
    472                   Pointee(PartiallyEqualVerdict(verdict)), _))
    473       .WillOnce(SaveArg<1>(&cb));
    474 
    475   ClientMalwareRequest malware_verdict;
    476   malware_verdict.set_url(verdict.url());
    477   EXPECT_CALL(*mock_extractor, ExtractMalwareFeatures(_, _, _))
    478       .WillOnce(InvokeMalwareCallback(&malware_verdict));
    479   EXPECT_CALL(*csd_service_,
    480               SendClientReportMalwareRequest(_, _)).Times(0);
    481 
    482   OnPhishingDetectionDone(verdict.SerializeAsString());
    483   EXPECT_TRUE(Mock::VerifyAndClear(csd_host_.get()));
    484   ASSERT_FALSE(cb.is_null());
    485 
    486   // Make sure DoDisplayBlockingPage is not going to be called.
    487   EXPECT_CALL(*ui_manager_.get(), DoDisplayBlockingPage(_)).Times(0);
    488   cb.Run(GURL(verdict.url()), false);
    489   base::RunLoop().RunUntilIdle();
    490   EXPECT_TRUE(Mock::VerifyAndClear(ui_manager_.get()));
    491 }
    492 
    493 TEST_F(ClientSideDetectionHostTest, OnPhishingDetectionDoneShowInterstitial) {
    494   // Case 3: client thinks the page is phishing and so does the server.
    495   // We show an interstitial.
    496   MockBrowserFeatureExtractor* mock_extractor =
    497       new StrictMock<MockBrowserFeatureExtractor>(
    498           web_contents(),
    499           csd_host_.get());
    500   SetFeatureExtractor(mock_extractor);  // The host class takes ownership.
    501 
    502   ClientSideDetectionService::ClientReportPhishingRequestCallback cb;
    503   GURL phishing_url("http://phishingurl.com/");
    504   ClientPhishingRequest verdict;
    505   verdict.set_url(phishing_url.spec());
    506   verdict.set_client_score(1.0f);
    507   verdict.set_is_phishing(true);
    508 
    509   ClientMalwareRequest malware_verdict;
    510   malware_verdict.set_url(verdict.url());
    511 
    512   EXPECT_CALL(*mock_extractor, ExtractFeatures(_, _, _))
    513       .WillOnce(DoAll(DeleteArg<1>(),
    514                       InvokeCallbackArgument<2>(true, &verdict)));
    515   EXPECT_CALL(*mock_extractor, ExtractMalwareFeatures(_, _, _))
    516       .WillOnce(InvokeMalwareCallback(&malware_verdict));
    517   EXPECT_CALL(*csd_service_,
    518               SendClientReportPhishingRequest(
    519                   Pointee(PartiallyEqualVerdict(verdict)), _))
    520       .WillOnce(SaveArg<1>(&cb));
    521   OnPhishingDetectionDone(verdict.SerializeAsString());
    522   EXPECT_TRUE(Mock::VerifyAndClear(csd_host_.get()));
    523   EXPECT_TRUE(Mock::VerifyAndClear(csd_service_.get()));
    524   ASSERT_FALSE(cb.is_null());
    525 
    526   UnsafeResource resource;
    527   EXPECT_CALL(*ui_manager_.get(), DoDisplayBlockingPage(_))
    528       .WillOnce(SaveArg<0>(&resource));
    529   cb.Run(phishing_url, true);
    530 
    531   base::RunLoop().RunUntilIdle();
    532   EXPECT_TRUE(Mock::VerifyAndClear(ui_manager_.get()));
    533   EXPECT_EQ(phishing_url, resource.url);
    534   EXPECT_EQ(phishing_url, resource.original_url);
    535   EXPECT_FALSE(resource.is_subresource);
    536   EXPECT_EQ(SB_THREAT_TYPE_CLIENT_SIDE_PHISHING_URL, resource.threat_type);
    537   EXPECT_EQ(web_contents()->GetRenderProcessHost()->GetID(),
    538             resource.render_process_host_id);
    539   EXPECT_EQ(web_contents()->GetRenderViewHost()->GetRoutingID(),
    540             resource.render_view_id);
    541 
    542   // Make sure the client object will be deleted.
    543   BrowserThread::PostTask(
    544       BrowserThread::IO,
    545       FROM_HERE,
    546       base::Bind(&MockSafeBrowsingUIManager::InvokeOnBlockingPageComplete,
    547                  ui_manager_, resource.callback));
    548 }
    549 
    550 TEST_F(ClientSideDetectionHostTest, OnPhishingDetectionDoneMultiplePings) {
    551   // Case 4 & 5: client thinks a page is phishing then navigates to
    552   // another page which is also considered phishing by the client
    553   // before the server responds with a verdict.  After a while the
    554   // server responds for both requests with a phishing verdict.  Only
    555   // a single interstitial is shown for the second URL.
    556   MockBrowserFeatureExtractor* mock_extractor =
    557       new StrictMock<MockBrowserFeatureExtractor>(
    558           web_contents(),
    559           csd_host_.get());
    560   SetFeatureExtractor(mock_extractor);  // The host class takes ownership.
    561 
    562   ClientSideDetectionService::ClientReportPhishingRequestCallback cb;
    563   GURL phishing_url("http://phishingurl.com/");
    564   ClientPhishingRequest verdict;
    565   verdict.set_url(phishing_url.spec());
    566   verdict.set_client_score(1.0f);
    567   verdict.set_is_phishing(true);
    568 
    569   ClientMalwareRequest malware_verdict;
    570   malware_verdict.set_url(verdict.url());
    571 
    572   EXPECT_CALL(*mock_extractor, ExtractFeatures(_, _, _))
    573       .WillOnce(DoAll(DeleteArg<1>(),
    574                       InvokeCallbackArgument<2>(true, &verdict)));
    575   EXPECT_CALL(*mock_extractor, ExtractMalwareFeatures(_, _, _))
    576       .WillOnce(InvokeMalwareCallback(&malware_verdict));
    577   EXPECT_CALL(*csd_service_,
    578               SendClientReportPhishingRequest(
    579                   Pointee(PartiallyEqualVerdict(verdict)), _))
    580       .WillOnce(SaveArg<1>(&cb));
    581   OnPhishingDetectionDone(verdict.SerializeAsString());
    582   EXPECT_TRUE(Mock::VerifyAndClear(csd_host_.get()));
    583   EXPECT_TRUE(Mock::VerifyAndClear(csd_service_.get()));
    584   ASSERT_FALSE(cb.is_null());
    585 
    586   // Set this back to a normal browser feature extractor since we're using
    587   // NavigateAndCommit() and it's easier to use the real thing than setting up
    588   // mock expectations.
    589   SetFeatureExtractor(new BrowserFeatureExtractor(web_contents(),
    590                                                   csd_host_.get()));
    591   GURL other_phishing_url("http://other_phishing_url.com/bla");
    592   ExpectPreClassificationChecks(other_phishing_url, &kFalse, &kFalse, &kFalse,
    593                                 &kFalse, &kFalse, &kFalse);
    594   // We navigate away.  The callback cb should be revoked.
    595   NavigateAndCommit(other_phishing_url);
    596   // Wait for the pre-classification checks to finish for other_phishing_url.
    597   WaitAndCheckPreClassificationChecks();
    598 
    599   ClientSideDetectionService::ClientReportPhishingRequestCallback cb_other;
    600   verdict.set_url(other_phishing_url.spec());
    601   verdict.set_client_score(0.8f);
    602   EXPECT_CALL(*csd_service_,
    603               SendClientReportPhishingRequest(
    604                   Pointee(PartiallyEqualVerdict(verdict)), _))
    605       .WillOnce(DoAll(DeleteArg<0>(),
    606                       SaveArg<1>(&cb_other),
    607                       QuitUIMessageLoop()));
    608   std::vector<GURL> redirect_chain;
    609   redirect_chain.push_back(other_phishing_url);
    610   SetRedirectChain(redirect_chain);
    611   OnPhishingDetectionDone(verdict.SerializeAsString());
    612   base::MessageLoop::current()->Run();
    613   EXPECT_TRUE(Mock::VerifyAndClear(csd_host_.get()));
    614   EXPECT_TRUE(Mock::VerifyAndClear(csd_service_.get()));
    615   ASSERT_FALSE(cb_other.is_null());
    616 
    617   // We expect that the interstitial is shown for the second phishing URL and
    618   // not for the first phishing URL.
    619   UnsafeResource resource;
    620   EXPECT_CALL(*ui_manager_.get(), DoDisplayBlockingPage(_))
    621       .WillOnce(SaveArg<0>(&resource));
    622 
    623   cb.Run(phishing_url, true);  // Should have no effect.
    624   cb_other.Run(other_phishing_url, true);  // Should show interstitial.
    625 
    626   base::RunLoop().RunUntilIdle();
    627   EXPECT_TRUE(Mock::VerifyAndClear(ui_manager_.get()));
    628   EXPECT_EQ(other_phishing_url, resource.url);
    629   EXPECT_EQ(other_phishing_url, resource.original_url);
    630   EXPECT_FALSE(resource.is_subresource);
    631   EXPECT_EQ(SB_THREAT_TYPE_CLIENT_SIDE_PHISHING_URL, resource.threat_type);
    632   EXPECT_EQ(web_contents()->GetRenderProcessHost()->GetID(),
    633             resource.render_process_host_id);
    634   EXPECT_EQ(web_contents()->GetRenderViewHost()->GetRoutingID(),
    635             resource.render_view_id);
    636 
    637   // Make sure the client object will be deleted.
    638   BrowserThread::PostTask(
    639       BrowserThread::IO,
    640       FROM_HERE,
    641       base::Bind(&MockSafeBrowsingUIManager::InvokeOnBlockingPageComplete,
    642                  ui_manager_, resource.callback));
    643 }
    644 
    645 TEST_F(ClientSideDetectionHostTest,
    646        OnPhishingDetectionDoneVerdictNotPhishing) {
    647   // Case 6: renderer sends a verdict string that isn't phishing.
    648   MockBrowserFeatureExtractor* mock_extractor =
    649       new StrictMock<MockBrowserFeatureExtractor>(
    650           web_contents(),
    651           csd_host_.get());
    652   SetFeatureExtractor(mock_extractor);  // The host class takes ownership.
    653 
    654   ClientPhishingRequest verdict;
    655   verdict.set_url("http://not-phishing.com/");
    656   verdict.set_client_score(0.1f);
    657   verdict.set_is_phishing(false);
    658 
    659   ClientMalwareRequest malware_verdict;
    660   malware_verdict.set_url(verdict.url());
    661 
    662   EXPECT_CALL(*mock_extractor, ExtractFeatures(_, _, _)).Times(0);
    663   EXPECT_CALL(*mock_extractor, ExtractMalwareFeatures(_, _, _))
    664       .WillOnce(InvokeMalwareCallback(&malware_verdict));
    665   OnPhishingDetectionDone(verdict.SerializeAsString());
    666   EXPECT_TRUE(Mock::VerifyAndClear(mock_extractor));
    667 }
    668 
    669 TEST_F(ClientSideDetectionHostTest,
    670        OnPhishingDetectionDoneVerdictNotPhishingButSBMatchSubResource) {
    671   // Case 7: renderer sends a verdict string that isn't phishing but the URL
    672   // of a subresource was on the regular phishing or malware lists.
    673   GURL url("http://not-phishing.com/");
    674   ClientPhishingRequest verdict;
    675   verdict.set_url(url.spec());
    676   verdict.set_client_score(0.1f);
    677   verdict.set_is_phishing(false);
    678 
    679   // First we have to navigate to the URL to set the unique page ID.
    680   ExpectPreClassificationChecks(url, &kFalse, &kFalse, &kFalse, &kFalse,
    681                                 &kFalse, &kFalse);
    682   NavigateAndCommit(url);
    683   WaitAndCheckPreClassificationChecks();
    684   SetUnsafeSubResourceForCurrent();
    685 
    686   EXPECT_CALL(*csd_service_,
    687               SendClientReportPhishingRequest(
    688                   Pointee(PartiallyEqualVerdict(verdict)), CallbackIsNull()))
    689       .WillOnce(DoAll(DeleteArg<0>(), QuitUIMessageLoop()));
    690   std::vector<GURL> redirect_chain;
    691   redirect_chain.push_back(url);
    692   SetRedirectChain(redirect_chain);
    693   OnPhishingDetectionDone(verdict.SerializeAsString());
    694   base::MessageLoop::current()->Run();
    695   EXPECT_TRUE(Mock::VerifyAndClear(csd_host_.get()));
    696 }
    697 
    698 TEST_F(ClientSideDetectionHostTest,
    699        OnPhishingDetectionDoneVerdictNotPhishingButSBMatchOnNewRVH) {
    700   // When navigating to a different host (thus creating a pending RVH) which
    701   // matches regular malware list, and after navigation the renderer sends a
    702   // verdict string that isn't phishing, we should still send the report.
    703 
    704   // Do an initial navigation to a safe host.
    705   GURL start_url("http://safe.example.com/");
    706   ExpectPreClassificationChecks(
    707       start_url, &kFalse, &kFalse, &kFalse, &kFalse, &kFalse, &kFalse);
    708   NavigateAndCommit(start_url);
    709   WaitAndCheckPreClassificationChecks();
    710 
    711   // Now navigate to a different host which will have a malware hit before the
    712   // navigation commits.
    713   GURL url("http://malware-but-not-phishing.com/");
    714   ClientPhishingRequest verdict;
    715   verdict.set_url(url.spec());
    716   verdict.set_client_score(0.1f);
    717   verdict.set_is_phishing(false);
    718 
    719   ExpectPreClassificationChecks(url, &kFalse, &kFalse, &kFalse, &kFalse,
    720                                 &kFalse, &kFalse);
    721   NavigateWithSBHitAndCommit(url);
    722   WaitAndCheckPreClassificationChecks();
    723 
    724   EXPECT_CALL(*csd_service_,
    725               SendClientReportPhishingRequest(
    726                   Pointee(PartiallyEqualVerdict(verdict)), CallbackIsNull()))
    727       .WillOnce(DoAll(DeleteArg<0>(), QuitUIMessageLoop()));
    728   std::vector<GURL> redirect_chain;
    729   redirect_chain.push_back(url);
    730   SetRedirectChain(redirect_chain);
    731   OnPhishingDetectionDone(verdict.SerializeAsString());
    732   base::MessageLoop::current()->Run();
    733   EXPECT_TRUE(Mock::VerifyAndClear(csd_host_.get()));
    734 }
    735 
    736 TEST_F(ClientSideDetectionHostTest, UpdateIPUrlMap) {
    737   BrowseInfo* browse_info = GetBrowseInfo();
    738 
    739   // Empty IP or host are skipped
    740   UpdateIPUrlMap("250.10.10.10", std::string());
    741   ASSERT_EQ(0U, browse_info->ips.size());
    742   UpdateIPUrlMap(std::string(), "http://google.com/a");
    743   ASSERT_EQ(0U, browse_info->ips.size());
    744   UpdateIPUrlMap(std::string(), std::string());
    745   ASSERT_EQ(0U, browse_info->ips.size());
    746 
    747   std::vector<IPUrlInfo> expected_urls;
    748   for (int i = 0; i < 20; i++) {
    749     std::string url = base::StringPrintf("http://%d.com/", i);
    750     expected_urls.push_back(IPUrlInfo(url, "", "", ResourceType::OBJECT));
    751     UpdateIPUrlMap("250.10.10.10", url);
    752   }
    753   ASSERT_EQ(1U, browse_info->ips.size());
    754   ASSERT_EQ(20U, browse_info->ips["250.10.10.10"].size());
    755   CheckIPUrlEqual(expected_urls,
    756                   browse_info->ips["250.10.10.10"]);
    757 
    758   // Add more urls for this ip, it exceeds max limit and won't be added
    759   UpdateIPUrlMap("250.10.10.10", "http://21.com/");
    760   ASSERT_EQ(1U, browse_info->ips.size());
    761   ASSERT_EQ(20U, browse_info->ips["250.10.10.10"].size());
    762   CheckIPUrlEqual(expected_urls,
    763                   browse_info->ips["250.10.10.10"]);
    764 
    765   // Add 199 more IPs
    766   for (int i = 0; i < 199; i++) {
    767     std::string ip = base::StringPrintf("%d.%d.%d.256", i, i, i);
    768     expected_urls.clear();
    769     expected_urls.push_back(IPUrlInfo("test.com/", "", "",
    770                             ResourceType::OBJECT));
    771     UpdateIPUrlMap(ip, "test.com/");
    772     ASSERT_EQ(1U, browse_info->ips[ip].size());
    773     CheckIPUrlEqual(expected_urls,
    774                     browse_info->ips[ip]);
    775   }
    776   ASSERT_EQ(200U, browse_info->ips.size());
    777 
    778   // Exceeding max ip limit 200, these won't be added
    779   UpdateIPUrlMap("250.250.250.250", "goo.com/");
    780   UpdateIPUrlMap("250.250.250.250", "bar.com/");
    781   UpdateIPUrlMap("250.250.0.250", "foo.com/");
    782   ASSERT_EQ(200U, browse_info->ips.size());
    783 
    784   // Add url to existing IPs succeed
    785   UpdateIPUrlMap("100.100.100.256", "more.com/");
    786   ASSERT_EQ(2U, browse_info->ips["100.100.100.256"].size());
    787   expected_urls.clear();
    788   expected_urls.push_back(IPUrlInfo("test.com/", "", "", ResourceType::OBJECT));
    789   expected_urls.push_back(IPUrlInfo("more.com/", "", "", ResourceType::OBJECT));
    790   CheckIPUrlEqual(expected_urls,
    791                   browse_info->ips["100.100.100.256"]);
    792 }
    793 
    794 TEST_F(ClientSideDetectionHostTest,
    795        OnPhishingDetectionDoneVerdictNotPhishingNotMalwareIP) {
    796   // Case 7: renderer sends a verdict string that isn't phishing and not matches
    797   // malware bad IP list
    798   MockBrowserFeatureExtractor* mock_extractor =
    799       new StrictMock<MockBrowserFeatureExtractor>(
    800           web_contents(),
    801           csd_host_.get());
    802   SetFeatureExtractor(mock_extractor);  // The host class takes ownership.
    803 
    804   ClientPhishingRequest verdict;
    805   verdict.set_url("http://not-phishing.com/");
    806   verdict.set_client_score(0.1f);
    807   verdict.set_is_phishing(false);
    808 
    809   ClientMalwareRequest malware_verdict;
    810   malware_verdict.set_url(verdict.url());
    811 
    812   // That is a special case.  If there were no IP matches or if feature
    813   // extraction failed the callback will delete the malware_verdict.
    814   EXPECT_CALL(*mock_extractor, ExtractMalwareFeatures(_, _, _))
    815       .WillOnce(InvokeMalwareCallback(&malware_verdict));
    816   EXPECT_CALL(*csd_service_,
    817               SendClientReportMalwareRequest(_, _)).Times(0);
    818   EXPECT_CALL(*mock_extractor, ExtractFeatures(_, _, _)).Times(0);
    819 
    820   OnPhishingDetectionDone(verdict.SerializeAsString());
    821   EXPECT_TRUE(Mock::VerifyAndClear(mock_extractor));
    822 }
    823 
    824 TEST_F(ClientSideDetectionHostTest,
    825        OnPhishingDetectionDoneVerdictNotPhishingButMalwareIP) {
    826   // Case 8: renderer sends a verdict string that isn't phishing but matches
    827   // malware bad IP list
    828   MockBrowserFeatureExtractor* mock_extractor =
    829       new StrictMock<MockBrowserFeatureExtractor>(
    830           web_contents(),
    831           csd_host_.get());
    832   SetFeatureExtractor(mock_extractor);  // The host class takes ownership.
    833 
    834   ClientPhishingRequest verdict;
    835   verdict.set_url("http://not-phishing.com/");
    836   verdict.set_client_score(0.1f);
    837   verdict.set_is_phishing(false);
    838 
    839   ClientMalwareRequest malware_verdict;
    840   malware_verdict.set_url(verdict.url());
    841   malware_verdict.set_referrer_url("http://referrer.com/");
    842   ClientMalwareRequest::UrlInfo* badipurl =
    843       malware_verdict.add_bad_ip_url_info();
    844   badipurl->set_ip("1.2.3.4");
    845   badipurl->set_url("badip.com");
    846 
    847   EXPECT_CALL(*mock_extractor, ExtractMalwareFeatures(_, _, _))
    848       .WillOnce(InvokeMalwareCallback(&malware_verdict));
    849   EXPECT_CALL(*csd_service_,
    850               SendClientReportMalwareRequest(
    851                   Pointee(PartiallyEqualMalwareVerdict(malware_verdict)), _))
    852       .WillOnce(DeleteArg<0>());
    853   EXPECT_CALL(*mock_extractor, ExtractFeatures(_, _, _)).Times(0);
    854 
    855   SetReferrer(GURL("http://referrer.com/"));
    856   OnPhishingDetectionDone(verdict.SerializeAsString());
    857   EXPECT_TRUE(Mock::VerifyAndClear(mock_extractor));
    858 }
    859 
    860 TEST_F(ClientSideDetectionHostTest,
    861        OnPhishingDetectionDoneVerdictPhishingAndMalwareIP) {
    862   // Case 9: renderer sends a verdict string that is phishing and matches
    863   // malware bad IP list
    864   MockBrowserFeatureExtractor* mock_extractor =
    865       new StrictMock<MockBrowserFeatureExtractor>(
    866           web_contents(),
    867           csd_host_.get());
    868   SetFeatureExtractor(mock_extractor);  // The host class takes ownership.
    869 
    870   ClientSideDetectionService::ClientReportPhishingRequestCallback cb;
    871   ClientPhishingRequest verdict;
    872   verdict.set_url("http://not-phishing.com/");
    873   verdict.set_client_score(0.1f);
    874   verdict.set_is_phishing(true);
    875 
    876   ClientMalwareRequest malware_verdict;
    877   malware_verdict.set_url(verdict.url());
    878   ClientMalwareRequest::UrlInfo* badipurl =
    879       malware_verdict.add_bad_ip_url_info();
    880   badipurl->set_ip("1.2.3.4");
    881   badipurl->set_url("badip.com");
    882 
    883   EXPECT_CALL(*mock_extractor, ExtractMalwareFeatures(_, _, _))
    884       .WillOnce(InvokeMalwareCallback(&malware_verdict));
    885   EXPECT_CALL(*csd_service_,
    886               SendClientReportMalwareRequest(
    887                   Pointee(PartiallyEqualMalwareVerdict(malware_verdict)), _))
    888       .WillOnce(DeleteArg<0>());
    889 
    890   EXPECT_CALL(*mock_extractor, ExtractFeatures(_, _, _))
    891       .WillOnce(DoAll(DeleteArg<1>(),
    892                       InvokeCallbackArgument<2>(true, &verdict)));
    893 
    894   EXPECT_CALL(*csd_service_,
    895               SendClientReportPhishingRequest(
    896                   Pointee(PartiallyEqualVerdict(verdict)), _))
    897       .WillOnce(SaveArg<1>(&cb));
    898 
    899   // Referrer url using https won't be set and sent out.
    900   SetReferrer(GURL("https://referrer.com/"));
    901   OnPhishingDetectionDone(verdict.SerializeAsString());
    902   EXPECT_TRUE(Mock::VerifyAndClear(mock_extractor));
    903   EXPECT_TRUE(Mock::VerifyAndClear(csd_host_.get()));
    904   EXPECT_TRUE(Mock::VerifyAndClear(csd_service_.get()));
    905   ASSERT_FALSE(cb.is_null());
    906 }
    907 
    908 TEST_F(ClientSideDetectionHostTest,
    909        OnPhishingDetectionDoneShowMalwareInterstitial) {
    910   // Case 10: client thinks the page match malware IP and so does the server.
    911   // We show an sub-resource malware interstitial.
    912   MockBrowserFeatureExtractor* mock_extractor =
    913       new StrictMock<MockBrowserFeatureExtractor>(
    914           web_contents(),
    915           csd_host_.get());
    916   SetFeatureExtractor(mock_extractor);  // The host class takes ownership.
    917 
    918   ClientPhishingRequest verdict;
    919   verdict.set_url("http://not-phishing.com/");
    920   verdict.set_client_score(0.1f);
    921   verdict.set_is_phishing(false);
    922 
    923   ClientSideDetectionService::ClientReportMalwareRequestCallback cb;
    924   GURL malware_landing_url("http://malware.com/");
    925   GURL malware_ip_url("http://badip.com");
    926   ClientMalwareRequest malware_verdict;
    927   malware_verdict.set_url("http://malware.com/");
    928   ClientMalwareRequest::UrlInfo* badipurl =
    929       malware_verdict.add_bad_ip_url_info();
    930   badipurl->set_ip("1.2.3.4");
    931   badipurl->set_url("http://badip.com");
    932 
    933   EXPECT_CALL(*mock_extractor, ExtractMalwareFeatures(_, _, _))
    934       .WillOnce(InvokeMalwareCallback(&malware_verdict));
    935   EXPECT_CALL(*csd_service_,
    936               SendClientReportMalwareRequest(
    937                   Pointee(PartiallyEqualMalwareVerdict(malware_verdict)), _))
    938       .WillOnce(DoAll(DeleteArg<0>(), SaveArg<1>(&cb)));
    939   OnPhishingDetectionDone(verdict.SerializeAsString());
    940   EXPECT_TRUE(Mock::VerifyAndClear(csd_host_.get()));
    941   EXPECT_TRUE(Mock::VerifyAndClear(csd_service_.get()));
    942   ASSERT_FALSE(cb.is_null());
    943 
    944   UnsafeResource resource;
    945   EXPECT_CALL(*ui_manager_.get(), DoDisplayBlockingPage(_))
    946       .WillOnce(SaveArg<0>(&resource));
    947   cb.Run(malware_landing_url, malware_ip_url, true);
    948 
    949   base::RunLoop().RunUntilIdle();
    950   EXPECT_TRUE(Mock::VerifyAndClear(ui_manager_.get()));
    951   EXPECT_EQ(malware_ip_url, resource.url);
    952   EXPECT_EQ(malware_landing_url, resource.original_url);
    953   EXPECT_TRUE(resource.is_subresource);
    954   EXPECT_EQ(SB_THREAT_TYPE_CLIENT_SIDE_MALWARE_URL, resource.threat_type);
    955   EXPECT_EQ(web_contents()->GetRenderProcessHost()->GetID(),
    956             resource.render_process_host_id);
    957   EXPECT_EQ(web_contents()->GetRenderViewHost()->GetRoutingID(),
    958             resource.render_view_id);
    959 
    960   // Make sure the client object will be deleted.
    961   BrowserThread::PostTask(
    962       BrowserThread::IO,
    963       FROM_HERE,
    964       base::Bind(&MockSafeBrowsingUIManager::InvokeOnBlockingPageComplete,
    965                  ui_manager_, resource.callback));
    966 }
    967 
    968 TEST_F(ClientSideDetectionHostTest, NavigationCancelsShouldClassifyUrl) {
    969   // Test that canceling pending should classify requests works as expected.
    970 
    971   GURL first_url("http://first.phishy.url.com");
    972   GURL second_url("http://second.url.com/");
    973   // The first few checks are done synchronously so check that they have been
    974   // done for the first URL, while the second URL has all the checks done.  We
    975   // need to manually set up the IsPrivateIPAddress mock since if the same mock
    976   // expectation is specified twice, gmock will only use the last instance of
    977   // it, meaning the first will never be matched.
    978   EXPECT_CALL(*csd_service_, IsPrivateIPAddress(_))
    979       .WillOnce(Return(false))
    980       .WillOnce(Return(false));
    981   ExpectPreClassificationChecks(first_url, NULL, &kFalse, &kFalse, NULL,
    982                                 NULL, NULL);
    983   ExpectPreClassificationChecks(second_url, NULL, &kFalse, &kFalse, &kFalse,
    984                                 &kFalse, &kFalse);
    985 
    986   NavigateAndCommit(first_url);
    987   // Don't flush the message loop, as we want to navigate to a different
    988   // url before the final pre-classification checks are run.
    989   NavigateAndCommit(second_url);
    990   WaitAndCheckPreClassificationChecks();
    991 }
    992 
    993 TEST_F(ClientSideDetectionHostTest, ShouldClassifyUrl) {
    994   // Navigate the tab to a page.  We should see a StartPhishingDetection IPC.
    995   GURL url("http://host.com/");
    996   ExpectPreClassificationChecks(url, &kFalse, &kFalse, &kFalse, &kFalse,
    997                                 &kFalse, &kFalse);
    998   NavigateAndCommit(url);
    999   WaitAndCheckPreClassificationChecks();
   1000 
   1001   const IPC::Message* msg = process()->sink().GetFirstMessageMatching(
   1002       SafeBrowsingMsg_StartPhishingDetection::ID);
   1003   ASSERT_TRUE(msg);
   1004   Tuple1<GURL> actual_url;
   1005   SafeBrowsingMsg_StartPhishingDetection::Read(msg, &actual_url);
   1006   EXPECT_EQ(url, actual_url.a);
   1007   EXPECT_EQ(rvh()->GetRoutingID(), msg->routing_id());
   1008   process()->sink().ClearMessages();
   1009 
   1010   // Now try an in-page navigation.  This should not trigger an IPC.
   1011   EXPECT_CALL(*csd_service_, IsPrivateIPAddress(_)).Times(0);
   1012   url = GURL("http://host.com/#foo");
   1013   ExpectPreClassificationChecks(url, NULL, NULL, NULL, NULL, NULL, NULL);
   1014   NavigateAndCommit(url);
   1015   WaitAndCheckPreClassificationChecks();
   1016 
   1017   msg = process()->sink().GetFirstMessageMatching(
   1018       SafeBrowsingMsg_StartPhishingDetection::ID);
   1019   ASSERT_FALSE(msg);
   1020 
   1021   // Check that XHTML is supported, in addition to the default HTML type.
   1022   // Note: for this test to work correctly, the new URL must be on the
   1023   // same domain as the previous URL, otherwise it will create a new
   1024   // RenderViewHost that won't have the mime type set.
   1025   url = GURL("http://host.com/xhtml");
   1026   rvh_tester()->SetContentsMimeType("application/xhtml+xml");
   1027   ExpectPreClassificationChecks(url, &kFalse, &kFalse, &kFalse, &kFalse,
   1028                                 &kFalse, &kFalse);
   1029   NavigateAndCommit(url);
   1030   WaitAndCheckPreClassificationChecks();
   1031   msg = process()->sink().GetFirstMessageMatching(
   1032       SafeBrowsingMsg_StartPhishingDetection::ID);
   1033   ASSERT_TRUE(msg);
   1034   SafeBrowsingMsg_StartPhishingDetection::Read(msg, &actual_url);
   1035   EXPECT_EQ(url, actual_url.a);
   1036   EXPECT_EQ(rvh()->GetRoutingID(), msg->routing_id());
   1037   process()->sink().ClearMessages();
   1038 
   1039   // Navigate to a new host, which should cause another IPC.
   1040   url = GURL("http://host2.com/");
   1041   ExpectPreClassificationChecks(url, &kFalse, &kFalse, &kFalse, &kFalse,
   1042                                 &kFalse, &kFalse);
   1043   NavigateAndCommit(url);
   1044   WaitAndCheckPreClassificationChecks();
   1045   msg = process()->sink().GetFirstMessageMatching(
   1046       SafeBrowsingMsg_StartPhishingDetection::ID);
   1047   ASSERT_TRUE(msg);
   1048   SafeBrowsingMsg_StartPhishingDetection::Read(msg, &actual_url);
   1049   EXPECT_EQ(url, actual_url.a);
   1050   EXPECT_EQ(rvh()->GetRoutingID(), msg->routing_id());
   1051   process()->sink().ClearMessages();
   1052 
   1053   // If the mime type is not one that we support, no IPC should be triggered.
   1054   // Note: for this test to work correctly, the new URL must be on the
   1055   // same domain as the previous URL, otherwise it will create a new
   1056   // RenderViewHost that won't have the mime type set.
   1057   url = GURL("http://host2.com/image.jpg");
   1058   rvh_tester()->SetContentsMimeType("image/jpeg");
   1059   ExpectPreClassificationChecks(url, NULL, NULL, NULL, NULL, NULL, NULL);
   1060   NavigateAndCommit(url);
   1061   WaitAndCheckPreClassificationChecks();
   1062   msg = process()->sink().GetFirstMessageMatching(
   1063       SafeBrowsingMsg_StartPhishingDetection::ID);
   1064   ASSERT_FALSE(msg);
   1065 
   1066   // If IsPrivateIPAddress returns true, no IPC should be triggered.
   1067   url = GURL("http://host3.com/");
   1068   ExpectPreClassificationChecks(url, &kTrue, NULL, NULL, NULL, NULL, NULL);
   1069   NavigateAndCommit(url);
   1070   WaitAndCheckPreClassificationChecks();
   1071   msg = process()->sink().GetFirstMessageMatching(
   1072       SafeBrowsingMsg_StartPhishingDetection::ID);
   1073   ASSERT_FALSE(msg);
   1074 
   1075   // If the tab is incognito there should be no IPC.  Also, we shouldn't
   1076   // even check the csd-whitelist.
   1077   url = GURL("http://host4.com/");
   1078   ExpectPreClassificationChecks(url, &kFalse, &kTrue, NULL, NULL, NULL, NULL);
   1079   NavigateAndCommit(url);
   1080   WaitAndCheckPreClassificationChecks();
   1081   msg = process()->sink().GetFirstMessageMatching(
   1082       SafeBrowsingMsg_StartPhishingDetection::ID);
   1083   ASSERT_FALSE(msg);
   1084 
   1085   // If the URL is on the csd whitelist, no IPC should be triggered.
   1086   url = GURL("http://host5.com/");
   1087   ExpectPreClassificationChecks(url, &kFalse, &kFalse, &kTrue, NULL, NULL,
   1088                                 NULL);
   1089   NavigateAndCommit(url);
   1090   WaitAndCheckPreClassificationChecks();
   1091   msg = process()->sink().GetFirstMessageMatching(
   1092       SafeBrowsingMsg_StartPhishingDetection::ID);
   1093   ASSERT_FALSE(msg);
   1094 
   1095   // If item is in the cache but it isn't valid, we will classify regardless
   1096   // of whether we are over the reporting limit.
   1097   url = GURL("http://host6.com/");
   1098   ExpectPreClassificationChecks(url, &kFalse, &kFalse, &kFalse, &kFalse, &kTrue,
   1099                                 NULL);
   1100   NavigateAndCommit(url);
   1101   WaitAndCheckPreClassificationChecks();
   1102   msg = process()->sink().GetFirstMessageMatching(
   1103       SafeBrowsingMsg_StartPhishingDetection::ID);
   1104   ASSERT_TRUE(msg);
   1105   SafeBrowsingMsg_StartPhishingDetection::Read(msg, &actual_url);
   1106   EXPECT_EQ(url, actual_url.a);
   1107   EXPECT_EQ(rvh()->GetRoutingID(), msg->routing_id());
   1108   process()->sink().ClearMessages();
   1109 
   1110   // If the url isn't in the cache and we are over the reporting limit, we
   1111   // don't do classification.
   1112   url = GURL("http://host7.com/");
   1113   ExpectPreClassificationChecks(url, &kFalse, &kFalse, &kFalse, &kFalse,
   1114                                 &kFalse, &kTrue);
   1115   NavigateAndCommit(url);
   1116   WaitAndCheckPreClassificationChecks();
   1117   msg = process()->sink().GetFirstMessageMatching(
   1118       SafeBrowsingMsg_StartPhishingDetection::ID);
   1119   ASSERT_FALSE(msg);
   1120 
   1121   // If result is cached, we will try and display the blocking page directly
   1122   // with no start classification message.
   1123   url = GURL("http://host8.com/");
   1124   ExpectPreClassificationChecks(url, &kFalse, &kFalse, &kFalse, &kTrue, NULL,
   1125                                 NULL);
   1126 
   1127   UnsafeResource resource;
   1128   EXPECT_CALL(*ui_manager_.get(), DoDisplayBlockingPage(_))
   1129       .WillOnce(SaveArg<0>(&resource));
   1130 
   1131   NavigateAndCommit(url);
   1132   // Wait for CheckCsdWhitelist and CheckCache() to be called.
   1133   base::RunLoop().RunUntilIdle();
   1134   // Now we check that all expected functions were indeed called on the two
   1135   // service objects.
   1136   EXPECT_TRUE(Mock::VerifyAndClear(csd_host_.get()));
   1137   EXPECT_TRUE(Mock::VerifyAndClear(ui_manager_.get()));
   1138   EXPECT_EQ(url, resource.url);
   1139   EXPECT_EQ(url, resource.original_url);
   1140   resource.callback.Reset();
   1141   msg = process()->sink().GetFirstMessageMatching(
   1142       SafeBrowsingMsg_StartPhishingDetection::ID);
   1143   ASSERT_FALSE(msg);
   1144 }
   1145 }  // namespace safe_browsing
   1146