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.
5 #include "chrome/browser/safe_browsing/client_side_detection_host.h"
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 "base/strings/utf_string_conversions.h"
16 #include "chrome/browser/browser_process.h"
17 #include "chrome/browser/profiles/profile.h"
18 #include "chrome/browser/safe_browsing/browser_feature_extractor.h"
19 #include "chrome/browser/safe_browsing/client_side_detection_service.h"
20 #include "chrome/browser/safe_browsing/database_manager.h"
21 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
22 #include "chrome/common/pref_names.h"
23 #include "chrome/common/safe_browsing/csd.pb.h"
24 #include "chrome/common/safe_browsing/safebrowsing_messages.h"
25 #include "content/public/browser/browser_thread.h"
26 #include "content/public/browser/navigation_controller.h"
27 #include "content/public/browser/navigation_details.h"
28 #include "content/public/browser/navigation_entry.h"
29 #include "content/public/browser/render_process_host.h"
30 #include "content/public/browser/render_view_host.h"
31 #include "content/public/browser/resource_request_details.h"
32 #include "content/public/browser/web_contents.h"
33 #include "content/public/common/frame_navigate_params.h"
34 #include "content/public/common/url_constants.h"
37 using content::BrowserThread
;
38 using content::NavigationEntry
;
39 using content::ResourceRequestDetails
;
40 using content::ResourceType
;
41 using content::WebContents
;
43 namespace safe_browsing
{
45 const size_t ClientSideDetectionHost::kMaxUrlsPerIP
= 20;
46 const size_t ClientSideDetectionHost::kMaxIPsPerBrowse
= 200;
48 const char kSafeBrowsingMatchKey
[] = "safe_browsing_match";
50 typedef base::Callback
<void(bool)> ShouldClassifyUrlCallback
;
52 // This class is instantiated each time a new toplevel URL loads, and
53 // asynchronously checks whether the malware and phishing classifiers should run
54 // for this URL. If so, it notifies the host class by calling the provided
55 // callback form the UI thread. Objects of this class are ref-counted and will
56 // be destroyed once nobody uses it anymore. If |web_contents|, |csd_service|
57 // or |host| go away you need to call Cancel(). We keep the |database_manager|
58 // alive in a ref pointer for as long as it takes.
59 class ClientSideDetectionHost::ShouldClassifyUrlRequest
60 : public base::RefCountedThreadSafe
<
61 ClientSideDetectionHost::ShouldClassifyUrlRequest
> {
63 ShouldClassifyUrlRequest(
64 const content::FrameNavigateParams
& params
,
65 const ShouldClassifyUrlCallback
& start_phishing_classification
,
66 const ShouldClassifyUrlCallback
& start_malware_classification
,
67 WebContents
* web_contents
,
68 ClientSideDetectionService
* csd_service
,
69 SafeBrowsingDatabaseManager
* database_manager
,
70 ClientSideDetectionHost
* host
)
72 web_contents_(web_contents
),
73 csd_service_(csd_service
),
74 database_manager_(database_manager
),
76 start_phishing_classification_cb_(start_phishing_classification
),
77 start_malware_classification_cb_(start_malware_classification
) {
78 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
79 DCHECK(web_contents_
);
81 DCHECK(database_manager_
.get());
86 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
88 // We start by doing some simple checks that can run on the UI thread.
89 UMA_HISTOGRAM_BOOLEAN("SBClientPhishing.ClassificationStart", 1);
90 UMA_HISTOGRAM_BOOLEAN("SBClientMalware.ClassificationStart", 1);
92 // Only classify [X]HTML documents.
93 if (params_
.contents_mime_type
!= "text/html" &&
94 params_
.contents_mime_type
!= "application/xhtml+xml") {
95 DVLOG(1) << "Skipping phishing classification for URL: " << params_
.url
96 << " because it has an unsupported MIME type: "
97 << params_
.contents_mime_type
;
98 DontClassifyForPhishing(NO_CLASSIFY_UNSUPPORTED_MIME_TYPE
);
101 if (csd_service_
->IsPrivateIPAddress(params_
.socket_address
.host())) {
102 DVLOG(1) << "Skipping phishing classification for URL: " << params_
.url
103 << " because of hosting on private IP: "
104 << params_
.socket_address
.host();
105 DontClassifyForPhishing(NO_CLASSIFY_PRIVATE_IP
);
106 DontClassifyForMalware(NO_CLASSIFY_PRIVATE_IP
);
109 // For phishing we only classify HTTP pages.
110 if (!params_
.url
.SchemeIs(url::kHttpScheme
)) {
111 DVLOG(1) << "Skipping phishing classification for URL: " << params_
.url
112 << " because it is not HTTP: "
113 << params_
.socket_address
.host();
114 DontClassifyForPhishing(NO_CLASSIFY_NOT_HTTP_URL
);
117 // Don't run any classifier if the tab is incognito.
118 if (web_contents_
->GetBrowserContext()->IsOffTheRecord()) {
119 DVLOG(1) << "Skipping phishing and malware classification for URL: "
120 << params_
.url
<< " because we're browsing incognito.";
121 DontClassifyForPhishing(NO_CLASSIFY_OFF_THE_RECORD
);
122 DontClassifyForMalware(NO_CLASSIFY_OFF_THE_RECORD
);
125 // We lookup the csd-whitelist before we lookup the cache because
126 // a URL may have recently been whitelisted. If the URL matches
127 // the csd-whitelist we won't start phishing classification. The
128 // csd-whitelist check has to be done on the IO thread because it
129 // uses the SafeBrowsing service class.
130 if (ShouldClassifyForPhishing() || ShouldClassifyForMalware()) {
131 BrowserThread::PostTask(
134 base::Bind(&ShouldClassifyUrlRequest::CheckSafeBrowsingDatabase
,
140 DontClassifyForPhishing(NO_CLASSIFY_CANCEL
);
141 DontClassifyForMalware(NO_CLASSIFY_CANCEL
);
142 // Just to make sure we don't do anything stupid we reset all these
143 // pointers except for the safebrowsing service class which may be
144 // accessed by CheckSafeBrowsingDatabase().
145 web_contents_
= NULL
;
151 friend class base::RefCountedThreadSafe
<
152 ClientSideDetectionHost::ShouldClassifyUrlRequest
>;
154 // Enum used to keep stats about why the pre-classification check failed.
155 enum PreClassificationCheckFailures
{
156 OBSOLETE_NO_CLASSIFY_PROXY_FETCH
,
157 NO_CLASSIFY_PRIVATE_IP
,
158 NO_CLASSIFY_OFF_THE_RECORD
,
159 NO_CLASSIFY_MATCH_CSD_WHITELIST
,
160 NO_CLASSIFY_TOO_MANY_REPORTS
,
161 NO_CLASSIFY_UNSUPPORTED_MIME_TYPE
,
162 NO_CLASSIFY_NO_DATABASE_MANAGER
,
163 NO_CLASSIFY_KILLSWITCH
,
165 NO_CLASSIFY_RESULT_FROM_CACHE
,
166 NO_CLASSIFY_NOT_HTTP_URL
,
168 NO_CLASSIFY_MAX
// Always add new values before this one.
171 // The destructor can be called either from the UI or the IO thread.
172 virtual ~ShouldClassifyUrlRequest() { }
174 bool ShouldClassifyForPhishing() const {
175 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
176 return !start_phishing_classification_cb_
.is_null();
179 bool ShouldClassifyForMalware() const {
180 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
181 return !start_malware_classification_cb_
.is_null();
184 void DontClassifyForPhishing(PreClassificationCheckFailures reason
) {
185 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
186 if (ShouldClassifyForPhishing()) {
187 // Track the first reason why we stopped classifying for phishing.
188 UMA_HISTOGRAM_ENUMERATION("SBClientPhishing.PreClassificationCheckFail",
189 reason
, NO_CLASSIFY_MAX
);
190 DVLOG(2) << "Failed phishing pre-classification checks. Reason: "
192 start_phishing_classification_cb_
.Run(false);
194 start_phishing_classification_cb_
.Reset();
197 void DontClassifyForMalware(PreClassificationCheckFailures reason
) {
198 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
199 if (ShouldClassifyForMalware()) {
200 // Track the first reason why we stopped classifying for malware.
201 UMA_HISTOGRAM_ENUMERATION("SBClientMalware.PreClassificationCheckFail",
202 reason
, NO_CLASSIFY_MAX
);
203 DVLOG(2) << "Failed malware pre-classification checks. Reason: "
205 start_malware_classification_cb_
.Run(false);
207 start_malware_classification_cb_
.Reset();
210 void CheckSafeBrowsingDatabase(const GURL
& url
) {
211 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
212 // We don't want to call the classification callbacks from the IO
213 // thread so we simply pass the results of this method to CheckCache()
214 // which is called on the UI thread;
215 PreClassificationCheckFailures phishing_reason
= NO_CLASSIFY_MAX
;
216 PreClassificationCheckFailures malware_reason
= NO_CLASSIFY_MAX
;
217 if (!database_manager_
.get()) {
218 // We cannot check the Safe Browsing whitelists so we stop here
220 malware_reason
= phishing_reason
= NO_CLASSIFY_NO_DATABASE_MANAGER
;
222 if (database_manager_
->MatchCsdWhitelistUrl(url
)) {
223 DVLOG(1) << "Skipping phishing classification for URL: " << url
224 << " because it matches the csd whitelist";
225 phishing_reason
= NO_CLASSIFY_MATCH_CSD_WHITELIST
;
227 if (database_manager_
->IsMalwareKillSwitchOn()) {
228 malware_reason
= NO_CLASSIFY_KILLSWITCH
;
231 BrowserThread::PostTask(
234 base::Bind(&ShouldClassifyUrlRequest::CheckCache
,
240 void CheckCache(PreClassificationCheckFailures phishing_reason
,
241 PreClassificationCheckFailures malware_reason
) {
242 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
243 if (phishing_reason
!= NO_CLASSIFY_MAX
)
244 DontClassifyForPhishing(phishing_reason
);
245 if (malware_reason
!= NO_CLASSIFY_MAX
)
246 DontClassifyForMalware(malware_reason
);
247 if (!ShouldClassifyForMalware() && !ShouldClassifyForPhishing()) {
248 return; // No point in doing anything else.
250 // If result is cached, we don't want to run classification again.
251 // In that case we're just trying to show the warning.
253 if (csd_service_
->GetValidCachedResult(params_
.url
, &is_phishing
)) {
254 DVLOG(1) << "Satisfying request for " << params_
.url
<< " from cache";
255 UMA_HISTOGRAM_BOOLEAN("SBClientPhishing.RequestSatisfiedFromCache", 1);
256 // Since we are already on the UI thread, this is safe.
257 host_
->MaybeShowPhishingWarning(params_
.url
, is_phishing
);
258 DontClassifyForPhishing(NO_CLASSIFY_RESULT_FROM_CACHE
);
261 // We want to limit the number of requests, though we will ignore the
262 // limit for urls in the cache. We don't want to start classifying
263 // too many pages as phishing, but for those that we already think are
264 // phishing we want to send a request to the server to give ourselves
265 // a chance to fix misclassifications.
266 if (csd_service_
->IsInCache(params_
.url
)) {
267 DVLOG(1) << "Reporting limit skipped for " << params_
.url
268 << " as it was in the cache.";
269 UMA_HISTOGRAM_BOOLEAN("SBClientPhishing.ReportLimitSkipped", 1);
270 } else if (csd_service_
->OverPhishingReportLimit()) {
271 DVLOG(1) << "Too many report phishing requests sent recently, "
272 << "not running classification for " << params_
.url
;
273 DontClassifyForPhishing(NO_CLASSIFY_TOO_MANY_REPORTS
);
275 if (csd_service_
->OverMalwareReportLimit()) {
276 DontClassifyForMalware(NO_CLASSIFY_TOO_MANY_REPORTS
);
279 // Everything checks out, so start classification.
280 // |web_contents_| is safe to call as we will be destructed
282 if (ShouldClassifyForPhishing()) {
283 start_phishing_classification_cb_
.Run(true);
284 // Reset the callback to make sure ShouldClassifyForPhishing()
286 start_phishing_classification_cb_
.Reset();
288 if (ShouldClassifyForMalware()) {
289 start_malware_classification_cb_
.Run(true);
290 // Reset the callback to make sure ShouldClassifyForMalware()
292 start_malware_classification_cb_
.Reset();
296 content::FrameNavigateParams params_
;
297 WebContents
* web_contents_
;
298 ClientSideDetectionService
* csd_service_
;
299 // We keep a ref pointer here just to make sure the safe browsing
300 // database manager stays alive long enough.
301 scoped_refptr
<SafeBrowsingDatabaseManager
> database_manager_
;
302 ClientSideDetectionHost
* host_
;
304 ShouldClassifyUrlCallback start_phishing_classification_cb_
;
305 ShouldClassifyUrlCallback start_malware_classification_cb_
;
307 DISALLOW_COPY_AND_ASSIGN(ShouldClassifyUrlRequest
);
311 ClientSideDetectionHost
* ClientSideDetectionHost::Create(
313 return new ClientSideDetectionHost(tab
);
316 ClientSideDetectionHost::ClientSideDetectionHost(WebContents
* tab
)
317 : content::WebContentsObserver(tab
),
319 classification_request_(NULL
),
320 should_extract_malware_features_(true),
321 should_classify_for_malware_(false),
322 pageload_complete_(false),
323 unsafe_unique_page_id_(-1),
324 weak_factory_(this) {
326 // Note: csd_service_ and sb_service will be NULL here in testing.
327 csd_service_
= g_browser_process
->safe_browsing_detection_service();
328 feature_extractor_
.reset(new BrowserFeatureExtractor(tab
, this));
330 scoped_refptr
<SafeBrowsingService
> sb_service
=
331 g_browser_process
->safe_browsing_service();
332 if (sb_service
.get()) {
333 ui_manager_
= sb_service
->ui_manager();
334 database_manager_
= sb_service
->database_manager();
335 ui_manager_
->AddObserver(this);
339 ClientSideDetectionHost::~ClientSideDetectionHost() {
340 if (ui_manager_
.get())
341 ui_manager_
->RemoveObserver(this);
344 bool ClientSideDetectionHost::OnMessageReceived(const IPC::Message
& message
) {
346 IPC_BEGIN_MESSAGE_MAP(ClientSideDetectionHost
, message
)
347 IPC_MESSAGE_HANDLER(SafeBrowsingHostMsg_PhishingDetectionDone
,
348 OnPhishingDetectionDone
)
349 IPC_MESSAGE_UNHANDLED(handled
= false)
350 IPC_END_MESSAGE_MAP()
354 void ClientSideDetectionHost::DidNavigateMainFrame(
355 const content::LoadCommittedDetails
& details
,
356 const content::FrameNavigateParams
& params
) {
357 // TODO(noelutz): move this DCHECK to WebContents and fix all the unit tests
358 // that don't call this method on the UI thread.
359 // DCHECK_CURRENTLY_ON(BrowserThread::UI);
360 if (details
.is_in_page
) {
361 // If the navigation is within the same page, the user isn't really
362 // navigating away. We don't need to cancel a pending callback or
363 // begin a new classification.
366 // Cancel any pending classification request.
367 if (classification_request_
.get()) {
368 classification_request_
->Cancel();
370 // If we navigate away and there currently is a pending phishing
371 // report request we have to cancel it to make sure we don't display
372 // an interstitial for the wrong page. Note that this won't cancel
373 // the server ping back but only cancel the showing of the
375 weak_factory_
.InvalidateWeakPtrs();
380 browse_info_
.reset(new BrowseInfo
);
382 // Store redirect chain information.
383 if (params
.url
.host() != cur_host_
) {
384 cur_host_
= params
.url
.host();
385 cur_host_redirects_
= params
.redirects
;
387 browse_info_
->url
= params
.url
;
388 browse_info_
->host_redirects
= cur_host_redirects_
;
389 browse_info_
->url_redirects
= params
.redirects
;
390 browse_info_
->referrer
= params
.referrer
.url
;
391 browse_info_
->http_status_code
= details
.http_status_code
;
393 should_extract_malware_features_
= true;
394 should_classify_for_malware_
= false;
395 pageload_complete_
= false;
397 // Check whether we can cassify the current URL for phishing or malware.
398 classification_request_
= new ShouldClassifyUrlRequest(
400 base::Bind(&ClientSideDetectionHost::OnPhishingPreClassificationDone
,
401 weak_factory_
.GetWeakPtr()),
402 base::Bind(&ClientSideDetectionHost::OnMalwarePreClassificationDone
,
403 weak_factory_
.GetWeakPtr()),
404 web_contents(), csd_service_
, database_manager_
.get(), this);
405 classification_request_
->Start();
408 void ClientSideDetectionHost::OnSafeBrowsingHit(
409 const SafeBrowsingUIManager::UnsafeResource
& resource
) {
410 if (!web_contents() || !web_contents()->GetController().GetActiveEntry())
413 // Check that the hit is either malware or phishing.
414 if (resource
.threat_type
!= SB_THREAT_TYPE_URL_PHISHING
&&
415 resource
.threat_type
!= SB_THREAT_TYPE_URL_MALWARE
)
418 // Check that this notification is really for us.
419 content::RenderViewHost
* hit_rvh
= content::RenderViewHost::FromID(
420 resource
.render_process_host_id
, resource
.render_view_id
);
422 web_contents() != content::WebContents::FromRenderViewHost(hit_rvh
))
425 // Store the unique page ID for later.
426 unsafe_unique_page_id_
=
427 web_contents()->GetController().GetActiveEntry()->GetUniqueID();
429 // We also keep the resource around in order to be able to send the
430 // malicious URL to the server.
431 unsafe_resource_
.reset(new SafeBrowsingUIManager::UnsafeResource(resource
));
432 unsafe_resource_
->callback
.Reset(); // Don't do anything stupid.
435 void ClientSideDetectionHost::OnSafeBrowsingMatch(
436 const SafeBrowsingUIManager::UnsafeResource
& resource
) {
437 if (!web_contents() || !web_contents()->GetController().GetActiveEntry())
440 // Check that this notification is really for us.
441 content::RenderViewHost
* hit_rvh
= content::RenderViewHost::FromID(
442 resource
.render_process_host_id
, resource
.render_view_id
);
444 web_contents() != content::WebContents::FromRenderViewHost(hit_rvh
))
447 web_contents()->GetController().GetActiveEntry()->SetExtraData(
448 kSafeBrowsingMatchKey
, base::ASCIIToUTF16("1"));
451 scoped_refptr
<SafeBrowsingDatabaseManager
>
452 ClientSideDetectionHost::database_manager() {
453 return database_manager_
;
456 bool ClientSideDetectionHost::DidPageReceiveSafeBrowsingMatch() const {
457 if (!web_contents() || !web_contents()->GetController().GetVisibleEntry())
460 // If an interstitial page is showing, GetVisibleEntry will return the
461 // transient NavigationEntry for the interstitial. The transient entry
462 // will not have the flag set, so use the pending entry instead if there
464 NavigationEntry
* entry
= web_contents()->GetController().GetPendingEntry();
466 entry
= web_contents()->GetController().GetVisibleEntry();
467 if (entry
->GetPageType() == content::PAGE_TYPE_INTERSTITIAL
)
468 entry
= web_contents()->GetController().GetLastCommittedEntry();
473 base::string16 value
;
474 return entry
->GetExtraData(kSafeBrowsingMatchKey
, &value
);
477 void ClientSideDetectionHost::WebContentsDestroyed() {
478 // Tell any pending classification request that it is being canceled.
479 if (classification_request_
.get()) {
480 classification_request_
->Cancel();
482 // Cancel all pending feature extractions.
483 feature_extractor_
.reset();
486 void ClientSideDetectionHost::OnPhishingPreClassificationDone(
487 bool should_classify
) {
488 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
489 if (browse_info_
.get() && should_classify
) {
490 DVLOG(1) << "Instruct renderer to start phishing detection for URL: "
491 << browse_info_
->url
;
492 content::RenderViewHost
* rvh
= web_contents()->GetRenderViewHost();
493 rvh
->Send(new SafeBrowsingMsg_StartPhishingDetection(
494 rvh
->GetRoutingID(), browse_info_
->url
));
498 void ClientSideDetectionHost::OnMalwarePreClassificationDone(
499 bool should_classify
) {
500 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
501 // If classification checks failed we should stop extracting malware features.
502 DVLOG(2) << "Malware pre-classification checks done. Should classify: "
504 should_extract_malware_features_
= should_classify
;
505 should_classify_for_malware_
= should_classify
;
506 MaybeStartMalwareFeatureExtraction();
509 void ClientSideDetectionHost::DidStopLoading() {
510 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
511 if (!csd_service_
|| !browse_info_
.get())
513 DVLOG(2) << "Page finished loading.";
514 pageload_complete_
= true;
515 MaybeStartMalwareFeatureExtraction();
518 void ClientSideDetectionHost::MaybeStartMalwareFeatureExtraction() {
519 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
520 if (csd_service_
&& browse_info_
.get() &&
521 should_classify_for_malware_
&&
522 pageload_complete_
) {
523 scoped_ptr
<ClientMalwareRequest
> malware_request(
524 new ClientMalwareRequest
);
525 // Start browser-side malware feature extraction. Once we're done it will
526 // send the malware client verdict request.
527 malware_request
->set_url(browse_info_
->url
.spec());
528 const GURL
& referrer
= browse_info_
->referrer
;
529 if (referrer
.SchemeIs("http")) { // Only send http urls.
530 malware_request
->set_referrer_url(referrer
.spec());
532 // This function doesn't expect browse_info_ to stay around after this
534 feature_extractor_
->ExtractMalwareFeatures(
536 malware_request
.release(),
537 base::Bind(&ClientSideDetectionHost::MalwareFeatureExtractionDone
,
538 weak_factory_
.GetWeakPtr()));
539 should_classify_for_malware_
= false;
543 void ClientSideDetectionHost::OnPhishingDetectionDone(
544 const std::string
& verdict_str
) {
545 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
546 // There is something seriously wrong if there is no service class but
547 // this method is called. The renderer should not start phishing detection
548 // if there isn't any service class in the browser.
549 DCHECK(csd_service_
);
550 DCHECK(browse_info_
.get());
552 // We parse the protocol buffer here. If we're unable to parse it we won't
553 // send the verdict further.
554 scoped_ptr
<ClientPhishingRequest
> verdict(new ClientPhishingRequest
);
556 browse_info_
.get() &&
557 verdict
->ParseFromString(verdict_str
) &&
558 verdict
->IsInitialized()) {
559 // We only send phishing verdict to the server if the verdict is phishing or
560 // if a SafeBrowsing interstitial was already shown for this site. E.g., a
561 // malware or phishing interstitial was shown but the user clicked
563 if (verdict
->is_phishing() || DidShowSBInterstitial()) {
564 if (DidShowSBInterstitial()) {
565 browse_info_
->unsafe_resource
.reset(unsafe_resource_
.release());
567 // Start browser-side feature extraction. Once we're done it will send
568 // the client verdict request.
569 feature_extractor_
->ExtractFeatures(
572 base::Bind(&ClientSideDetectionHost::FeatureExtractionDone
,
573 weak_factory_
.GetWeakPtr()));
578 void ClientSideDetectionHost::MaybeShowPhishingWarning(GURL phishing_url
,
580 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
581 DVLOG(2) << "Received server phishing verdict for URL:" << phishing_url
582 << " is_phishing:" << is_phishing
;
584 DCHECK(web_contents());
585 if (ui_manager_
.get()) {
586 SafeBrowsingUIManager::UnsafeResource resource
;
587 resource
.url
= phishing_url
;
588 resource
.original_url
= phishing_url
;
589 resource
.is_subresource
= false;
590 resource
.threat_type
= SB_THREAT_TYPE_CLIENT_SIDE_PHISHING_URL
;
591 resource
.render_process_host_id
=
592 web_contents()->GetRenderProcessHost()->GetID();
593 resource
.render_view_id
=
594 web_contents()->GetRenderViewHost()->GetRoutingID();
595 if (!ui_manager_
->IsWhitelisted(resource
)) {
596 // We need to stop any pending navigations, otherwise the interstital
597 // might not get created properly.
598 web_contents()->GetController().DiscardNonCommittedEntries();
600 ui_manager_
->DisplayBlockingPage(resource
);
602 // If there is true phishing verdict, invalidate weakptr so that no longer
603 // consider the malware vedict.
604 weak_factory_
.InvalidateWeakPtrs();
608 void ClientSideDetectionHost::MaybeShowMalwareWarning(GURL original_url
,
611 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
612 DVLOG(2) << "Received server malawre IP verdict for URL:" << malware_url
613 << " is_malware:" << is_malware
;
614 if (is_malware
&& malware_url
.is_valid() && original_url
.is_valid()) {
615 DCHECK(web_contents());
616 if (ui_manager_
.get()) {
617 SafeBrowsingUIManager::UnsafeResource resource
;
618 resource
.url
= malware_url
;
619 resource
.original_url
= original_url
;
620 resource
.is_subresource
= (malware_url
.host() != original_url
.host());
621 resource
.threat_type
= SB_THREAT_TYPE_CLIENT_SIDE_MALWARE_URL
;
622 resource
.render_process_host_id
=
623 web_contents()->GetRenderProcessHost()->GetID();
624 resource
.render_view_id
=
625 web_contents()->GetRenderViewHost()->GetRoutingID();
626 if (!ui_manager_
->IsWhitelisted(resource
)) {
627 // We need to stop any pending navigations, otherwise the interstital
628 // might not get created properly.
629 web_contents()->GetController().DiscardNonCommittedEntries();
631 ui_manager_
->DisplayBlockingPage(resource
);
633 // If there is true malware verdict, invalidate weakptr so that no longer
634 // consider the phishing vedict.
635 weak_factory_
.InvalidateWeakPtrs();
639 void ClientSideDetectionHost::FeatureExtractionDone(
641 scoped_ptr
<ClientPhishingRequest
> request
) {
643 DVLOG(2) << "Feature extraction done (success:" << success
<< ") for URL: "
644 << request
->url() << ". Start sending client phishing request.";
645 ClientSideDetectionService::ClientReportPhishingRequestCallback callback
;
646 // If the client-side verdict isn't phishing we don't care about the server
647 // response because we aren't going to display a warning.
648 if (request
->is_phishing()) {
649 callback
= base::Bind(&ClientSideDetectionHost::MaybeShowPhishingWarning
,
650 weak_factory_
.GetWeakPtr());
653 Profile::FromBrowserContext(web_contents()->GetBrowserContext());
654 // Send ping even if the browser feature extraction failed.
655 csd_service_
->SendClientReportPhishingRequest(
656 request
.release(), // The service takes ownership of the request object.
657 profile
->GetPrefs()->GetBoolean(
658 prefs::kSafeBrowsingExtendedReportingEnabled
),
662 void ClientSideDetectionHost::MalwareFeatureExtractionDone(
663 bool feature_extraction_success
,
664 scoped_ptr
<ClientMalwareRequest
> request
) {
665 DCHECK(request
.get());
666 DVLOG(2) << "Malware Feature extraction done for URL: " << request
->url()
667 << ", with badip url count:" << request
->bad_ip_url_info_size();
669 // Send ping if there is matching features.
670 if (feature_extraction_success
&& request
->bad_ip_url_info_size() > 0) {
671 DVLOG(1) << "Start sending client malware request.";
672 ClientSideDetectionService::ClientReportMalwareRequestCallback callback
;
673 callback
= base::Bind(&ClientSideDetectionHost::MaybeShowMalwareWarning
,
674 weak_factory_
.GetWeakPtr());
675 csd_service_
->SendClientReportMalwareRequest(request
.release(), callback
);
679 void ClientSideDetectionHost::UpdateIPUrlMap(const std::string
& ip
,
680 const std::string
& url
,
681 const std::string
& method
,
682 const std::string
& referrer
,
683 const ResourceType resource_type
) {
684 if (ip
.empty() || url
.empty())
687 IPUrlMap::iterator it
= browse_info_
->ips
.find(ip
);
688 if (it
== browse_info_
->ips
.end()) {
689 if (browse_info_
->ips
.size() < kMaxIPsPerBrowse
) {
690 std::vector
<IPUrlInfo
> url_infos
;
691 url_infos
.push_back(IPUrlInfo(url
, method
, referrer
, resource_type
));
692 browse_info_
->ips
.insert(make_pair(ip
, url_infos
));
694 } else if (it
->second
.size() < kMaxUrlsPerIP
) {
695 it
->second
.push_back(IPUrlInfo(url
, method
, referrer
, resource_type
));
699 void ClientSideDetectionHost::DidGetResourceResponseStart(
700 const content::ResourceRequestDetails
& details
) {
701 if (browse_info_
.get() && should_extract_malware_features_
&&
702 details
.url
.is_valid()) {
703 UpdateIPUrlMap(details
.socket_address
.host() /* ip */,
704 details
.url
.spec() /* url */,
707 details
.resource_type
);
711 bool ClientSideDetectionHost::DidShowSBInterstitial() const {
712 if (unsafe_unique_page_id_
<= 0 || !web_contents()) {
715 const NavigationEntry
* nav_entry
=
716 web_contents()->GetController().GetActiveEntry();
717 return (nav_entry
&& nav_entry
->GetUniqueID() == unsafe_unique_page_id_
);
720 void ClientSideDetectionHost::set_client_side_detection_service(
721 ClientSideDetectionService
* service
) {
722 csd_service_
= service
;
725 void ClientSideDetectionHost::set_safe_browsing_managers(
726 SafeBrowsingUIManager
* ui_manager
,
727 SafeBrowsingDatabaseManager
* database_manager
) {
728 if (ui_manager_
.get())
729 ui_manager_
->RemoveObserver(this);
731 ui_manager_
= ui_manager
;
733 ui_manager_
->AddObserver(this);
735 database_manager_
= database_manager
;
738 } // namespace safe_browsing