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 "chrome/browser/safe_browsing/client_side_detection_host.h"
      6 
      7 #include <vector>
      8 
      9 #include "base/logging.h"
     10 #include "base/memory/ref_counted.h"
     11 #include "base/memory/scoped_ptr.h"
     12 #include "base/metrics/histogram.h"
     13 #include "base/prefs/pref_service.h"
     14 #include "base/sequenced_task_runner_helpers.h"
     15 #include "chrome/browser/browser_process.h"
     16 #include "chrome/browser/profiles/profile.h"
     17 #include "chrome/browser/safe_browsing/browser_feature_extractor.h"
     18 #include "chrome/browser/safe_browsing/client_side_detection_service.h"
     19 #include "chrome/browser/safe_browsing/database_manager.h"
     20 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
     21 #include "chrome/common/chrome_switches.h"
     22 #include "chrome/common/chrome_version_info.h"
     23 #include "chrome/common/pref_names.h"
     24 #include "chrome/common/safe_browsing/csd.pb.h"
     25 #include "chrome/common/safe_browsing/safebrowsing_messages.h"
     26 #include "content/public/browser/browser_thread.h"
     27 #include "content/public/browser/navigation_controller.h"
     28 #include "content/public/browser/navigation_details.h"
     29 #include "content/public/browser/navigation_entry.h"
     30 #include "content/public/browser/notification_details.h"
     31 #include "content/public/browser/notification_source.h"
     32 #include "content/public/browser/notification_types.h"
     33 #include "content/public/browser/render_process_host.h"
     34 #include "content/public/browser/render_view_host.h"
     35 #include "content/public/browser/resource_request_details.h"
     36 #include "content/public/browser/web_contents.h"
     37 #include "content/public/common/frame_navigate_params.h"
     38 #include "url/gurl.h"
     39 
     40 using content::BrowserThread;
     41 using content::NavigationEntry;
     42 using content::ResourceRequestDetails;
     43 using content::WebContents;
     44 
     45 namespace safe_browsing {
     46 
     47 const int ClientSideDetectionHost::kMaxUrlsPerIP = 20;
     48 const int ClientSideDetectionHost::kMaxIPsPerBrowse = 200;
     49 
     50 // This class is instantiated each time a new toplevel URL loads, and
     51 // asynchronously checks whether the phishing classifier should run for this
     52 // URL.  If so, it notifies the renderer with a StartPhishingDetection IPC.
     53 // Objects of this class are ref-counted and will be destroyed once nobody
     54 // uses it anymore.  If |web_contents|, |csd_service| or |host| go away you need
     55 // to call Cancel().  We keep the |database_manager| alive in a ref pointer for
     56 // as long as it takes.
     57 class ClientSideDetectionHost::ShouldClassifyUrlRequest
     58     : public base::RefCountedThreadSafe<
     59           ClientSideDetectionHost::ShouldClassifyUrlRequest> {
     60  public:
     61   ShouldClassifyUrlRequest(const content::FrameNavigateParams& params,
     62                            WebContents* web_contents,
     63                            ClientSideDetectionService* csd_service,
     64                            SafeBrowsingDatabaseManager* database_manager,
     65                            ClientSideDetectionHost* host)
     66       : canceled_(false),
     67         params_(params),
     68         web_contents_(web_contents),
     69         csd_service_(csd_service),
     70         database_manager_(database_manager),
     71         host_(host) {
     72     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
     73     DCHECK(web_contents_);
     74     DCHECK(csd_service_);
     75     DCHECK(database_manager_.get());
     76     DCHECK(host_);
     77   }
     78 
     79   void Start() {
     80     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
     81 
     82     // We start by doing some simple checks that can run on the UI thread.
     83     UMA_HISTOGRAM_COUNTS("SBClientPhishing.ClassificationStart", 1);
     84 
     85     // Only classify [X]HTML documents.
     86     if (params_.contents_mime_type != "text/html" &&
     87         params_.contents_mime_type != "application/xhtml+xml") {
     88       VLOG(1) << "Skipping phishing classification for URL: " << params_.url
     89               << " because it has an unsupported MIME type: "
     90               << params_.contents_mime_type;
     91       UMA_HISTOGRAM_ENUMERATION("SBClientPhishing.PreClassificationCheckFail",
     92                                 NO_CLASSIFY_UNSUPPORTED_MIME_TYPE,
     93                                 NO_CLASSIFY_MAX);
     94       return;
     95     }
     96 
     97     if (csd_service_->IsPrivateIPAddress(params_.socket_address.host())) {
     98       VLOG(1) << "Skipping phishing classification for URL: " << params_.url
     99               << " because of hosting on private IP: "
    100               << params_.socket_address.host();
    101       UMA_HISTOGRAM_ENUMERATION("SBClientPhishing.PreClassificationCheckFail",
    102                                 NO_CLASSIFY_PRIVATE_IP,
    103                                 NO_CLASSIFY_MAX);
    104       return;
    105     }
    106 
    107     // Don't run the phishing classifier if the tab is incognito.
    108     if (web_contents_->GetBrowserContext()->IsOffTheRecord()) {
    109       VLOG(1) << "Skipping phishing classification for URL: " << params_.url
    110               << " because we're browsing incognito.";
    111       UMA_HISTOGRAM_ENUMERATION("SBClientPhishing.PreClassificationCheckFail",
    112                                 NO_CLASSIFY_OFF_THE_RECORD,
    113                                 NO_CLASSIFY_MAX);
    114 
    115       return;
    116     }
    117 
    118     // We lookup the csd-whitelist before we lookup the cache because
    119     // a URL may have recently been whitelisted.  If the URL matches
    120     // the csd-whitelist we won't start classification.  The
    121     // csd-whitelist check has to be done on the IO thread because it
    122     // uses the SafeBrowsing service class.
    123     BrowserThread::PostTask(
    124         BrowserThread::IO,
    125         FROM_HERE,
    126         base::Bind(&ShouldClassifyUrlRequest::CheckCsdWhitelist,
    127                    this, params_.url));
    128   }
    129 
    130   void Cancel() {
    131     canceled_ = true;
    132     // Just to make sure we don't do anything stupid we reset all these
    133     // pointers except for the safebrowsing service class which may be
    134     // accessed by CheckCsdWhitelist().
    135     web_contents_ = NULL;
    136     csd_service_ = NULL;
    137     host_ = NULL;
    138   }
    139 
    140  private:
    141   friend class base::RefCountedThreadSafe<
    142       ClientSideDetectionHost::ShouldClassifyUrlRequest>;
    143 
    144   // Enum used to keep stats about why the pre-classification check failed.
    145   enum PreClassificationCheckFailures {
    146     OBSOLETE_NO_CLASSIFY_PROXY_FETCH,
    147     NO_CLASSIFY_PRIVATE_IP,
    148     NO_CLASSIFY_OFF_THE_RECORD,
    149     NO_CLASSIFY_MATCH_CSD_WHITELIST,
    150     NO_CLASSIFY_TOO_MANY_REPORTS,
    151     NO_CLASSIFY_UNSUPPORTED_MIME_TYPE,
    152 
    153     NO_CLASSIFY_MAX  // Always add new values before this one.
    154   };
    155 
    156   // The destructor can be called either from the UI or the IO thread.
    157   virtual ~ShouldClassifyUrlRequest() { }
    158 
    159   void CheckCsdWhitelist(const GURL& url) {
    160     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    161     if (!database_manager_.get() ||
    162         database_manager_->MatchCsdWhitelistUrl(url)) {
    163       // We're done.  There is no point in going back to the UI thread.
    164       VLOG(1) << "Skipping phishing classification for URL: " << url
    165               << " because it matches the csd whitelist";
    166       UMA_HISTOGRAM_ENUMERATION("SBClientPhishing.PreClassificationCheckFail",
    167                                 NO_CLASSIFY_MATCH_CSD_WHITELIST,
    168                                 NO_CLASSIFY_MAX);
    169       return;
    170     }
    171 
    172     bool malware_killswitch_on = database_manager_->IsMalwareKillSwitchOn();
    173 
    174     BrowserThread::PostTask(
    175         BrowserThread::UI,
    176         FROM_HERE,
    177         base::Bind(&ShouldClassifyUrlRequest::CheckCache, this,
    178                    malware_killswitch_on));
    179   }
    180 
    181   void CheckCache(bool malware_killswitch_on) {
    182     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    183     if (canceled_) {
    184       return;
    185     }
    186 
    187     host_->SetMalwareKillSwitch(malware_killswitch_on);
    188     // If result is cached, we don't want to run classification again
    189     bool is_phishing;
    190     if (csd_service_->GetValidCachedResult(params_.url, &is_phishing)) {
    191       VLOG(1) << "Satisfying request for " << params_.url << " from cache";
    192       UMA_HISTOGRAM_COUNTS("SBClientPhishing.RequestSatisfiedFromCache", 1);
    193       // Since we are already on the UI thread, this is safe.
    194       host_->MaybeShowPhishingWarning(params_.url, is_phishing);
    195       return;
    196     }
    197 
    198     // We want to limit the number of requests, though we will ignore the
    199     // limit for urls in the cache.  We don't want to start classifying
    200     // too many pages as phishing, but for those that we already think are
    201     // phishing we want to give ourselves a chance to fix false positives.
    202     if (csd_service_->IsInCache(params_.url)) {
    203       VLOG(1) << "Reporting limit skipped for " << params_.url
    204               << " as it was in the cache.";
    205       UMA_HISTOGRAM_COUNTS("SBClientPhishing.ReportLimitSkipped", 1);
    206     } else if (csd_service_->OverPhishingReportLimit()) {
    207       VLOG(1) << "Too many report phishing requests sent recently, "
    208               << "not running classification for " << params_.url;
    209       UMA_HISTOGRAM_ENUMERATION("SBClientPhishing.PreClassificationCheckFail",
    210                                 NO_CLASSIFY_TOO_MANY_REPORTS,
    211                                 NO_CLASSIFY_MAX);
    212       return;
    213     }
    214 
    215     // Everything checks out, so start classification.
    216     // |web_contents_| is safe to call as we will be destructed
    217     // before it is.
    218     VLOG(1) << "Instruct renderer to start phishing detection for URL: "
    219             << params_.url;
    220     content::RenderViewHost* rvh = web_contents_->GetRenderViewHost();
    221     rvh->Send(new SafeBrowsingMsg_StartPhishingDetection(
    222         rvh->GetRoutingID(), params_.url));
    223   }
    224 
    225   // No need to protect |canceled_| with a lock because it is only read and
    226   // written by the UI thread.
    227   bool canceled_;
    228   content::FrameNavigateParams params_;
    229   WebContents* web_contents_;
    230   ClientSideDetectionService* csd_service_;
    231   // We keep a ref pointer here just to make sure the safe browsing
    232   // database manager stays alive long enough.
    233   scoped_refptr<SafeBrowsingDatabaseManager> database_manager_;
    234   ClientSideDetectionHost* host_;
    235 
    236   DISALLOW_COPY_AND_ASSIGN(ShouldClassifyUrlRequest);
    237 };
    238 
    239 // static
    240 ClientSideDetectionHost* ClientSideDetectionHost::Create(
    241     WebContents* tab) {
    242   return new ClientSideDetectionHost(tab);
    243 }
    244 
    245 ClientSideDetectionHost::ClientSideDetectionHost(WebContents* tab)
    246     : content::WebContentsObserver(tab),
    247       csd_service_(NULL),
    248       weak_factory_(this),
    249       unsafe_unique_page_id_(-1),
    250       malware_killswitch_on_(false),
    251       malware_report_enabled_(false),
    252       malware_or_phishing_match_(false) {
    253   DCHECK(tab);
    254   // Note: csd_service_ and sb_service will be NULL here in testing.
    255   csd_service_ = g_browser_process->safe_browsing_detection_service();
    256   feature_extractor_.reset(new BrowserFeatureExtractor(tab, this));
    257   registrar_.Add(this, content::NOTIFICATION_RESOURCE_RESPONSE_STARTED,
    258                  content::Source<WebContents>(tab));
    259 
    260   scoped_refptr<SafeBrowsingService> sb_service =
    261       g_browser_process->safe_browsing_service();
    262   if (sb_service.get()) {
    263     ui_manager_ = sb_service->ui_manager();
    264     database_manager_ = sb_service->database_manager();
    265     ui_manager_->AddObserver(this);
    266   }
    267 
    268   // Only enable the malware bad IP matching and report feature for canary
    269   // and dev channel.
    270   chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel();
    271   malware_report_enabled_ = (
    272       channel == chrome::VersionInfo::CHANNEL_DEV ||
    273       channel == chrome::VersionInfo::CHANNEL_CANARY);
    274 }
    275 
    276 ClientSideDetectionHost::~ClientSideDetectionHost() {
    277   if (ui_manager_.get())
    278     ui_manager_->RemoveObserver(this);
    279 }
    280 
    281 bool ClientSideDetectionHost::OnMessageReceived(const IPC::Message& message) {
    282   bool handled = true;
    283   IPC_BEGIN_MESSAGE_MAP(ClientSideDetectionHost, message)
    284     IPC_MESSAGE_HANDLER(SafeBrowsingHostMsg_PhishingDetectionDone,
    285                         OnPhishingDetectionDone)
    286     IPC_MESSAGE_UNHANDLED(handled = false)
    287   IPC_END_MESSAGE_MAP()
    288   return handled;
    289 }
    290 
    291 void ClientSideDetectionHost::DidNavigateMainFrame(
    292     const content::LoadCommittedDetails& details,
    293     const content::FrameNavigateParams& params) {
    294   malware_or_phishing_match_ = false;
    295 
    296   // TODO(noelutz): move this DCHECK to WebContents and fix all the unit tests
    297   // that don't call this method on the UI thread.
    298   // DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    299   if (details.is_in_page) {
    300     // If the navigation is within the same page, the user isn't really
    301     // navigating away.  We don't need to cancel a pending callback or
    302     // begin a new classification.
    303     return;
    304   }
    305   // If we navigate away and there currently is a pending phishing
    306   // report request we have to cancel it to make sure we don't display
    307   // an interstitial for the wrong page.  Note that this won't cancel
    308   // the server ping back but only cancel the showing of the
    309   // interstial.
    310   weak_factory_.InvalidateWeakPtrs();
    311 
    312   if (!csd_service_) {
    313     return;
    314   }
    315 
    316   // Cancel any pending classification request.
    317   if (classification_request_.get()) {
    318     classification_request_->Cancel();
    319   }
    320   browse_info_.reset(new BrowseInfo);
    321 
    322   // Store redirect chain information.
    323   if (params.url.host() != cur_host_) {
    324     cur_host_ = params.url.host();
    325     cur_host_redirects_ = params.redirects;
    326   }
    327   browse_info_->host_redirects = cur_host_redirects_;
    328   browse_info_->url_redirects = params.redirects;
    329   browse_info_->referrer = params.referrer.url;
    330   browse_info_->http_status_code = details.http_status_code;
    331 
    332   // Notify the renderer if it should classify this URL.
    333   classification_request_ = new ShouldClassifyUrlRequest(
    334       params, web_contents(), csd_service_, database_manager_.get(), this);
    335   classification_request_->Start();
    336 }
    337 
    338 void ClientSideDetectionHost::OnSafeBrowsingHit(
    339     const SafeBrowsingUIManager::UnsafeResource& resource) {
    340   if (!web_contents() || !web_contents()->GetController().GetActiveEntry())
    341     return;
    342 
    343   // Check that the hit is either malware or phishing.
    344   if (resource.threat_type != SB_THREAT_TYPE_URL_PHISHING &&
    345       resource.threat_type != SB_THREAT_TYPE_URL_MALWARE)
    346     return;
    347 
    348   // Check that this notification is really for us.
    349   content::RenderViewHost* hit_rvh = content::RenderViewHost::FromID(
    350       resource.render_process_host_id, resource.render_view_id);
    351   if (!hit_rvh ||
    352       web_contents() != content::WebContents::FromRenderViewHost(hit_rvh))
    353     return;
    354 
    355   // Store the unique page ID for later.
    356   unsafe_unique_page_id_ =
    357       web_contents()->GetController().GetActiveEntry()->GetUniqueID();
    358   // We also keep the resource around in order to be able to send the
    359   // malicious URL to the server.
    360   unsafe_resource_.reset(new SafeBrowsingUIManager::UnsafeResource(resource));
    361   unsafe_resource_->callback.Reset();  // Don't do anything stupid.
    362 }
    363 
    364 void ClientSideDetectionHost::OnSafeBrowsingMatch(
    365     const SafeBrowsingUIManager::UnsafeResource& resource) {
    366   malware_or_phishing_match_ = true;
    367 }
    368 
    369 scoped_refptr<SafeBrowsingDatabaseManager>
    370 ClientSideDetectionHost::database_manager() {
    371   return database_manager_;
    372 }
    373 
    374 bool ClientSideDetectionHost::DidPageReceiveSafeBrowsingMatch() const {
    375   return malware_or_phishing_match_ || DidShowSBInterstitial();
    376 }
    377 
    378 void ClientSideDetectionHost::WebContentsDestroyed(WebContents* tab) {
    379   DCHECK(tab);
    380   // Tell any pending classification request that it is being canceled.
    381   if (classification_request_.get()) {
    382     classification_request_->Cancel();
    383   }
    384   // Cancel all pending feature extractions.
    385   feature_extractor_.reset();
    386 }
    387 
    388 void ClientSideDetectionHost::OnPhishingDetectionDone(
    389     const std::string& verdict_str) {
    390   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    391   // There is something seriously wrong if there is no service class but
    392   // this method is called.  The renderer should not start phishing detection
    393   // if there isn't any service class in the browser.
    394   DCHECK(csd_service_);
    395   // There shouldn't be any pending requests because we revoke them everytime
    396   // we navigate away.
    397   DCHECK(!weak_factory_.HasWeakPtrs());
    398   DCHECK(browse_info_.get());
    399 
    400   // We parse the protocol buffer here.  If we're unable to parse it we won't
    401   // send the verdict further.
    402   scoped_ptr<ClientPhishingRequest> verdict(new ClientPhishingRequest);
    403   if (csd_service_ &&
    404       !weak_factory_.HasWeakPtrs() &&
    405       browse_info_.get() &&
    406       verdict->ParseFromString(verdict_str) &&
    407       verdict->IsInitialized()) {
    408     // We do the malware IP matching and request sending if the feature
    409     // is enabled.
    410     if (malware_report_enabled_ && !MalwareKillSwitchIsOn()) {
    411       scoped_ptr<ClientMalwareRequest> malware_verdict(
    412           new ClientMalwareRequest);
    413       // Start browser-side malware feature extraction.  Once we're done it will
    414       // send the malware client verdict request.
    415       malware_verdict->set_url(verdict->url());
    416       const GURL& referrer = browse_info_->referrer;
    417       if (referrer.SchemeIs("http")) {  // Only send http urls.
    418           malware_verdict->set_referrer_url(referrer.spec());
    419       }
    420       // This function doesn't expect browse_info_ to stay around after this
    421       // function returns.
    422       feature_extractor_->ExtractMalwareFeatures(
    423           browse_info_.get(),
    424           malware_verdict.release(),
    425           base::Bind(&ClientSideDetectionHost::MalwareFeatureExtractionDone,
    426                      weak_factory_.GetWeakPtr()));
    427     }
    428 
    429     // We only send phishing verdict to the server if the verdict is phishing or
    430     // if a SafeBrowsing interstitial was already shown for this site.  E.g., a
    431     // malware or phishing interstitial was shown but the user clicked
    432     // through.
    433     if (verdict->is_phishing() || DidShowSBInterstitial()) {
    434       if (DidShowSBInterstitial()) {
    435         browse_info_->unsafe_resource.reset(unsafe_resource_.release());
    436       }
    437       // Start browser-side feature extraction.  Once we're done it will send
    438       // the client verdict request.
    439       feature_extractor_->ExtractFeatures(
    440           browse_info_.get(),
    441           verdict.release(),
    442           base::Bind(&ClientSideDetectionHost::FeatureExtractionDone,
    443                      weak_factory_.GetWeakPtr()));
    444     }
    445   }
    446   browse_info_.reset();
    447 }
    448 
    449 void ClientSideDetectionHost::MaybeShowPhishingWarning(GURL phishing_url,
    450                                                        bool is_phishing) {
    451   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    452   VLOG(2) << "Received server phishing verdict for URL:" << phishing_url
    453           << " is_phishing:" << is_phishing;
    454   if (is_phishing) {
    455     DCHECK(web_contents());
    456     if (ui_manager_.get()) {
    457       SafeBrowsingUIManager::UnsafeResource resource;
    458       resource.url = phishing_url;
    459       resource.original_url = phishing_url;
    460       resource.is_subresource = false;
    461       resource.threat_type = SB_THREAT_TYPE_CLIENT_SIDE_PHISHING_URL;
    462       resource.render_process_host_id =
    463           web_contents()->GetRenderProcessHost()->GetID();
    464       resource.render_view_id =
    465           web_contents()->GetRenderViewHost()->GetRoutingID();
    466       if (!ui_manager_->IsWhitelisted(resource)) {
    467         // We need to stop any pending navigations, otherwise the interstital
    468         // might not get created properly.
    469         web_contents()->GetController().DiscardNonCommittedEntries();
    470       }
    471       ui_manager_->DoDisplayBlockingPage(resource);
    472     }
    473     // If there is true phishing verdict, invalidate weakptr so that no longer
    474     // consider the malware vedict.
    475     weak_factory_.InvalidateWeakPtrs();
    476   }
    477 }
    478 
    479 void ClientSideDetectionHost::MaybeShowMalwareWarning(GURL original_url,
    480                                                       GURL malware_url,
    481                                                       bool is_malware) {
    482   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    483   VLOG(2) << "Received server malawre IP verdict for URL:" << malware_url
    484           << " is_malware:" << is_malware;
    485   if (is_malware && malware_url.is_valid() && original_url.is_valid()) {
    486     DCHECK(web_contents());
    487     if (ui_manager_.get()) {
    488       SafeBrowsingUIManager::UnsafeResource resource;
    489       resource.url = malware_url;
    490       resource.original_url = original_url;
    491       resource.is_subresource = (malware_url.host() != original_url.host());
    492       resource.threat_type = SB_THREAT_TYPE_CLIENT_SIDE_MALWARE_URL;
    493       resource.render_process_host_id =
    494           web_contents()->GetRenderProcessHost()->GetID();
    495       resource.render_view_id =
    496           web_contents()->GetRenderViewHost()->GetRoutingID();
    497       if (!ui_manager_->IsWhitelisted(resource)) {
    498         // We need to stop any pending navigations, otherwise the interstital
    499         // might not get created properly.
    500         web_contents()->GetController().DiscardNonCommittedEntries();
    501       }
    502       ui_manager_->DoDisplayBlockingPage(resource);
    503     }
    504     // If there is true malware verdict, invalidate weakptr so that no longer
    505     // consider the phishing vedict.
    506     weak_factory_.InvalidateWeakPtrs();
    507   }
    508 }
    509 
    510 void ClientSideDetectionHost::FeatureExtractionDone(
    511     bool success,
    512     ClientPhishingRequest* request) {
    513   DCHECK(request);
    514   VLOG(2) << "Feature extraction done (success:" << success << ") for URL: "
    515           << request->url() << ". Start sending client phishing request.";
    516   ClientSideDetectionService::ClientReportPhishingRequestCallback callback;
    517   // If the client-side verdict isn't phishing we don't care about the server
    518   // response because we aren't going to display a warning.
    519   if (request->is_phishing()) {
    520     callback = base::Bind(&ClientSideDetectionHost::MaybeShowPhishingWarning,
    521                           weak_factory_.GetWeakPtr());
    522   }
    523   // Send ping even if the browser feature extraction failed.
    524   csd_service_->SendClientReportPhishingRequest(
    525       request,  // The service takes ownership of the request object.
    526       callback);
    527 }
    528 
    529 void ClientSideDetectionHost::MalwareFeatureExtractionDone(
    530     bool feature_extraction_success,
    531     scoped_ptr<ClientMalwareRequest> request) {
    532   DCHECK(request.get());
    533   VLOG(2) << "Malware Feature extraction done for URL: " << request->url()
    534           << ", with badip url count:" << request->bad_ip_url_info_size();
    535 
    536   // Send ping if there is matching features.
    537   if (feature_extraction_success && request->bad_ip_url_info_size() > 0) {
    538     VLOG(1) << "Start sending client malware request.";
    539     ClientSideDetectionService::ClientReportMalwareRequestCallback callback;
    540     callback = base::Bind(&ClientSideDetectionHost::MaybeShowMalwareWarning,
    541                           weak_factory_.GetWeakPtr());
    542     csd_service_->SendClientReportMalwareRequest(request.release(), callback);
    543   }
    544 }
    545 
    546 void ClientSideDetectionHost::UpdateIPUrlMap(
    547     const std::string& ip,
    548     const std::string& url,
    549     const std::string& method,
    550     const std::string& referrer,
    551     const ResourceType::Type resource_type) {
    552   if (ip.empty() || url.empty())
    553     return;
    554 
    555   IPUrlMap::iterator it = browse_info_->ips.find(ip);
    556   if (it == browse_info_->ips.end()) {
    557     if (int(browse_info_->ips.size()) < kMaxIPsPerBrowse) {
    558       std::vector<IPUrlInfo> url_infos;
    559       url_infos.push_back(IPUrlInfo(url, method, referrer, resource_type));
    560       browse_info_->ips.insert(make_pair(ip, url_infos));
    561     }
    562   } else if (int(it->second.size()) < kMaxUrlsPerIP) {
    563     it->second.push_back(IPUrlInfo(url, method, referrer, resource_type));
    564   }
    565 }
    566 
    567 void ClientSideDetectionHost::Observe(
    568     int type,
    569     const content::NotificationSource& source,
    570     const content::NotificationDetails& details) {
    571   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    572   DCHECK_EQ(type, content::NOTIFICATION_RESOURCE_RESPONSE_STARTED);
    573   const ResourceRequestDetails* req = content::Details<ResourceRequestDetails>(
    574       details).ptr();
    575   if (req && browse_info_.get() && malware_report_enabled_ &&
    576       !MalwareKillSwitchIsOn()) {
    577     if (req->url.is_valid()) {
    578       UpdateIPUrlMap(req->socket_address.host() /* ip */,
    579                      req->url.spec()  /* url */,
    580                      req->method,
    581                      req->referrer,
    582                      req->resource_type);
    583     }
    584   }
    585 }
    586 
    587 bool ClientSideDetectionHost::DidShowSBInterstitial() const {
    588   if (unsafe_unique_page_id_ <= 0 || !web_contents()) {
    589     return false;
    590   }
    591   const NavigationEntry* nav_entry =
    592       web_contents()->GetController().GetActiveEntry();
    593   return (nav_entry && nav_entry->GetUniqueID() == unsafe_unique_page_id_);
    594 }
    595 
    596 void ClientSideDetectionHost::set_client_side_detection_service(
    597     ClientSideDetectionService* service) {
    598   csd_service_ = service;
    599 }
    600 
    601 void ClientSideDetectionHost::set_safe_browsing_managers(
    602     SafeBrowsingUIManager* ui_manager,
    603     SafeBrowsingDatabaseManager* database_manager) {
    604   if (ui_manager_.get())
    605     ui_manager_->RemoveObserver(this);
    606 
    607   ui_manager_ = ui_manager;
    608   if (ui_manager)
    609     ui_manager_->AddObserver(this);
    610 
    611   database_manager_ = database_manager;
    612 }
    613 
    614 bool ClientSideDetectionHost::MalwareKillSwitchIsOn() {
    615   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    616   return malware_killswitch_on_;
    617 }
    618 
    619 void ClientSideDetectionHost::SetMalwareKillSwitch(bool killswitch_on) {
    620   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    621   malware_killswitch_on_ = killswitch_on;
    622 }
    623 
    624 }  // namespace safe_browsing
    625