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/web_contents.h"
     23 #include "content/public/test/mock_render_process_host.h"
     24 #include "content/public/test/test_browser_thread.h"
     25 #include "content/public/test/test_renderer_host.h"
     26 #include "ipc/ipc_test_sink.h"
     27 #include "testing/gmock/include/gmock/gmock.h"
     28 #include "testing/gtest/include/gtest/gtest.h"
     29 #include "url/gurl.h"
     30 
     31 using ::testing::_;
     32 using ::testing::DeleteArg;
     33 using ::testing::DoAll;
     34 using ::testing::Eq;
     35 using ::testing::IsNull;
     36 using ::testing::Mock;
     37 using ::testing::NiceMock;
     38 using ::testing::NotNull;
     39 using ::testing::Pointee;
     40 using ::testing::Return;
     41 using ::testing::SaveArg;
     42 using ::testing::SetArgumentPointee;
     43 using ::testing::StrictMock;
     44 using content::BrowserThread;
     45 using content::RenderViewHostTester;
     46 using content::WebContents;
     47 
     48 namespace {
     49 const bool kFalse = false;
     50 const bool kTrue = true;
     51 }
     52 
     53 namespace safe_browsing {
     54 namespace {
     55 // This matcher verifies that the client computed verdict
     56 // (ClientPhishingRequest) which is passed to SendClientReportPhishingRequest
     57 // has the expected fields set.  Note: we can't simply compare the protocol
     58 // buffer strings because the BrowserFeatureExtractor might add features to the
     59 // verdict object before calling SendClientReportPhishingRequest.
     60 MATCHER_P(PartiallyEqualVerdict, other, "") {
     61   return (other.url() == arg.url() &&
     62           other.client_score() == arg.client_score() &&
     63           other.is_phishing() == arg.is_phishing());
     64 }
     65 
     66 MATCHER_P(PartiallyEqualMalwareVerdict, other, "") {
     67   return (other.url() == arg.url() &&
     68           other.feature_map_size() == arg.feature_map_size());
     69 }
     70 
     71 // Test that the callback is NULL when the verdict is not phishing.
     72 MATCHER(CallbackIsNull, "") {
     73   return arg.is_null();
     74 }
     75 
     76 ACTION(QuitUIMessageLoop) {
     77   EXPECT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::UI));
     78   base::MessageLoopForUI::current()->Quit();
     79 }
     80 
     81 // It's kind of insane that InvokeArgument doesn't work with callbacks, but it
     82 // doesn't seem like it.
     83 ACTION_TEMPLATE(InvokeCallbackArgument,
     84                 HAS_1_TEMPLATE_PARAMS(int, k),
     85                 AND_2_VALUE_PARAMS(p0, p1)) {
     86   ::std::tr1::get<k>(args).Run(p0, p1);
     87 }
     88 
     89 void EmptyUrlCheckCallback(bool processed) {
     90 }
     91 
     92 class MockClientSideDetectionService : public ClientSideDetectionService {
     93  public:
     94   MockClientSideDetectionService() : ClientSideDetectionService(NULL) {}
     95   virtual ~MockClientSideDetectionService() {};
     96 
     97   MOCK_METHOD2(SendClientReportPhishingRequest,
     98                void(ClientPhishingRequest*,
     99                     const ClientReportPhishingRequestCallback&));
    100   MOCK_METHOD2(SendClientReportMalwareRequest,
    101                void(ClientMalwareRequest*,
    102                     const ClientReportMalwareRequestCallback&));
    103   MOCK_CONST_METHOD1(IsPrivateIPAddress, bool(const std::string&));
    104   MOCK_METHOD2(GetValidCachedResult, bool(const GURL&, bool*));
    105   MOCK_METHOD1(IsInCache, bool(const GURL&));
    106   MOCK_METHOD0(OverPhishingReportLimit, bool());
    107 
    108  private:
    109   DISALLOW_COPY_AND_ASSIGN(MockClientSideDetectionService);
    110 };
    111 
    112 class MockSafeBrowsingUIManager : public SafeBrowsingUIManager {
    113  public:
    114   explicit MockSafeBrowsingUIManager(SafeBrowsingService* service)
    115       : SafeBrowsingUIManager(service) { }
    116 
    117   MOCK_METHOD1(DoDisplayBlockingPage, void(const UnsafeResource& resource));
    118 
    119   // Helper function which calls OnBlockingPageComplete for this client
    120   // object.
    121   void InvokeOnBlockingPageComplete(const UrlCheckCallback& callback) {
    122     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    123     DCHECK(!callback.is_null());
    124     // Note: this will delete the client object in the case of the CsdClient
    125     // implementation.
    126     callback.Run(false);
    127   }
    128 
    129  protected:
    130   virtual ~MockSafeBrowsingUIManager() { }
    131 
    132  private:
    133   DISALLOW_COPY_AND_ASSIGN(MockSafeBrowsingUIManager);
    134 };
    135 
    136 class MockSafeBrowsingDatabaseManager : public SafeBrowsingDatabaseManager {
    137  public:
    138   explicit MockSafeBrowsingDatabaseManager(SafeBrowsingService* service)
    139       : SafeBrowsingDatabaseManager(service) { }
    140 
    141   MOCK_METHOD1(MatchCsdWhitelistUrl, bool(const GURL&));
    142 
    143  protected:
    144   virtual ~MockSafeBrowsingDatabaseManager() {}
    145 
    146  private:
    147   DISALLOW_COPY_AND_ASSIGN(MockSafeBrowsingDatabaseManager);
    148 };
    149 
    150 class MockTestingProfile : public TestingProfile {
    151  public:
    152   MockTestingProfile() {}
    153   virtual ~MockTestingProfile() {}
    154 
    155   MOCK_CONST_METHOD0(IsOffTheRecord, bool());
    156 };
    157 
    158 class MockBrowserFeatureExtractor : public BrowserFeatureExtractor {
    159  public:
    160   explicit MockBrowserFeatureExtractor(
    161       WebContents* tab,
    162       ClientSideDetectionService* service)
    163       : BrowserFeatureExtractor(tab, service) {}
    164   virtual ~MockBrowserFeatureExtractor() {}
    165 
    166   MOCK_METHOD3(ExtractFeatures,
    167                void(const BrowseInfo* info,
    168                     ClientPhishingRequest*,
    169                     const BrowserFeatureExtractor::DoneCallback&));
    170 
    171   MOCK_METHOD2(ExtractMalwareFeatures,
    172                void(const BrowseInfo* info,
    173                     ClientMalwareRequest*));
    174 };
    175 
    176 }  // namespace
    177 
    178 class ClientSideDetectionHostTest : public ChromeRenderViewHostTestHarness {
    179  public:
    180   typedef SafeBrowsingUIManager::UnsafeResource UnsafeResource;
    181 
    182   virtual void SetUp() {
    183     ChromeRenderViewHostTestHarness::SetUp();
    184 
    185     // Inject service classes.
    186     csd_service_.reset(new StrictMock<MockClientSideDetectionService>());
    187     // Only used for initializing mock objects.
    188     SafeBrowsingService* sb_service =
    189         SafeBrowsingService::CreateSafeBrowsingService();
    190     database_manager_ =
    191         new StrictMock<MockSafeBrowsingDatabaseManager>(sb_service);
    192     ui_manager_ = new StrictMock<MockSafeBrowsingUIManager>(sb_service);
    193     csd_host_.reset(safe_browsing::ClientSideDetectionHost::Create(
    194         web_contents()));
    195     csd_host_->set_client_side_detection_service(csd_service_.get());
    196     csd_host_->set_safe_browsing_managers(ui_manager_.get(),
    197                                           database_manager_.get());
    198     // We need to create this here since we don't call
    199     // DidNavigateMainFramePostCommit in this test.
    200     csd_host_->browse_info_.reset(new BrowseInfo);
    201 
    202     // By default this is set to false. Turn it on as if we are in canary or
    203     // dev channel
    204     csd_host_->malware_report_enabled_ = true;
    205   }
    206 
    207   virtual void TearDown() {
    208     // Delete the host object on the UI thread and release the
    209     // SafeBrowsingService.
    210     BrowserThread::DeleteSoon(BrowserThread::UI, FROM_HERE,
    211                               csd_host_.release());
    212     database_manager_ = NULL;
    213     ui_manager_ = NULL;
    214     base::RunLoop().RunUntilIdle();
    215     ChromeRenderViewHostTestHarness::TearDown();
    216   }
    217 
    218   virtual content::BrowserContext* CreateBrowserContext() OVERRIDE {
    219     // Set custom profile object so that we can mock calls to IsOffTheRecord.
    220     // This needs to happen before we call the parent SetUp() function.  We use
    221     // a nice mock because other parts of the code are calling IsOffTheRecord.
    222     mock_profile_ = new NiceMock<MockTestingProfile>();
    223     return mock_profile_;
    224   }
    225 
    226   void OnPhishingDetectionDone(const std::string& verdict_str) {
    227     csd_host_->OnPhishingDetectionDone(verdict_str);
    228   }
    229 
    230   void UpdateIPUrlMap(const std::string& ip, const std::string& host) {
    231     csd_host_->UpdateIPUrlMap(ip, host);
    232   }
    233 
    234   BrowseInfo* GetBrowseInfo() {
    235     return csd_host_->browse_info_.get();
    236   }
    237 
    238   void ExpectPreClassificationChecks(const GURL& url,
    239                                      const bool* is_private,
    240                                      const bool* is_incognito,
    241                                      const bool* match_csd_whitelist,
    242                                      const bool* get_valid_cached_result,
    243                                      const bool* is_in_cache,
    244                                      const bool* over_report_limit) {
    245     if (is_private) {
    246       EXPECT_CALL(*csd_service_, IsPrivateIPAddress(_))
    247           .WillOnce(Return(*is_private));
    248     }
    249     if (is_incognito) {
    250       EXPECT_CALL(*mock_profile_, IsOffTheRecord())
    251           .WillRepeatedly(Return(*is_incognito));
    252     }
    253     if (match_csd_whitelist) {
    254       EXPECT_CALL(*database_manager_.get(), MatchCsdWhitelistUrl(url))
    255           .WillOnce(Return(*match_csd_whitelist));
    256     }
    257     if (get_valid_cached_result) {
    258       EXPECT_CALL(*csd_service_, GetValidCachedResult(url, NotNull()))
    259           .WillOnce(DoAll(SetArgumentPointee<1>(true),
    260                           Return(*get_valid_cached_result)));
    261     }
    262     if (is_in_cache) {
    263       EXPECT_CALL(*csd_service_, IsInCache(url)).WillOnce(Return(*is_in_cache));
    264     }
    265     if (over_report_limit) {
    266       EXPECT_CALL(*csd_service_, OverPhishingReportLimit())
    267           .WillOnce(Return(*over_report_limit));
    268     }
    269   }
    270 
    271   void WaitAndCheckPreClassificationChecks() {
    272     // Wait for CheckCsdWhitelist and CheckCache() to be called if at all.
    273     base::RunLoop().RunUntilIdle();
    274     EXPECT_TRUE(Mock::VerifyAndClear(csd_service_.get()));
    275     EXPECT_TRUE(Mock::VerifyAndClear(ui_manager_.get()));
    276     EXPECT_TRUE(Mock::VerifyAndClear(database_manager_.get()));
    277     EXPECT_TRUE(Mock::VerifyAndClear(mock_profile_));
    278   }
    279 
    280   void SetFeatureExtractor(BrowserFeatureExtractor* extractor) {
    281     csd_host_->feature_extractor_.reset(extractor);
    282   }
    283 
    284   void SetRedirectChain(const std::vector<GURL>& redirect_chain) {
    285     csd_host_->browse_info_->url_redirects = redirect_chain;
    286   }
    287 
    288   void SetUnsafeResourceToCurrent() {
    289     UnsafeResource resource;
    290     resource.url = GURL("http://www.malware.com/");
    291     resource.original_url = web_contents()->GetURL();
    292     resource.is_subresource = true;
    293     resource.threat_type = SB_THREAT_TYPE_URL_MALWARE;
    294     resource.callback = base::Bind(&EmptyUrlCheckCallback);
    295     resource.render_process_host_id = web_contents()->GetRenderProcessHost()->
    296         GetID();
    297     resource.render_view_id =
    298         web_contents()->GetRenderViewHost()->GetRoutingID();
    299     csd_host_->OnSafeBrowsingHit(resource);
    300     resource.callback.Reset();
    301     ASSERT_TRUE(csd_host_->DidShowSBInterstitial());
    302     ASSERT_TRUE(csd_host_->unsafe_resource_.get());
    303     // Test that the resource above was copied.
    304     EXPECT_EQ(resource.url, csd_host_->unsafe_resource_->url);
    305     EXPECT_EQ(resource.original_url, csd_host_->unsafe_resource_->original_url);
    306     EXPECT_EQ(resource.is_subresource,
    307               csd_host_->unsafe_resource_->is_subresource);
    308     EXPECT_EQ(resource.threat_type, csd_host_->unsafe_resource_->threat_type);
    309     EXPECT_TRUE(csd_host_->unsafe_resource_->callback.is_null());
    310     EXPECT_EQ(resource.render_process_host_id,
    311               csd_host_->unsafe_resource_->render_process_host_id);
    312     EXPECT_EQ(resource.render_view_id,
    313               csd_host_->unsafe_resource_->render_view_id);
    314   }
    315 
    316  protected:
    317   scoped_ptr<ClientSideDetectionHost> csd_host_;
    318   scoped_ptr<StrictMock<MockClientSideDetectionService> > csd_service_;
    319   scoped_refptr<StrictMock<MockSafeBrowsingUIManager> > ui_manager_;
    320   scoped_refptr<StrictMock<MockSafeBrowsingDatabaseManager> > database_manager_;
    321   MockTestingProfile* mock_profile_;  // We don't own this object
    322 };
    323 
    324 
    325 TEST_F(ClientSideDetectionHostTest, OnPhishingDetectionDoneInvalidVerdict) {
    326   // Case 0: renderer sends an invalid verdict string that we're unable to
    327   // parse.
    328   MockBrowserFeatureExtractor* mock_extractor = new MockBrowserFeatureExtractor(
    329       web_contents(),
    330       csd_service_.get());
    331   SetFeatureExtractor(mock_extractor);  // The host class takes ownership.
    332   EXPECT_CALL(*mock_extractor, ExtractFeatures(_, _, _)).Times(0);
    333   OnPhishingDetectionDone("Invalid Protocol Buffer");
    334   EXPECT_TRUE(Mock::VerifyAndClear(mock_extractor));
    335 }
    336 
    337 TEST_F(ClientSideDetectionHostTest, OnPhishingDetectionDoneNotPhishing) {
    338   // Case 1: client thinks the page is phishing.  The server does not agree.
    339   // No interstitial is shown.
    340   MockBrowserFeatureExtractor* mock_extractor = new MockBrowserFeatureExtractor(
    341       web_contents(),
    342       csd_service_.get());
    343   SetFeatureExtractor(mock_extractor);  // The host class takes ownership.
    344 
    345   ClientSideDetectionService::ClientReportPhishingRequestCallback cb;
    346   ClientPhishingRequest verdict;
    347   verdict.set_url("http://phishingurl.com/");
    348   verdict.set_client_score(1.0f);
    349   verdict.set_is_phishing(true);
    350 
    351   EXPECT_CALL(*mock_extractor, ExtractFeatures(_, _, _))
    352       .WillOnce(DoAll(DeleteArg<1>(),
    353                       InvokeCallbackArgument<2>(true, &verdict)));
    354   EXPECT_CALL(*csd_service_,
    355               SendClientReportPhishingRequest(
    356                   Pointee(PartiallyEqualVerdict(verdict)), _))
    357       .WillOnce(SaveArg<1>(&cb));
    358   OnPhishingDetectionDone(verdict.SerializeAsString());
    359   EXPECT_TRUE(Mock::VerifyAndClear(csd_service_.get()));
    360   ASSERT_FALSE(cb.is_null());
    361 
    362   // Make sure DoDisplayBlockingPage is not going to be called.
    363   EXPECT_CALL(*ui_manager_.get(), DoDisplayBlockingPage(_)).Times(0);
    364   cb.Run(GURL(verdict.url()), false);
    365   base::RunLoop().RunUntilIdle();
    366   EXPECT_TRUE(Mock::VerifyAndClear(ui_manager_.get()));
    367 }
    368 
    369 TEST_F(ClientSideDetectionHostTest, OnPhishingDetectionDoneDisabled) {
    370   // Case 2: client thinks the page is phishing and so does the server but
    371   // showing the interstitial is disabled => no interstitial is shown.
    372   MockBrowserFeatureExtractor* mock_extractor = new MockBrowserFeatureExtractor(
    373       web_contents(),
    374       csd_service_.get());
    375   SetFeatureExtractor(mock_extractor);  // The host class takes ownership.
    376 
    377   ClientSideDetectionService::ClientReportPhishingRequestCallback cb;
    378   ClientPhishingRequest verdict;
    379   verdict.set_url("http://phishingurl.com/");
    380   verdict.set_client_score(1.0f);
    381   verdict.set_is_phishing(true);
    382 
    383   EXPECT_CALL(*mock_extractor, ExtractFeatures(_, _, _))
    384       .WillOnce(DoAll(DeleteArg<1>(),
    385                       InvokeCallbackArgument<2>(true, &verdict)));
    386   EXPECT_CALL(*csd_service_,
    387               SendClientReportPhishingRequest(
    388                   Pointee(PartiallyEqualVerdict(verdict)), _))
    389       .WillOnce(SaveArg<1>(&cb));
    390   OnPhishingDetectionDone(verdict.SerializeAsString());
    391   EXPECT_TRUE(Mock::VerifyAndClear(csd_service_.get()));
    392   ASSERT_FALSE(cb.is_null());
    393 
    394   // Make sure DoDisplayBlockingPage is not going to be called.
    395   EXPECT_CALL(*ui_manager_.get(), DoDisplayBlockingPage(_)).Times(0);
    396   cb.Run(GURL(verdict.url()), false);
    397   base::RunLoop().RunUntilIdle();
    398   EXPECT_TRUE(Mock::VerifyAndClear(ui_manager_.get()));
    399 }
    400 
    401 TEST_F(ClientSideDetectionHostTest, OnPhishingDetectionDoneShowInterstitial) {
    402   // Case 3: client thinks the page is phishing and so does the server.
    403   // We show an interstitial.
    404   MockBrowserFeatureExtractor* mock_extractor = new MockBrowserFeatureExtractor(
    405       web_contents(),
    406       csd_service_.get());
    407   SetFeatureExtractor(mock_extractor);  // The host class takes ownership.
    408 
    409   ClientSideDetectionService::ClientReportPhishingRequestCallback cb;
    410   GURL phishing_url("http://phishingurl.com/");
    411   ClientPhishingRequest verdict;
    412   verdict.set_url(phishing_url.spec());
    413   verdict.set_client_score(1.0f);
    414   verdict.set_is_phishing(true);
    415 
    416   EXPECT_CALL(*mock_extractor, ExtractFeatures(_, _, _))
    417       .WillOnce(DoAll(DeleteArg<1>(),
    418                       InvokeCallbackArgument<2>(true, &verdict)));
    419   EXPECT_CALL(*csd_service_,
    420               SendClientReportPhishingRequest(
    421                   Pointee(PartiallyEqualVerdict(verdict)), _))
    422       .WillOnce(SaveArg<1>(&cb));
    423   OnPhishingDetectionDone(verdict.SerializeAsString());
    424   EXPECT_TRUE(Mock::VerifyAndClear(csd_service_.get()));
    425   ASSERT_FALSE(cb.is_null());
    426 
    427   UnsafeResource resource;
    428   EXPECT_CALL(*ui_manager_.get(), DoDisplayBlockingPage(_))
    429       .WillOnce(SaveArg<0>(&resource));
    430   cb.Run(phishing_url, true);
    431 
    432   base::RunLoop().RunUntilIdle();
    433   EXPECT_TRUE(Mock::VerifyAndClear(ui_manager_.get()));
    434   EXPECT_EQ(phishing_url, resource.url);
    435   EXPECT_EQ(phishing_url, resource.original_url);
    436   EXPECT_FALSE(resource.is_subresource);
    437   EXPECT_EQ(SB_THREAT_TYPE_CLIENT_SIDE_PHISHING_URL, resource.threat_type);
    438   EXPECT_EQ(web_contents()->GetRenderProcessHost()->GetID(),
    439             resource.render_process_host_id);
    440   EXPECT_EQ(web_contents()->GetRenderViewHost()->GetRoutingID(),
    441             resource.render_view_id);
    442 
    443   // Make sure the client object will be deleted.
    444   BrowserThread::PostTask(
    445       BrowserThread::IO,
    446       FROM_HERE,
    447       base::Bind(&MockSafeBrowsingUIManager::InvokeOnBlockingPageComplete,
    448                  ui_manager_, resource.callback));
    449 }
    450 
    451 TEST_F(ClientSideDetectionHostTest, OnPhishingDetectionDoneMultiplePings) {
    452   // Case 4 & 5: client thinks a page is phishing then navigates to
    453   // another page which is also considered phishing by the client
    454   // before the server responds with a verdict.  After a while the
    455   // server responds for both requests with a phishing verdict.  Only
    456   // a single interstitial is shown for the second URL.
    457   MockBrowserFeatureExtractor* mock_extractor = new MockBrowserFeatureExtractor(
    458       web_contents(),
    459       csd_service_.get());
    460   SetFeatureExtractor(mock_extractor);  // The host class takes ownership.
    461 
    462   ClientSideDetectionService::ClientReportPhishingRequestCallback cb;
    463   GURL phishing_url("http://phishingurl.com/");
    464   ClientPhishingRequest verdict;
    465   verdict.set_url(phishing_url.spec());
    466   verdict.set_client_score(1.0f);
    467   verdict.set_is_phishing(true);
    468 
    469   EXPECT_CALL(*mock_extractor, ExtractFeatures(_, _, _))
    470       .WillOnce(DoAll(DeleteArg<1>(),
    471                       InvokeCallbackArgument<2>(true, &verdict)));
    472   EXPECT_CALL(*csd_service_,
    473               SendClientReportPhishingRequest(
    474                   Pointee(PartiallyEqualVerdict(verdict)), _))
    475       .WillOnce(SaveArg<1>(&cb));
    476   OnPhishingDetectionDone(verdict.SerializeAsString());
    477   EXPECT_TRUE(Mock::VerifyAndClear(csd_service_.get()));
    478   ASSERT_FALSE(cb.is_null());
    479 
    480   // Set this back to a normal browser feature extractor since we're using
    481   // NavigateAndCommit() and it's easier to use the real thing than setting up
    482   // mock expectations.
    483   SetFeatureExtractor(new BrowserFeatureExtractor(web_contents(),
    484                                                   csd_service_.get()));
    485   GURL other_phishing_url("http://other_phishing_url.com/bla");
    486   ExpectPreClassificationChecks(other_phishing_url, &kFalse, &kFalse, &kFalse,
    487                                 &kFalse, &kFalse, &kFalse);
    488   // We navigate away.  The callback cb should be revoked.
    489   NavigateAndCommit(other_phishing_url);
    490   // Wait for the pre-classification checks to finish for other_phishing_url.
    491   WaitAndCheckPreClassificationChecks();
    492 
    493   ClientSideDetectionService::ClientReportPhishingRequestCallback cb_other;
    494   verdict.set_url(other_phishing_url.spec());
    495   verdict.set_client_score(0.8f);
    496   EXPECT_CALL(*csd_service_,
    497               SendClientReportPhishingRequest(
    498                   Pointee(PartiallyEqualVerdict(verdict)), _))
    499       .WillOnce(DoAll(DeleteArg<0>(),
    500                       SaveArg<1>(&cb_other),
    501                       QuitUIMessageLoop()));
    502   std::vector<GURL> redirect_chain;
    503   redirect_chain.push_back(other_phishing_url);
    504   SetRedirectChain(redirect_chain);
    505   OnPhishingDetectionDone(verdict.SerializeAsString());
    506   base::MessageLoop::current()->Run();
    507   EXPECT_TRUE(Mock::VerifyAndClear(csd_service_.get()));
    508   ASSERT_FALSE(cb_other.is_null());
    509 
    510   // We expect that the interstitial is shown for the second phishing URL and
    511   // not for the first phishing URL.
    512   UnsafeResource resource;
    513   EXPECT_CALL(*ui_manager_.get(), DoDisplayBlockingPage(_))
    514       .WillOnce(SaveArg<0>(&resource));
    515 
    516   cb.Run(phishing_url, true);  // Should have no effect.
    517   cb_other.Run(other_phishing_url, true);  // Should show interstitial.
    518 
    519   base::RunLoop().RunUntilIdle();
    520   EXPECT_TRUE(Mock::VerifyAndClear(ui_manager_.get()));
    521   EXPECT_EQ(other_phishing_url, resource.url);
    522   EXPECT_EQ(other_phishing_url, resource.original_url);
    523   EXPECT_FALSE(resource.is_subresource);
    524   EXPECT_EQ(SB_THREAT_TYPE_CLIENT_SIDE_PHISHING_URL, resource.threat_type);
    525   EXPECT_EQ(web_contents()->GetRenderProcessHost()->GetID(),
    526             resource.render_process_host_id);
    527   EXPECT_EQ(web_contents()->GetRenderViewHost()->GetRoutingID(),
    528             resource.render_view_id);
    529 
    530   // Make sure the client object will be deleted.
    531   BrowserThread::PostTask(
    532       BrowserThread::IO,
    533       FROM_HERE,
    534       base::Bind(&MockSafeBrowsingUIManager::InvokeOnBlockingPageComplete,
    535                  ui_manager_, resource.callback));
    536 }
    537 
    538 TEST_F(ClientSideDetectionHostTest,
    539        OnPhishingDetectionDoneVerdictNotPhishing) {
    540   // Case 6: renderer sends a verdict string that isn't phishing.
    541   MockBrowserFeatureExtractor* mock_extractor = new MockBrowserFeatureExtractor(
    542       web_contents(),
    543       csd_service_.get());
    544   SetFeatureExtractor(mock_extractor);  // The host class takes ownership.
    545 
    546   ClientPhishingRequest verdict;
    547   verdict.set_url("http://not-phishing.com/");
    548   verdict.set_client_score(0.1f);
    549   verdict.set_is_phishing(false);
    550 
    551   EXPECT_CALL(*mock_extractor, ExtractFeatures(_, _, _)).Times(0);
    552   OnPhishingDetectionDone(verdict.SerializeAsString());
    553   EXPECT_TRUE(Mock::VerifyAndClear(mock_extractor));
    554 }
    555 
    556 TEST_F(ClientSideDetectionHostTest,
    557        OnPhishingDetectionDoneVerdictNotPhishingButSBMatch) {
    558   // Case 7: renderer sends a verdict string that isn't phishing but the URL
    559   // was on the regular phishing or malware lists.
    560   GURL url("http://not-phishing.com/");
    561   ClientPhishingRequest verdict;
    562   verdict.set_url(url.spec());
    563   verdict.set_client_score(0.1f);
    564   verdict.set_is_phishing(false);
    565 
    566   // First we have to navigate to the URL to set the unique page ID.
    567   ExpectPreClassificationChecks(url, &kFalse, &kFalse, &kFalse, &kFalse,
    568                                 &kFalse, &kFalse);
    569   NavigateAndCommit(url);
    570   WaitAndCheckPreClassificationChecks();
    571   SetUnsafeResourceToCurrent();
    572 
    573   EXPECT_CALL(*csd_service_,
    574               SendClientReportPhishingRequest(
    575                   Pointee(PartiallyEqualVerdict(verdict)), CallbackIsNull()))
    576       .WillOnce(DoAll(DeleteArg<0>(), QuitUIMessageLoop()));
    577   std::vector<GURL> redirect_chain;
    578   redirect_chain.push_back(url);
    579   SetRedirectChain(redirect_chain);
    580   OnPhishingDetectionDone(verdict.SerializeAsString());
    581   base::MessageLoop::current()->Run();
    582   EXPECT_TRUE(Mock::VerifyAndClear(csd_service_.get()));
    583 }
    584 
    585 TEST_F(ClientSideDetectionHostTest, UpdateIPUrlMap) {
    586   BrowseInfo* browse_info = GetBrowseInfo();
    587 
    588   // Empty IP or host are skipped
    589   UpdateIPUrlMap("250.10.10.10", std::string());
    590   ASSERT_EQ(0U, browse_info->ips.size());
    591   UpdateIPUrlMap(std::string(), "http://google.com/a");
    592   ASSERT_EQ(0U, browse_info->ips.size());
    593   UpdateIPUrlMap(std::string(), std::string());
    594   ASSERT_EQ(0U, browse_info->ips.size());
    595 
    596   std::set<std::string> expected_urls;
    597   for (int i = 0; i < 20; i++) {
    598     std::string url = base::StringPrintf("http://%d.com/", i);
    599     expected_urls.insert(url);
    600     UpdateIPUrlMap("250.10.10.10", url);
    601   }
    602   ASSERT_EQ(1U, browse_info->ips.size());
    603   ASSERT_EQ(20U, browse_info->ips["250.10.10.10"].size());
    604   EXPECT_EQ(expected_urls, browse_info->ips["250.10.10.10"]);
    605 
    606   // Add more urls for this ip, it exceeds max limit and won't be added
    607   UpdateIPUrlMap("250.10.10.10", "http://21.com/");
    608   ASSERT_EQ(1U, browse_info->ips.size());
    609   ASSERT_EQ(20U, browse_info->ips["250.10.10.10"].size());
    610   EXPECT_EQ(expected_urls, browse_info->ips["250.10.10.10"]);
    611 
    612   // Add 199 more IPs
    613   for (int i = 0; i < 199; i++) {
    614     std::string ip = base::StringPrintf("%d.%d.%d.256", i, i, i);
    615     expected_urls.clear();
    616     expected_urls.insert("test.com/");
    617     UpdateIPUrlMap(ip, "test.com/");
    618     ASSERT_EQ(1U, browse_info->ips[ip].size());
    619     EXPECT_EQ(expected_urls, browse_info->ips[ip]);
    620   }
    621   ASSERT_EQ(200U, browse_info->ips.size());
    622 
    623   // Exceeding max ip limit 200, these won't be added
    624   UpdateIPUrlMap("250.250.250.250", "goo.com/");
    625   UpdateIPUrlMap("250.250.250.250", "bar.com/");
    626   UpdateIPUrlMap("250.250.0.250", "foo.com/");
    627   ASSERT_EQ(200U, browse_info->ips.size());
    628 
    629   // Add url to existing IPs succeed
    630   UpdateIPUrlMap("100.100.100.256", "more.com/");
    631   ASSERT_EQ(2U, browse_info->ips["100.100.100.256"].size());
    632   expected_urls.clear();
    633   expected_urls.insert("test.com/");
    634   expected_urls.insert("more.com/");
    635   EXPECT_EQ(expected_urls, browse_info->ips["100.100.100.256"]);
    636 }
    637 
    638 TEST_F(ClientSideDetectionHostTest,
    639        OnPhishingDetectionDoneVerdictNotPhishingNotMalwareIP) {
    640   // Case 7: renderer sends a verdict string that isn't phishing and not matches
    641   // malware bad IP list
    642   MockBrowserFeatureExtractor* mock_extractor = new MockBrowserFeatureExtractor(
    643       web_contents(),
    644       csd_service_.get());
    645   SetFeatureExtractor(mock_extractor);  // The host class takes ownership.
    646 
    647   ClientPhishingRequest verdict;
    648   verdict.set_url("http://not-phishing.com/");
    649   verdict.set_client_score(0.1f);
    650   verdict.set_is_phishing(false);
    651 
    652   ClientMalwareRequest malware_verdict;
    653   malware_verdict.set_url("http://not-phishing.com/");
    654 
    655   EXPECT_CALL(*mock_extractor, ExtractMalwareFeatures(_, _))
    656       .WillOnce(SetArgumentPointee<1>(malware_verdict));
    657   EXPECT_CALL(*csd_service_,
    658               SendClientReportMalwareRequest(_, _)).Times(0);
    659   EXPECT_CALL(*mock_extractor, ExtractFeatures(_, _, _)).Times(0);
    660 
    661   OnPhishingDetectionDone(verdict.SerializeAsString());
    662   EXPECT_TRUE(Mock::VerifyAndClear(mock_extractor));
    663 }
    664 
    665 TEST_F(ClientSideDetectionHostTest,
    666        OnPhishingDetectionDoneVerdictNotPhishingButMalwareIP) {
    667   // Case 8: renderer sends a verdict string that isn't phishing but matches
    668   // malware bad IP list
    669   MockBrowserFeatureExtractor* mock_extractor = new MockBrowserFeatureExtractor(
    670       web_contents(),
    671       csd_service_.get());
    672   SetFeatureExtractor(mock_extractor);  // The host class takes ownership.
    673 
    674   ClientPhishingRequest verdict;
    675   verdict.set_url("http://not-phishing.com/");
    676   verdict.set_client_score(0.1f);
    677   verdict.set_is_phishing(false);
    678 
    679   ClientMalwareRequest malware_verdict;
    680   malware_verdict.set_url("http://not-phishing.com/");
    681   ClientMalwareRequest::Feature* feature = malware_verdict.add_feature_map();
    682   feature->set_name("malwareip1.2.3.4");
    683   feature->set_value(1.0);
    684   feature->add_metainfo("badip.com");
    685 
    686   EXPECT_CALL(*mock_extractor, ExtractMalwareFeatures(_, _))
    687       .WillOnce(SetArgumentPointee<1>(malware_verdict));
    688   EXPECT_CALL(*csd_service_,
    689               SendClientReportMalwareRequest(
    690                   Pointee(PartiallyEqualMalwareVerdict(malware_verdict)), _))
    691       .WillOnce(DeleteArg<0>());
    692   EXPECT_CALL(*mock_extractor, ExtractFeatures(_, _, _)).Times(0);
    693 
    694   OnPhishingDetectionDone(verdict.SerializeAsString());
    695   EXPECT_TRUE(Mock::VerifyAndClear(mock_extractor));
    696 }
    697 
    698 TEST_F(ClientSideDetectionHostTest,
    699        OnPhishingDetectionDoneVerdictPhishingAndMalwareIP) {
    700   // Case 9: renderer sends a verdict string that is phishing and matches
    701   // malware bad IP list
    702   MockBrowserFeatureExtractor* mock_extractor = new MockBrowserFeatureExtractor(
    703       web_contents(),
    704       csd_service_.get());
    705   SetFeatureExtractor(mock_extractor);  // The host class takes ownership.
    706 
    707   ClientSideDetectionService::ClientReportPhishingRequestCallback cb;
    708   ClientPhishingRequest verdict;
    709   verdict.set_url("http://not-phishing.com/");
    710   verdict.set_client_score(0.1f);
    711   verdict.set_is_phishing(true);
    712 
    713   ClientMalwareRequest malware_verdict;
    714   malware_verdict.set_url("http://not-phishing.com/");
    715   ClientMalwareRequest::Feature* feature = malware_verdict.add_feature_map();
    716   feature->set_name("malwareip1.2.3.4");
    717   feature->set_value(1.0);
    718   feature->add_metainfo("badip.com");
    719 
    720   EXPECT_CALL(*mock_extractor, ExtractMalwareFeatures(_, _))
    721       .WillOnce(SetArgumentPointee<1>(malware_verdict));
    722   EXPECT_CALL(*csd_service_,
    723               SendClientReportMalwareRequest(
    724                   Pointee(PartiallyEqualMalwareVerdict(malware_verdict)), _))
    725       .WillOnce(DeleteArg<0>());
    726 
    727   EXPECT_CALL(*mock_extractor, ExtractFeatures(_, _, _))
    728       .WillOnce(DoAll(DeleteArg<1>(),
    729                       InvokeCallbackArgument<2>(true, &verdict)));
    730 
    731   EXPECT_CALL(*csd_service_,
    732               SendClientReportPhishingRequest(
    733                   Pointee(PartiallyEqualVerdict(verdict)), _))
    734       .WillOnce(SaveArg<1>(&cb));
    735   OnPhishingDetectionDone(verdict.SerializeAsString());
    736   EXPECT_TRUE(Mock::VerifyAndClear(mock_extractor));
    737   EXPECT_TRUE(Mock::VerifyAndClear(csd_service_.get()));
    738   ASSERT_FALSE(cb.is_null());
    739 }
    740 
    741 TEST_F(ClientSideDetectionHostTest, NavigationCancelsShouldClassifyUrl) {
    742   // Test that canceling pending should classify requests works as expected.
    743 
    744   GURL first_url("http://first.phishy.url.com");
    745   GURL second_url("http://second.url.com/");
    746   // The first few checks are done synchronously so check that they have been
    747   // done for the first URL, while the second URL has all the checks done.  We
    748   // need to manually set up the IsPrivateIPAddress mock since if the same mock
    749   // expectation is specified twice, gmock will only use the last instance of
    750   // it, meaning the first will never be matched.
    751   EXPECT_CALL(*csd_service_, IsPrivateIPAddress(_))
    752       .WillOnce(Return(false))
    753       .WillOnce(Return(false));
    754   ExpectPreClassificationChecks(first_url, NULL, &kFalse, &kFalse, NULL,
    755                                 NULL, NULL);
    756   ExpectPreClassificationChecks(second_url, NULL, &kFalse, &kFalse, &kFalse,
    757                                 &kFalse, &kFalse);
    758 
    759   NavigateAndCommit(first_url);
    760   // Don't flush the message loop, as we want to navigate to a different
    761   // url before the final pre-classification checks are run.
    762   NavigateAndCommit(second_url);
    763   WaitAndCheckPreClassificationChecks();
    764 }
    765 
    766 TEST_F(ClientSideDetectionHostTest, ShouldClassifyUrl) {
    767   // Navigate the tab to a page.  We should see a StartPhishingDetection IPC.
    768   GURL url("http://host.com/");
    769   ExpectPreClassificationChecks(url, &kFalse, &kFalse, &kFalse, &kFalse,
    770                                 &kFalse, &kFalse);
    771   NavigateAndCommit(url);
    772   WaitAndCheckPreClassificationChecks();
    773 
    774   const IPC::Message* msg = process()->sink().GetFirstMessageMatching(
    775       SafeBrowsingMsg_StartPhishingDetection::ID);
    776   ASSERT_TRUE(msg);
    777   Tuple1<GURL> actual_url;
    778   SafeBrowsingMsg_StartPhishingDetection::Read(msg, &actual_url);
    779   EXPECT_EQ(url, actual_url.a);
    780   EXPECT_EQ(rvh()->GetRoutingID(), msg->routing_id());
    781   process()->sink().ClearMessages();
    782 
    783   // Now try an in-page navigation.  This should not trigger an IPC.
    784   EXPECT_CALL(*csd_service_, IsPrivateIPAddress(_)).Times(0);
    785   url = GURL("http://host.com/#foo");
    786   ExpectPreClassificationChecks(url, NULL, NULL, NULL, NULL, NULL, NULL);
    787   NavigateAndCommit(url);
    788   WaitAndCheckPreClassificationChecks();
    789 
    790   msg = process()->sink().GetFirstMessageMatching(
    791       SafeBrowsingMsg_StartPhishingDetection::ID);
    792   ASSERT_FALSE(msg);
    793 
    794   // Check that XHTML is supported, in addition to the default HTML type.
    795   // Note: for this test to work correctly, the new URL must be on the
    796   // same domain as the previous URL, otherwise it will create a new
    797   // RenderViewHost that won't have the mime type set.
    798   url = GURL("http://host.com/xhtml");
    799   rvh_tester()->SetContentsMimeType("application/xhtml+xml");
    800   ExpectPreClassificationChecks(url, &kFalse, &kFalse, &kFalse, &kFalse,
    801                                 &kFalse, &kFalse);
    802   NavigateAndCommit(url);
    803   WaitAndCheckPreClassificationChecks();
    804   msg = process()->sink().GetFirstMessageMatching(
    805       SafeBrowsingMsg_StartPhishingDetection::ID);
    806   ASSERT_TRUE(msg);
    807   SafeBrowsingMsg_StartPhishingDetection::Read(msg, &actual_url);
    808   EXPECT_EQ(url, actual_url.a);
    809   EXPECT_EQ(rvh()->GetRoutingID(), msg->routing_id());
    810   process()->sink().ClearMessages();
    811 
    812   // Navigate to a new host, which should cause another IPC.
    813   url = GURL("http://host2.com/");
    814   ExpectPreClassificationChecks(url, &kFalse, &kFalse, &kFalse, &kFalse,
    815                                 &kFalse, &kFalse);
    816   NavigateAndCommit(url);
    817   WaitAndCheckPreClassificationChecks();
    818   msg = process()->sink().GetFirstMessageMatching(
    819       SafeBrowsingMsg_StartPhishingDetection::ID);
    820   ASSERT_TRUE(msg);
    821   SafeBrowsingMsg_StartPhishingDetection::Read(msg, &actual_url);
    822   EXPECT_EQ(url, actual_url.a);
    823   EXPECT_EQ(rvh()->GetRoutingID(), msg->routing_id());
    824   process()->sink().ClearMessages();
    825 
    826   // If the mime type is not one that we support, no IPC should be triggered.
    827   // Note: for this test to work correctly, the new URL must be on the
    828   // same domain as the previous URL, otherwise it will create a new
    829   // RenderViewHost that won't have the mime type set.
    830   url = GURL("http://host2.com/image.jpg");
    831   rvh_tester()->SetContentsMimeType("image/jpeg");
    832   ExpectPreClassificationChecks(url, NULL, NULL, NULL, NULL, NULL, NULL);
    833   NavigateAndCommit(url);
    834   WaitAndCheckPreClassificationChecks();
    835   msg = process()->sink().GetFirstMessageMatching(
    836       SafeBrowsingMsg_StartPhishingDetection::ID);
    837   ASSERT_FALSE(msg);
    838 
    839   // If IsPrivateIPAddress returns true, no IPC should be triggered.
    840   url = GURL("http://host3.com/");
    841   ExpectPreClassificationChecks(url, &kTrue, NULL, NULL, NULL, NULL, NULL);
    842   NavigateAndCommit(url);
    843   WaitAndCheckPreClassificationChecks();
    844   msg = process()->sink().GetFirstMessageMatching(
    845       SafeBrowsingMsg_StartPhishingDetection::ID);
    846   ASSERT_FALSE(msg);
    847 
    848   // If the tab is incognito there should be no IPC.  Also, we shouldn't
    849   // even check the csd-whitelist.
    850   url = GURL("http://host4.com/");
    851   ExpectPreClassificationChecks(url, &kFalse, &kTrue, NULL, NULL, NULL, NULL);
    852   NavigateAndCommit(url);
    853   WaitAndCheckPreClassificationChecks();
    854   msg = process()->sink().GetFirstMessageMatching(
    855       SafeBrowsingMsg_StartPhishingDetection::ID);
    856   ASSERT_FALSE(msg);
    857 
    858   // If the URL is on the csd whitelist, no IPC should be triggered.
    859   url = GURL("http://host5.com/");
    860   ExpectPreClassificationChecks(url, &kFalse, &kFalse, &kTrue, NULL, NULL,
    861                                 NULL);
    862   NavigateAndCommit(url);
    863   WaitAndCheckPreClassificationChecks();
    864   msg = process()->sink().GetFirstMessageMatching(
    865       SafeBrowsingMsg_StartPhishingDetection::ID);
    866   ASSERT_FALSE(msg);
    867 
    868   // If item is in the cache but it isn't valid, we will classify regardless
    869   // of whether we are over the reporting limit.
    870   url = GURL("http://host6.com/");
    871   ExpectPreClassificationChecks(url, &kFalse, &kFalse, &kFalse, &kFalse, &kTrue,
    872                                 NULL);
    873   NavigateAndCommit(url);
    874   WaitAndCheckPreClassificationChecks();
    875   msg = process()->sink().GetFirstMessageMatching(
    876       SafeBrowsingMsg_StartPhishingDetection::ID);
    877   ASSERT_TRUE(msg);
    878   SafeBrowsingMsg_StartPhishingDetection::Read(msg, &actual_url);
    879   EXPECT_EQ(url, actual_url.a);
    880   EXPECT_EQ(rvh()->GetRoutingID(), msg->routing_id());
    881   process()->sink().ClearMessages();
    882 
    883   // If the url isn't in the cache and we are over the reporting limit, we
    884   // don't do classification.
    885   url = GURL("http://host7.com/");
    886   ExpectPreClassificationChecks(url, &kFalse, &kFalse, &kFalse, &kFalse,
    887                                 &kFalse, &kTrue);
    888   NavigateAndCommit(url);
    889   WaitAndCheckPreClassificationChecks();
    890   msg = process()->sink().GetFirstMessageMatching(
    891       SafeBrowsingMsg_StartPhishingDetection::ID);
    892   ASSERT_FALSE(msg);
    893 
    894   // If result is cached, we will try and display the blocking page directly
    895   // with no start classification message.
    896   url = GURL("http://host8.com/");
    897   ExpectPreClassificationChecks(url, &kFalse, &kFalse, &kFalse, &kTrue, NULL,
    898                                 NULL);
    899 
    900   UnsafeResource resource;
    901   EXPECT_CALL(*ui_manager_.get(), DoDisplayBlockingPage(_))
    902       .WillOnce(SaveArg<0>(&resource));
    903 
    904   NavigateAndCommit(url);
    905   // Wait for CheckCsdWhitelist and CheckCache() to be called.
    906   base::RunLoop().RunUntilIdle();
    907   // Now we check that all expected functions were indeed called on the two
    908   // service objects.
    909   EXPECT_TRUE(Mock::VerifyAndClear(csd_service_.get()));
    910   EXPECT_TRUE(Mock::VerifyAndClear(ui_manager_.get()));
    911   EXPECT_EQ(url, resource.url);
    912   EXPECT_EQ(url, resource.original_url);
    913   resource.callback.Reset();
    914   msg = process()->sink().GetFirstMessageMatching(
    915       SafeBrowsingMsg_StartPhishingDetection::ID);
    916   ASSERT_FALSE(msg);
    917 }
    918 
    919 }  // namespace safe_browsing
    920