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/notification_details.h"
30 #include "content/public/browser/notification_source.h"
31 #include "content/public/browser/notification_types.h"
32 #include "content/public/browser/render_process_host.h"
33 #include "content/public/browser/render_view_host.h"
34 #include "content/public/browser/resource_request_details.h"
35 #include "content/public/browser/web_contents.h"
36 #include "content/public/common/frame_navigate_params.h"
37 #include "content/public/common/url_constants.h"
40 using content::BrowserThread
;
41 using content::NavigationEntry
;
42 using content::ResourceRequestDetails
;
43 using content::ResourceType
;
44 using content::WebContents
;
46 namespace safe_browsing
{
48 const size_t ClientSideDetectionHost::kMaxUrlsPerIP
= 20;
49 const size_t ClientSideDetectionHost::kMaxIPsPerBrowse
= 200;
51 const char kSafeBrowsingMatchKey
[] = "safe_browsing_match";
53 typedef base::Callback
<void(bool)> ShouldClassifyUrlCallback
;
55 // This class is instantiated each time a new toplevel URL loads, and
56 // asynchronously checks whether the malware and phishing classifiers should run
57 // for this URL. If so, it notifies the host class by calling the provided
58 // callback form the UI thread. Objects of this class are ref-counted and will
59 // be destroyed once nobody uses it anymore. If |web_contents|, |csd_service|
60 // or |host| go away you need to call Cancel(). We keep the |database_manager|
61 // alive in a ref pointer for as long as it takes.
62 class ClientSideDetectionHost::ShouldClassifyUrlRequest
63 : public base::RefCountedThreadSafe
<
64 ClientSideDetectionHost::ShouldClassifyUrlRequest
> {
66 ShouldClassifyUrlRequest(
67 const content::FrameNavigateParams
& params
,
68 const ShouldClassifyUrlCallback
& start_phishing_classification
,
69 const ShouldClassifyUrlCallback
& start_malware_classification
,
70 WebContents
* web_contents
,
71 ClientSideDetectionService
* csd_service
,
72 SafeBrowsingDatabaseManager
* database_manager
,
73 ClientSideDetectionHost
* host
)
75 web_contents_(web_contents
),
76 csd_service_(csd_service
),
77 database_manager_(database_manager
),
79 start_phishing_classification_cb_(start_phishing_classification
),
80 start_malware_classification_cb_(start_malware_classification
) {
81 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
82 DCHECK(web_contents_
);
84 DCHECK(database_manager_
.get());
89 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
91 // We start by doing some simple checks that can run on the UI thread.
92 UMA_HISTOGRAM_BOOLEAN("SBClientPhishing.ClassificationStart", 1);
93 UMA_HISTOGRAM_BOOLEAN("SBClientMalware.ClassificationStart", 1);
95 // Only classify [X]HTML documents.
96 if (params_
.contents_mime_type
!= "text/html" &&
97 params_
.contents_mime_type
!= "application/xhtml+xml") {
98 VLOG(1) << "Skipping phishing classification for URL: " << params_
.url
99 << " because it has an unsupported MIME type: "
100 << params_
.contents_mime_type
;
101 DontClassifyForPhishing(NO_CLASSIFY_UNSUPPORTED_MIME_TYPE
);
104 if (csd_service_
->IsPrivateIPAddress(params_
.socket_address
.host())) {
105 VLOG(1) << "Skipping phishing classification for URL: " << params_
.url
106 << " because of hosting on private IP: "
107 << params_
.socket_address
.host();
108 DontClassifyForPhishing(NO_CLASSIFY_PRIVATE_IP
);
109 DontClassifyForMalware(NO_CLASSIFY_PRIVATE_IP
);
112 // For phishing we only classify HTTP pages.
113 if (!params_
.url
.SchemeIs(url::kHttpScheme
)) {
114 VLOG(1) << "Skipping phishing classification for URL: " << params_
.url
115 << " because it is not HTTP: "
116 << params_
.socket_address
.host();
117 DontClassifyForPhishing(NO_CLASSIFY_NOT_HTTP_URL
);
120 // Don't run any classifier if the tab is incognito.
121 if (web_contents_
->GetBrowserContext()->IsOffTheRecord()) {
122 VLOG(1) << "Skipping phishing and malware classification for URL: "
123 << params_
.url
<< " because we're browsing incognito.";
124 DontClassifyForPhishing(NO_CLASSIFY_OFF_THE_RECORD
);
125 DontClassifyForMalware(NO_CLASSIFY_OFF_THE_RECORD
);
128 // We lookup the csd-whitelist before we lookup the cache because
129 // a URL may have recently been whitelisted. If the URL matches
130 // the csd-whitelist we won't start phishing classification. The
131 // csd-whitelist check has to be done on the IO thread because it
132 // uses the SafeBrowsing service class.
133 if (ShouldClassifyForPhishing() || ShouldClassifyForMalware()) {
134 BrowserThread::PostTask(
137 base::Bind(&ShouldClassifyUrlRequest::CheckSafeBrowsingDatabase
,
143 DontClassifyForPhishing(NO_CLASSIFY_CANCEL
);
144 DontClassifyForMalware(NO_CLASSIFY_CANCEL
);
145 // Just to make sure we don't do anything stupid we reset all these
146 // pointers except for the safebrowsing service class which may be
147 // accessed by CheckSafeBrowsingDatabase().
148 web_contents_
= NULL
;
154 friend class base::RefCountedThreadSafe
<
155 ClientSideDetectionHost::ShouldClassifyUrlRequest
>;
157 // Enum used to keep stats about why the pre-classification check failed.
158 enum PreClassificationCheckFailures
{
159 OBSOLETE_NO_CLASSIFY_PROXY_FETCH
,
160 NO_CLASSIFY_PRIVATE_IP
,
161 NO_CLASSIFY_OFF_THE_RECORD
,
162 NO_CLASSIFY_MATCH_CSD_WHITELIST
,
163 NO_CLASSIFY_TOO_MANY_REPORTS
,
164 NO_CLASSIFY_UNSUPPORTED_MIME_TYPE
,
165 NO_CLASSIFY_NO_DATABASE_MANAGER
,
166 NO_CLASSIFY_KILLSWITCH
,
168 NO_CLASSIFY_RESULT_FROM_CACHE
,
169 NO_CLASSIFY_NOT_HTTP_URL
,
171 NO_CLASSIFY_MAX
// Always add new values before this one.
174 // The destructor can be called either from the UI or the IO thread.
175 virtual ~ShouldClassifyUrlRequest() { }
177 bool ShouldClassifyForPhishing() const {
178 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
179 return !start_phishing_classification_cb_
.is_null();
182 bool ShouldClassifyForMalware() const {
183 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
184 return !start_malware_classification_cb_
.is_null();
187 void DontClassifyForPhishing(PreClassificationCheckFailures reason
) {
188 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
189 if (ShouldClassifyForPhishing()) {
190 // Track the first reason why we stopped classifying for phishing.
191 UMA_HISTOGRAM_ENUMERATION("SBClientPhishing.PreClassificationCheckFail",
192 reason
, NO_CLASSIFY_MAX
);
193 DVLOG(2) << "Failed phishing pre-classification checks. Reason: "
195 start_phishing_classification_cb_
.Run(false);
197 start_phishing_classification_cb_
.Reset();
200 void DontClassifyForMalware(PreClassificationCheckFailures reason
) {
201 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
202 if (ShouldClassifyForMalware()) {
203 // Track the first reason why we stopped classifying for malware.
204 UMA_HISTOGRAM_ENUMERATION("SBClientMalware.PreClassificationCheckFail",
205 reason
, NO_CLASSIFY_MAX
);
206 DVLOG(2) << "Failed malware pre-classification checks. Reason: "
208 start_malware_classification_cb_
.Run(false);
210 start_malware_classification_cb_
.Reset();
213 void CheckSafeBrowsingDatabase(const GURL
& url
) {
214 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
215 // We don't want to call the classification callbacks from the IO
216 // thread so we simply pass the results of this method to CheckCache()
217 // which is called on the UI thread;
218 PreClassificationCheckFailures phishing_reason
= NO_CLASSIFY_MAX
;
219 PreClassificationCheckFailures malware_reason
= NO_CLASSIFY_MAX
;
220 if (!database_manager_
.get()) {
221 // We cannot check the Safe Browsing whitelists so we stop here
223 malware_reason
= phishing_reason
= NO_CLASSIFY_NO_DATABASE_MANAGER
;
225 if (database_manager_
->MatchCsdWhitelistUrl(url
)) {
226 VLOG(1) << "Skipping phishing classification for URL: " << url
227 << " because it matches the csd whitelist";
228 phishing_reason
= NO_CLASSIFY_MATCH_CSD_WHITELIST
;
230 if (database_manager_
->IsMalwareKillSwitchOn()) {
231 malware_reason
= NO_CLASSIFY_KILLSWITCH
;
234 BrowserThread::PostTask(
237 base::Bind(&ShouldClassifyUrlRequest::CheckCache
,
243 void CheckCache(PreClassificationCheckFailures phishing_reason
,
244 PreClassificationCheckFailures malware_reason
) {
245 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
246 if (phishing_reason
!= NO_CLASSIFY_MAX
)
247 DontClassifyForPhishing(phishing_reason
);
248 if (malware_reason
!= NO_CLASSIFY_MAX
)
249 DontClassifyForMalware(malware_reason
);
250 if (!ShouldClassifyForMalware() && !ShouldClassifyForPhishing()) {
251 return; // No point in doing anything else.
253 // If result is cached, we don't want to run classification again.
254 // In that case we're just trying to show the warning.
256 if (csd_service_
->GetValidCachedResult(params_
.url
, &is_phishing
)) {
257 VLOG(1) << "Satisfying request for " << params_
.url
<< " from cache";
258 UMA_HISTOGRAM_BOOLEAN("SBClientPhishing.RequestSatisfiedFromCache", 1);
259 // Since we are already on the UI thread, this is safe.
260 host_
->MaybeShowPhishingWarning(params_
.url
, is_phishing
);
261 DontClassifyForPhishing(NO_CLASSIFY_RESULT_FROM_CACHE
);
264 // We want to limit the number of requests, though we will ignore the
265 // limit for urls in the cache. We don't want to start classifying
266 // too many pages as phishing, but for those that we already think are
267 // phishing we want to send a request to the server to give ourselves
268 // a chance to fix misclassifications.
269 if (csd_service_
->IsInCache(params_
.url
)) {
270 VLOG(1) << "Reporting limit skipped for " << params_
.url
271 << " as it was in the cache.";
272 UMA_HISTOGRAM_BOOLEAN("SBClientPhishing.ReportLimitSkipped", 1);
273 } else if (csd_service_
->OverPhishingReportLimit()) {
274 VLOG(1) << "Too many report phishing requests sent recently, "
275 << "not running classification for " << params_
.url
;
276 DontClassifyForPhishing(NO_CLASSIFY_TOO_MANY_REPORTS
);
278 if (csd_service_
->OverMalwareReportLimit()) {
279 DontClassifyForMalware(NO_CLASSIFY_TOO_MANY_REPORTS
);
282 // Everything checks out, so start classification.
283 // |web_contents_| is safe to call as we will be destructed
285 if (ShouldClassifyForPhishing()) {
286 start_phishing_classification_cb_
.Run(true);
287 // Reset the callback to make sure ShouldClassifyForPhishing()
289 start_phishing_classification_cb_
.Reset();
291 if (ShouldClassifyForMalware()) {
292 start_malware_classification_cb_
.Run(true);
293 // Reset the callback to make sure ShouldClassifyForMalware()
295 start_malware_classification_cb_
.Reset();
299 content::FrameNavigateParams params_
;
300 WebContents
* web_contents_
;
301 ClientSideDetectionService
* csd_service_
;
302 // We keep a ref pointer here just to make sure the safe browsing
303 // database manager stays alive long enough.
304 scoped_refptr
<SafeBrowsingDatabaseManager
> database_manager_
;
305 ClientSideDetectionHost
* host_
;
307 ShouldClassifyUrlCallback start_phishing_classification_cb_
;
308 ShouldClassifyUrlCallback start_malware_classification_cb_
;
310 DISALLOW_COPY_AND_ASSIGN(ShouldClassifyUrlRequest
);
314 ClientSideDetectionHost
* ClientSideDetectionHost::Create(
316 return new ClientSideDetectionHost(tab
);
319 ClientSideDetectionHost::ClientSideDetectionHost(WebContents
* tab
)
320 : content::WebContentsObserver(tab
),
322 classification_request_(NULL
),
323 should_extract_malware_features_(true),
324 should_classify_for_malware_(false),
325 pageload_complete_(false),
326 unsafe_unique_page_id_(-1),
327 weak_factory_(this) {
329 // Note: csd_service_ and sb_service will be NULL here in testing.
330 csd_service_
= g_browser_process
->safe_browsing_detection_service();
331 feature_extractor_
.reset(new BrowserFeatureExtractor(tab
, this));
332 registrar_
.Add(this, content::NOTIFICATION_RESOURCE_RESPONSE_STARTED
,
333 content::Source
<WebContents
>(tab
));
335 scoped_refptr
<SafeBrowsingService
> sb_service
=
336 g_browser_process
->safe_browsing_service();
337 if (sb_service
.get()) {
338 ui_manager_
= sb_service
->ui_manager();
339 database_manager_
= sb_service
->database_manager();
340 ui_manager_
->AddObserver(this);
344 ClientSideDetectionHost::~ClientSideDetectionHost() {
345 if (ui_manager_
.get())
346 ui_manager_
->RemoveObserver(this);
349 bool ClientSideDetectionHost::OnMessageReceived(const IPC::Message
& message
) {
351 IPC_BEGIN_MESSAGE_MAP(ClientSideDetectionHost
, message
)
352 IPC_MESSAGE_HANDLER(SafeBrowsingHostMsg_PhishingDetectionDone
,
353 OnPhishingDetectionDone
)
354 IPC_MESSAGE_UNHANDLED(handled
= false)
355 IPC_END_MESSAGE_MAP()
359 void ClientSideDetectionHost::DidNavigateMainFrame(
360 const content::LoadCommittedDetails
& details
,
361 const content::FrameNavigateParams
& params
) {
362 // TODO(noelutz): move this DCHECK to WebContents and fix all the unit tests
363 // that don't call this method on the UI thread.
364 // DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
365 if (details
.is_in_page
) {
366 // If the navigation is within the same page, the user isn't really
367 // navigating away. We don't need to cancel a pending callback or
368 // begin a new classification.
371 // Cancel any pending classification request.
372 if (classification_request_
.get()) {
373 classification_request_
->Cancel();
375 // If we navigate away and there currently is a pending phishing
376 // report request we have to cancel it to make sure we don't display
377 // an interstitial for the wrong page. Note that this won't cancel
378 // the server ping back but only cancel the showing of the
380 weak_factory_
.InvalidateWeakPtrs();
385 browse_info_
.reset(new BrowseInfo
);
387 // Store redirect chain information.
388 if (params
.url
.host() != cur_host_
) {
389 cur_host_
= params
.url
.host();
390 cur_host_redirects_
= params
.redirects
;
392 browse_info_
->url
= params
.url
;
393 browse_info_
->host_redirects
= cur_host_redirects_
;
394 browse_info_
->url_redirects
= params
.redirects
;
395 browse_info_
->referrer
= params
.referrer
.url
;
396 browse_info_
->http_status_code
= details
.http_status_code
;
397 browse_info_
->page_id
= params
.page_id
;
399 should_extract_malware_features_
= true;
400 should_classify_for_malware_
= false;
401 pageload_complete_
= false;
403 // Check whether we can cassify the current URL for phishing or malware.
404 classification_request_
= new ShouldClassifyUrlRequest(
406 base::Bind(&ClientSideDetectionHost::OnPhishingPreClassificationDone
,
407 weak_factory_
.GetWeakPtr()),
408 base::Bind(&ClientSideDetectionHost::OnMalwarePreClassificationDone
,
409 weak_factory_
.GetWeakPtr()),
410 web_contents(), csd_service_
, database_manager_
.get(), this);
411 classification_request_
->Start();
414 void ClientSideDetectionHost::OnSafeBrowsingHit(
415 const SafeBrowsingUIManager::UnsafeResource
& resource
) {
416 if (!web_contents() || !web_contents()->GetController().GetActiveEntry())
419 // Check that the hit is either malware or phishing.
420 if (resource
.threat_type
!= SB_THREAT_TYPE_URL_PHISHING
&&
421 resource
.threat_type
!= SB_THREAT_TYPE_URL_MALWARE
)
424 // Check that this notification is really for us.
425 content::RenderViewHost
* hit_rvh
= content::RenderViewHost::FromID(
426 resource
.render_process_host_id
, resource
.render_view_id
);
428 web_contents() != content::WebContents::FromRenderViewHost(hit_rvh
))
431 // Store the unique page ID for later.
432 unsafe_unique_page_id_
=
433 web_contents()->GetController().GetActiveEntry()->GetUniqueID();
435 // We also keep the resource around in order to be able to send the
436 // malicious URL to the server.
437 unsafe_resource_
.reset(new SafeBrowsingUIManager::UnsafeResource(resource
));
438 unsafe_resource_
->callback
.Reset(); // Don't do anything stupid.
441 void ClientSideDetectionHost::OnSafeBrowsingMatch(
442 const SafeBrowsingUIManager::UnsafeResource
& resource
) {
443 if (!web_contents() || !web_contents()->GetController().GetActiveEntry())
446 // Check that this notification is really for us.
447 content::RenderViewHost
* hit_rvh
= content::RenderViewHost::FromID(
448 resource
.render_process_host_id
, resource
.render_view_id
);
450 web_contents() != content::WebContents::FromRenderViewHost(hit_rvh
))
453 web_contents()->GetController().GetActiveEntry()->SetExtraData(
454 kSafeBrowsingMatchKey
, base::ASCIIToUTF16("1"));
457 scoped_refptr
<SafeBrowsingDatabaseManager
>
458 ClientSideDetectionHost::database_manager() {
459 return database_manager_
;
462 bool ClientSideDetectionHost::DidPageReceiveSafeBrowsingMatch() const {
463 if (!web_contents() || !web_contents()->GetController().GetVisibleEntry())
466 // If an interstitial page is showing, GetVisibleEntry will return the
467 // transient NavigationEntry for the interstitial. The transient entry
468 // will not have the flag set, so use the pending entry instead if there
470 NavigationEntry
* entry
= web_contents()->GetController().GetPendingEntry();
472 entry
= web_contents()->GetController().GetVisibleEntry();
473 if (entry
->GetPageType() == content::PAGE_TYPE_INTERSTITIAL
)
474 entry
= web_contents()->GetController().GetLastCommittedEntry();
479 base::string16 value
;
480 return entry
->GetExtraData(kSafeBrowsingMatchKey
, &value
);
483 void ClientSideDetectionHost::WebContentsDestroyed() {
484 // Tell any pending classification request that it is being canceled.
485 if (classification_request_
.get()) {
486 classification_request_
->Cancel();
488 // Cancel all pending feature extractions.
489 feature_extractor_
.reset();
492 void ClientSideDetectionHost::OnPhishingPreClassificationDone(
493 bool should_classify
) {
494 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
495 if (browse_info_
.get() && should_classify
) {
496 VLOG(1) << "Instruct renderer to start phishing detection for URL: "
497 << browse_info_
->url
;
498 content::RenderViewHost
* rvh
= web_contents()->GetRenderViewHost();
499 rvh
->Send(new SafeBrowsingMsg_StartPhishingDetection(
500 rvh
->GetRoutingID(), browse_info_
->url
));
504 void ClientSideDetectionHost::OnMalwarePreClassificationDone(
505 bool should_classify
) {
506 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
507 // If classification checks failed we should stop extracting malware features.
508 DVLOG(2) << "Malware pre-classification checks done. Should classify: "
510 should_extract_malware_features_
= should_classify
;
511 should_classify_for_malware_
= should_classify
;
512 MaybeStartMalwareFeatureExtraction();
515 void ClientSideDetectionHost::DidStopLoading(content::RenderViewHost
* rvh
) {
516 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
517 if (!csd_service_
|| !browse_info_
.get())
519 DVLOG(2) << "Page finished loading.";
520 pageload_complete_
= true;
521 MaybeStartMalwareFeatureExtraction();
524 void ClientSideDetectionHost::MaybeStartMalwareFeatureExtraction() {
525 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
526 if (csd_service_
&& browse_info_
.get() &&
527 should_classify_for_malware_
&&
528 pageload_complete_
) {
529 scoped_ptr
<ClientMalwareRequest
> malware_request(
530 new ClientMalwareRequest
);
531 // Start browser-side malware feature extraction. Once we're done it will
532 // send the malware client verdict request.
533 malware_request
->set_url(browse_info_
->url
.spec());
534 const GURL
& referrer
= browse_info_
->referrer
;
535 if (referrer
.SchemeIs("http")) { // Only send http urls.
536 malware_request
->set_referrer_url(referrer
.spec());
538 // This function doesn't expect browse_info_ to stay around after this
540 feature_extractor_
->ExtractMalwareFeatures(
542 malware_request
.release(),
543 base::Bind(&ClientSideDetectionHost::MalwareFeatureExtractionDone
,
544 weak_factory_
.GetWeakPtr()));
545 should_classify_for_malware_
= false;
549 void ClientSideDetectionHost::OnPhishingDetectionDone(
550 const std::string
& verdict_str
) {
551 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
552 // There is something seriously wrong if there is no service class but
553 // this method is called. The renderer should not start phishing detection
554 // if there isn't any service class in the browser.
555 DCHECK(csd_service_
);
556 DCHECK(browse_info_
.get());
558 // We parse the protocol buffer here. If we're unable to parse it we won't
559 // send the verdict further.
560 scoped_ptr
<ClientPhishingRequest
> verdict(new ClientPhishingRequest
);
562 browse_info_
.get() &&
563 verdict
->ParseFromString(verdict_str
) &&
564 verdict
->IsInitialized()) {
565 // We only send phishing verdict to the server if the verdict is phishing or
566 // if a SafeBrowsing interstitial was already shown for this site. E.g., a
567 // malware or phishing interstitial was shown but the user clicked
569 if (verdict
->is_phishing() || DidShowSBInterstitial()) {
570 if (DidShowSBInterstitial()) {
571 browse_info_
->unsafe_resource
.reset(unsafe_resource_
.release());
573 // Start browser-side feature extraction. Once we're done it will send
574 // the client verdict request.
575 feature_extractor_
->ExtractFeatures(
578 base::Bind(&ClientSideDetectionHost::FeatureExtractionDone
,
579 weak_factory_
.GetWeakPtr()));
584 void ClientSideDetectionHost::MaybeShowPhishingWarning(GURL phishing_url
,
586 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
587 DVLOG(2) << "Received server phishing verdict for URL:" << phishing_url
588 << " is_phishing:" << is_phishing
;
590 DCHECK(web_contents());
591 if (ui_manager_
.get()) {
592 SafeBrowsingUIManager::UnsafeResource resource
;
593 resource
.url
= phishing_url
;
594 resource
.original_url
= phishing_url
;
595 resource
.is_subresource
= false;
596 resource
.threat_type
= SB_THREAT_TYPE_CLIENT_SIDE_PHISHING_URL
;
597 resource
.render_process_host_id
=
598 web_contents()->GetRenderProcessHost()->GetID();
599 resource
.render_view_id
=
600 web_contents()->GetRenderViewHost()->GetRoutingID();
601 if (!ui_manager_
->IsWhitelisted(resource
)) {
602 // We need to stop any pending navigations, otherwise the interstital
603 // might not get created properly.
604 web_contents()->GetController().DiscardNonCommittedEntries();
606 ui_manager_
->DisplayBlockingPage(resource
);
608 // If there is true phishing verdict, invalidate weakptr so that no longer
609 // consider the malware vedict.
610 weak_factory_
.InvalidateWeakPtrs();
614 void ClientSideDetectionHost::MaybeShowMalwareWarning(GURL original_url
,
617 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
618 DVLOG(2) << "Received server malawre IP verdict for URL:" << malware_url
619 << " is_malware:" << is_malware
;
620 if (is_malware
&& malware_url
.is_valid() && original_url
.is_valid()) {
621 DCHECK(web_contents());
622 if (ui_manager_
.get()) {
623 SafeBrowsingUIManager::UnsafeResource resource
;
624 resource
.url
= malware_url
;
625 resource
.original_url
= original_url
;
626 resource
.is_subresource
= (malware_url
.host() != original_url
.host());
627 resource
.threat_type
= SB_THREAT_TYPE_CLIENT_SIDE_MALWARE_URL
;
628 resource
.render_process_host_id
=
629 web_contents()->GetRenderProcessHost()->GetID();
630 resource
.render_view_id
=
631 web_contents()->GetRenderViewHost()->GetRoutingID();
632 if (!ui_manager_
->IsWhitelisted(resource
)) {
633 // We need to stop any pending navigations, otherwise the interstital
634 // might not get created properly.
635 web_contents()->GetController().DiscardNonCommittedEntries();
637 ui_manager_
->DisplayBlockingPage(resource
);
639 // If there is true malware verdict, invalidate weakptr so that no longer
640 // consider the phishing vedict.
641 weak_factory_
.InvalidateWeakPtrs();
645 void ClientSideDetectionHost::FeatureExtractionDone(
647 scoped_ptr
<ClientPhishingRequest
> request
) {
649 DVLOG(2) << "Feature extraction done (success:" << success
<< ") for URL: "
650 << request
->url() << ". Start sending client phishing request.";
651 ClientSideDetectionService::ClientReportPhishingRequestCallback callback
;
652 // If the client-side verdict isn't phishing we don't care about the server
653 // response because we aren't going to display a warning.
654 if (request
->is_phishing()) {
655 callback
= base::Bind(&ClientSideDetectionHost::MaybeShowPhishingWarning
,
656 weak_factory_
.GetWeakPtr());
658 // Send ping even if the browser feature extraction failed.
659 csd_service_
->SendClientReportPhishingRequest(
660 request
.release(), // The service takes ownership of the request object.
664 void ClientSideDetectionHost::MalwareFeatureExtractionDone(
665 bool feature_extraction_success
,
666 scoped_ptr
<ClientMalwareRequest
> request
) {
667 DCHECK(request
.get());
668 DVLOG(2) << "Malware Feature extraction done for URL: " << request
->url()
669 << ", with badip url count:" << request
->bad_ip_url_info_size();
671 // Send ping if there is matching features.
672 if (feature_extraction_success
&& request
->bad_ip_url_info_size() > 0) {
673 VLOG(1) << "Start sending client malware request.";
674 ClientSideDetectionService::ClientReportMalwareRequestCallback callback
;
675 callback
= base::Bind(&ClientSideDetectionHost::MaybeShowMalwareWarning
,
676 weak_factory_
.GetWeakPtr());
677 csd_service_
->SendClientReportMalwareRequest(request
.release(), callback
);
681 void ClientSideDetectionHost::UpdateIPUrlMap(const std::string
& ip
,
682 const std::string
& url
,
683 const std::string
& method
,
684 const std::string
& referrer
,
685 const ResourceType resource_type
) {
686 if (ip
.empty() || url
.empty())
689 IPUrlMap::iterator it
= browse_info_
->ips
.find(ip
);
690 if (it
== browse_info_
->ips
.end()) {
691 if (browse_info_
->ips
.size() < kMaxIPsPerBrowse
) {
692 std::vector
<IPUrlInfo
> url_infos
;
693 url_infos
.push_back(IPUrlInfo(url
, method
, referrer
, resource_type
));
694 browse_info_
->ips
.insert(make_pair(ip
, url_infos
));
696 } else if (it
->second
.size() < kMaxUrlsPerIP
) {
697 it
->second
.push_back(IPUrlInfo(url
, method
, referrer
, resource_type
));
701 void ClientSideDetectionHost::Observe(
703 const content::NotificationSource
& source
,
704 const content::NotificationDetails
& details
) {
705 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
706 DCHECK_EQ(type
, content::NOTIFICATION_RESOURCE_RESPONSE_STARTED
);
707 const ResourceRequestDetails
* req
= content::Details
<ResourceRequestDetails
>(
709 if (req
&& browse_info_
.get() &&
710 should_extract_malware_features_
&& req
->url
.is_valid()) {
711 UpdateIPUrlMap(req
->socket_address
.host() /* ip */,
712 req
->url
.spec() /* url */,
719 bool ClientSideDetectionHost::DidShowSBInterstitial() const {
720 if (unsafe_unique_page_id_
<= 0 || !web_contents()) {
723 const NavigationEntry
* nav_entry
=
724 web_contents()->GetController().GetActiveEntry();
725 return (nav_entry
&& nav_entry
->GetUniqueID() == unsafe_unique_page_id_
);
728 void ClientSideDetectionHost::set_client_side_detection_service(
729 ClientSideDetectionService
* service
) {
730 csd_service_
= service
;
733 void ClientSideDetectionHost::set_safe_browsing_managers(
734 SafeBrowsingUIManager
* ui_manager
,
735 SafeBrowsingDatabaseManager
* database_manager
) {
736 if (ui_manager_
.get())
737 ui_manager_
->RemoveObserver(this);
739 ui_manager_
= ui_manager
;
741 ui_manager_
->AddObserver(this);
743 database_manager_
= database_manager
;
746 } // namespace safe_browsing