// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/safe_browsing/ui_manager.h" #include "base/bind.h" #include "base/bind_helpers.h" #include "base/callback.h" #include "base/debug/leak_tracker.h" #include "base/stl_util.h" #include "base/strings/string_util.h" #include "base/threading/thread.h" #include "base/threading/thread_restrictions.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/metrics/metrics_service.h" #include "chrome/browser/safe_browsing/malware_details.h" #include "chrome/browser/safe_browsing/ping_manager.h" #include "chrome/browser/safe_browsing/safe_browsing_blocking_page.h" #include "chrome/browser/safe_browsing/safe_browsing_service.h" #include "chrome/browser/tab_contents/tab_util.h" #include "chrome/common/url_constants.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/navigation_entry.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/web_contents.h" #include "net/base/registry_controlled_domains/registry_controlled_domain.h" #include "net/url_request/url_request_context.h" #include "net/url_request/url_request_context_getter.h" using content::BrowserThread; using content::NavigationEntry; using content::WebContents; struct SafeBrowsingUIManager::WhiteListedEntry { int render_process_host_id; int render_view_id; std::string domain; SBThreatType threat_type; }; SafeBrowsingUIManager::UnsafeResource::UnsafeResource() : is_subresource(false), threat_type(SB_THREAT_TYPE_SAFE), render_process_host_id(-1), render_view_id(-1) { } SafeBrowsingUIManager::UnsafeResource::~UnsafeResource() { } SafeBrowsingUIManager::SafeBrowsingUIManager( const scoped_refptr& service) : sb_service_(service) { } SafeBrowsingUIManager::~SafeBrowsingUIManager() { } void SafeBrowsingUIManager::StopOnIOThread(bool shutdown) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); if (shutdown) sb_service_ = NULL; } void SafeBrowsingUIManager::LogPauseDelay(base::TimeDelta time) { UMA_HISTOGRAM_LONG_TIMES("SB2.Delay", time); } // Only report SafeBrowsing related stats when UMA is enabled. User must also // ensure that safe browsing is enabled from the calling profile. bool SafeBrowsingUIManager::CanReportStats() const { const MetricsService* metrics = g_browser_process->metrics_service(); return metrics && metrics->reporting_active(); } void SafeBrowsingUIManager::OnBlockingPageDone( const std::vector& resources, bool proceed) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); for (std::vector::const_iterator iter = resources.begin(); iter != resources.end(); ++iter) { const UnsafeResource& resource = *iter; if (!resource.callback.is_null()) resource.callback.Run(proceed); if (proceed) { BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(&SafeBrowsingUIManager::UpdateWhitelist, this, resource)); } } } void SafeBrowsingUIManager::DisplayBlockingPage( const UnsafeResource& resource) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); // Indicate to interested observers that the resource in question matched the // SB filters. If the resource is already whitelisted, OnSafeBrowsingHit // won't be called. if (resource.threat_type != SB_THREAT_TYPE_SAFE) { FOR_EACH_OBSERVER(Observer, observer_list_, OnSafeBrowsingMatch(resource)); } // Check if the user has already ignored our warning for this render_view // and domain. if (IsWhitelisted(resource)) { if (!resource.callback.is_null()) { BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, base::Bind(resource.callback, true)); } return; } // The tab might have been closed. WebContents* web_contents = tab_util::GetWebContentsByID(resource.render_process_host_id, resource.render_view_id); if (!web_contents) { // The tab is gone and we did not have a chance at showing the interstitial. // Just act as if "Don't Proceed" were chosen. std::vector resources; resources.push_back(resource); BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, base::Bind(&SafeBrowsingUIManager::OnBlockingPageDone, this, resources, false)); return; } if (resource.threat_type != SB_THREAT_TYPE_SAFE && CanReportStats()) { GURL page_url = web_contents->GetURL(); GURL referrer_url; NavigationEntry* entry = web_contents->GetController().GetActiveEntry(); if (entry) referrer_url = entry->GetReferrer().url; // When the malicious url is on the main frame, and resource.original_url // is not the same as the resource.url, that means we have a redirect from // resource.original_url to resource.url. // Also, at this point, page_url points to the _previous_ page that we // were on. We replace page_url with resource.original_url and referrer // with page_url. if (!resource.is_subresource && !resource.original_url.is_empty() && resource.original_url != resource.url) { referrer_url = page_url; page_url = resource.original_url; } ReportSafeBrowsingHit(resource.url, page_url, referrer_url, resource.is_subresource, resource.threat_type, std::string() /* post_data */); } if (resource.threat_type != SB_THREAT_TYPE_SAFE) { FOR_EACH_OBSERVER(Observer, observer_list_, OnSafeBrowsingHit(resource)); } SafeBrowsingBlockingPage::ShowBlockingPage(this, resource); } // A safebrowsing hit is sent after a blocking page for malware/phishing // or after the warning dialog for download urls, only for UMA users. void SafeBrowsingUIManager::ReportSafeBrowsingHit( const GURL& malicious_url, const GURL& page_url, const GURL& referrer_url, bool is_subresource, SBThreatType threat_type, const std::string& post_data) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); if (!CanReportStats()) return; BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, base::Bind(&SafeBrowsingUIManager::ReportSafeBrowsingHitOnIOThread, this, malicious_url, page_url, referrer_url, is_subresource, threat_type, post_data)); } void SafeBrowsingUIManager::AddObserver(Observer* observer) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); observer_list_.AddObserver(observer); } void SafeBrowsingUIManager::RemoveObserver(Observer* observer) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); observer_list_.RemoveObserver(observer); } void SafeBrowsingUIManager::ReportSafeBrowsingHitOnIOThread( const GURL& malicious_url, const GURL& page_url, const GURL& referrer_url, bool is_subresource, SBThreatType threat_type, const std::string& post_data) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); // The service may delete the ping manager (i.e. when user disabling service, // etc). This happens on the IO thread. if (sb_service_.get() == NULL || sb_service_->ping_manager() == NULL) return; DVLOG(1) << "ReportSafeBrowsingHit: " << malicious_url << " " << page_url << " " << referrer_url << " " << is_subresource << " " << threat_type; sb_service_->ping_manager()->ReportSafeBrowsingHit( malicious_url, page_url, referrer_url, is_subresource, threat_type, post_data); } // If the user had opted-in to send MalwareDetails, this gets called // when the report is ready. void SafeBrowsingUIManager::SendSerializedMalwareDetails( const std::string& serialized) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); // The service may delete the ping manager (i.e. when user disabling service, // etc). This happens on the IO thread. if (sb_service_.get() == NULL || sb_service_->ping_manager() == NULL) return; if (!serialized.empty()) { DVLOG(1) << "Sending serialized malware details."; sb_service_->ping_manager()->ReportMalwareDetails(serialized); } } void SafeBrowsingUIManager::UpdateWhitelist(const UnsafeResource& resource) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); // Whitelist this domain and warning type for the given tab. WhiteListedEntry entry; entry.render_process_host_id = resource.render_process_host_id; entry.render_view_id = resource.render_view_id; entry.domain = net::registry_controlled_domains::GetDomainAndRegistry( resource.url, net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES); entry.threat_type = resource.threat_type; white_listed_entries_.push_back(entry); } bool SafeBrowsingUIManager::IsWhitelisted(const UnsafeResource& resource) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); // Check if the user has already ignored our warning for this render_view // and domain. for (size_t i = 0; i < white_listed_entries_.size(); ++i) { const WhiteListedEntry& entry = white_listed_entries_[i]; if (entry.render_process_host_id == resource.render_process_host_id && entry.render_view_id == resource.render_view_id && // Threat type must be the same or they can either be client-side // phishing/malware URL or a SafeBrowsing phishing/malware URL. // If we show one type of phishing/malware warning we don't want to show // a second phishing/malware warning. (entry.threat_type == resource.threat_type || (entry.threat_type == SB_THREAT_TYPE_URL_PHISHING && resource.threat_type == SB_THREAT_TYPE_CLIENT_SIDE_PHISHING_URL) || (entry.threat_type == SB_THREAT_TYPE_CLIENT_SIDE_PHISHING_URL && resource.threat_type == SB_THREAT_TYPE_URL_PHISHING) || (entry.threat_type == SB_THREAT_TYPE_URL_MALWARE && resource.threat_type == SB_THREAT_TYPE_CLIENT_SIDE_MALWARE_URL) || (entry.threat_type == SB_THREAT_TYPE_CLIENT_SIDE_MALWARE_URL && resource.threat_type == SB_THREAT_TYPE_URL_MALWARE))) { return entry.domain == net::registry_controlled_domains::GetDomainAndRegistry( resource.url, net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES); } } return false; }