| // 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/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 "components/metrics/metrics_service.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<SafeBrowsingService>& 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<UnsafeResource>& resources, |
| bool proceed) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| for (std::vector<UnsafeResource>::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<UnsafeResource> 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; |
| } |
| |