blob: b6a9663ddd160481c2e19b6434792b2124d87825 [file] [log] [blame]
// 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.
//
// Implementation of the SafeBrowsingBlockingPage class.
#include "chrome/browser/safe_browsing/safe_browsing_blocking_page.h"
#include "base/command_line.h"
#include "base/lazy_instance.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/interstitials/chrome_controller_client.h"
#include "chrome/browser/interstitials/chrome_metrics_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/renderer_preferences_util.h"
#include "chrome/browser/safe_browsing/threat_details.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "components/prefs/pref_service.h"
#include "components/safe_browsing_db/safe_browsing_prefs.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/interstitial_page.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/web_contents.h"
using content::BrowserThread;
using content::InterstitialPage;
using content::WebContents;
using security_interstitials::SafeBrowsingErrorUI;
using security_interstitials::SecurityInterstitialControllerClient;
namespace safe_browsing {
namespace {
// After a safe browsing interstitial where the user opted-in to the report
// but clicked "proceed anyway", we delay the call to
// ThreatDetails::FinishCollection() by this much time (in
// milliseconds).
const int64_t kThreatDetailsProceedDelayMilliSeconds = 3000;
// Constants for the Experience Sampling instrumentation.
const char kEventNameMalware[] = "safebrowsing_interstitial_";
const char kEventNameHarmful[] = "harmful_interstitial_";
const char kEventNamePhishing[] = "phishing_interstitial_";
const char kEventNameOther[] = "safebrowsing_other_interstitial_";
} // namespace
// static
SafeBrowsingBlockingPageFactory* SafeBrowsingBlockingPage::factory_ = NULL;
// The default SafeBrowsingBlockingPageFactory. Global, made a singleton so we
// don't leak it.
class SafeBrowsingBlockingPageFactoryImpl
: public SafeBrowsingBlockingPageFactory {
public:
SafeBrowsingBlockingPage* CreateSafeBrowsingPage(
BaseUIManager* ui_manager,
WebContents* web_contents,
const GURL& main_frame_url,
const SafeBrowsingBlockingPage::UnsafeResourceList& unsafe_resources)
override {
// Create appropriate display options for this blocking page.
PrefService* prefs =
Profile::FromBrowserContext(web_contents->GetBrowserContext())
->GetPrefs();
bool is_extended_reporting_opt_in_allowed =
prefs->GetBoolean(prefs::kSafeBrowsingExtendedReportingOptInAllowed);
bool is_proceed_anyway_disabled =
prefs->GetBoolean(prefs::kSafeBrowsingProceedAnywayDisabled);
// Determine if any prefs need to be updated prior to showing the security
// interstitial. This must happen before querying IsScout to populate the
// Display Options below.
safe_browsing::UpdatePrefsBeforeSecurityInterstitial(prefs);
SafeBrowsingErrorUI::SBErrorDisplayOptions display_options(
BaseBlockingPage::IsMainPageLoadBlocked(unsafe_resources),
is_extended_reporting_opt_in_allowed,
web_contents->GetBrowserContext()->IsOffTheRecord(),
IsExtendedReportingEnabled(*prefs), IsScout(*prefs),
is_proceed_anyway_disabled);
return new SafeBrowsingBlockingPage(ui_manager, web_contents,
main_frame_url, unsafe_resources,
display_options);
}
private:
friend struct base::DefaultLazyInstanceTraits<
SafeBrowsingBlockingPageFactoryImpl>;
SafeBrowsingBlockingPageFactoryImpl() { }
DISALLOW_COPY_AND_ASSIGN(SafeBrowsingBlockingPageFactoryImpl);
};
static base::LazyInstance<SafeBrowsingBlockingPageFactoryImpl>
g_safe_browsing_blocking_page_factory_impl = LAZY_INSTANCE_INITIALIZER;
// static
content::InterstitialPageDelegate::TypeID
SafeBrowsingBlockingPage::kTypeForTesting =
&SafeBrowsingBlockingPage::kTypeForTesting;
SafeBrowsingBlockingPage::SafeBrowsingBlockingPage(
BaseUIManager* ui_manager,
WebContents* web_contents,
const GURL& main_frame_url,
const UnsafeResourceList& unsafe_resources,
const SafeBrowsingErrorUI::SBErrorDisplayOptions& display_options)
: BaseBlockingPage(
ui_manager,
web_contents,
unsafe_resources[0].url,
unsafe_resources,
CreateControllerClient(web_contents, unsafe_resources),
display_options),
threat_details_proceed_delay_ms_(kThreatDetailsProceedDelayMilliSeconds) {
// Start computing threat details. They will be sent only
// if the user opts-in on the blocking page later.
// If there's more than one malicious resources, it means the user
// clicked through the first warning, so we don't prepare additional
// reports.
if (unsafe_resources.size() == 1 &&
ShouldReportThreatDetails(unsafe_resources[0].threat_type) &&
threat_details_.get() == NULL &&
sb_error_ui()->CanShowExtendedReportingOption()) {
threat_details_ = ThreatDetails::NewThreatDetails(ui_manager, web_contents,
unsafe_resources[0]);
}
}
bool SafeBrowsingBlockingPage::ShouldReportThreatDetails(
SBThreatType threat_type) {
return threat_type == SB_THREAT_TYPE_URL_PHISHING ||
threat_type == SB_THREAT_TYPE_URL_MALWARE ||
threat_type == SB_THREAT_TYPE_URL_UNWANTED ||
threat_type == SB_THREAT_TYPE_CLIENT_SIDE_PHISHING_URL ||
threat_type == SB_THREAT_TYPE_CLIENT_SIDE_MALWARE_URL;
}
SafeBrowsingBlockingPage::~SafeBrowsingBlockingPage() {
}
void SafeBrowsingBlockingPage::OverrideRendererPrefs(
content::RendererPreferences* prefs) {
Profile* profile = Profile::FromBrowserContext(
web_contents()->GetBrowserContext());
renderer_preferences_util::UpdateFromSystemSettings(
prefs, profile, web_contents());
}
void SafeBrowsingBlockingPage::OnProceed() {
set_proceeded(true);
UpdateMetricsAfterSecurityInterstitial();
// Send the threat details, if we opted to.
FinishThreatDetails(
base::TimeDelta::FromMilliseconds(threat_details_proceed_delay_ms_),
true, /* did_proceed */
controller()->metrics_helper()->NumVisits());
ui_manager()->OnBlockingPageDone(unsafe_resources(), true, web_contents(),
main_frame_url());
// Check to see if some new notifications of unsafe resources have been
// received while we were showing the interstitial.
UnsafeResourceMap* unsafe_resource_map = GetUnsafeResourcesMap();
UnsafeResourceMap::iterator iter = unsafe_resource_map->find(web_contents());
SafeBrowsingBlockingPage* blocking_page = NULL;
if (iter != unsafe_resource_map->end() && !iter->second.empty()) {
// All queued unsafe resources should be for the same page:
content::NavigationEntry* entry =
iter->second[0].GetNavigationEntryForResource();
// Build an interstitial for all the unsafe resources notifications.
// Don't show it now as showing an interstitial while an interstitial is
// already showing would cause DontProceed() to be invoked.
blocking_page = factory_->CreateSafeBrowsingPage(
ui_manager(), web_contents(), entry ? entry->GetURL() : GURL(),
iter->second);
unsafe_resource_map->erase(iter);
}
// Now that this interstitial is gone, we can show the new one.
if (blocking_page)
blocking_page->Show();
}
content::InterstitialPageDelegate::TypeID
SafeBrowsingBlockingPage::GetTypeForTesting() const {
return SafeBrowsingBlockingPage::kTypeForTesting;
}
void SafeBrowsingBlockingPage::FinishThreatDetails(const base::TimeDelta& delay,
bool did_proceed,
int num_visits) {
if (threat_details_.get() == NULL)
return; // Not all interstitials have threat details (eg., incognito mode).
const bool enabled =
sb_error_ui()->is_extended_reporting_enabled() &&
sb_error_ui()->is_extended_reporting_opt_in_allowed();
if (!enabled)
return;
controller()->metrics_helper()->RecordUserInteraction(
security_interstitials::MetricsHelper::EXTENDED_REPORTING_IS_ENABLED);
// Finish the malware details collection, send it over.
BrowserThread::PostDelayedTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&ThreatDetails::FinishCollection, threat_details_,
did_proceed, num_visits),
delay);
}
// static
SafeBrowsingBlockingPage* SafeBrowsingBlockingPage::CreateBlockingPage(
BaseUIManager* ui_manager,
WebContents* web_contents,
const GURL& main_frame_url,
const UnsafeResource& unsafe_resource) {
const UnsafeResourceList resources{unsafe_resource};
// Set up the factory if this has not been done already (tests do that
// before this method is called).
if (!factory_)
factory_ = g_safe_browsing_blocking_page_factory_impl.Pointer();
return factory_->CreateSafeBrowsingPage(ui_manager, web_contents,
main_frame_url, resources);
}
// static
void SafeBrowsingBlockingPage::ShowBlockingPage(
BaseUIManager* ui_manager,
const UnsafeResource& unsafe_resource) {
DVLOG(1) << __func__ << " " << unsafe_resource.url.spec();
WebContents* web_contents = unsafe_resource.web_contents_getter.Run();
if (!InterstitialPage::GetInterstitialPage(web_contents) ||
!unsafe_resource.is_subresource) {
// There is no interstitial currently showing in that tab, or we are about
// to display a new one for the main frame. If there is already an
// interstitial, showing the new one will automatically hide the old one.
content::NavigationEntry* entry =
unsafe_resource.GetNavigationEntryForResource();
SafeBrowsingBlockingPage* blocking_page =
CreateBlockingPage(ui_manager, web_contents,
entry ? entry->GetURL() : GURL(), unsafe_resource);
blocking_page->Show();
return;
}
// This is an interstitial for a page's resource, let's queue it.
UnsafeResourceMap* unsafe_resource_map = GetUnsafeResourcesMap();
(*unsafe_resource_map)[web_contents].push_back(unsafe_resource);
}
// static
std::string SafeBrowsingBlockingPage::GetSamplingEventName(
SafeBrowsingErrorUI::SBInterstitialReason interstitial_reason) {
switch (interstitial_reason) {
case SafeBrowsingErrorUI::SB_REASON_MALWARE:
return kEventNameMalware;
case SafeBrowsingErrorUI::SB_REASON_HARMFUL:
return kEventNameHarmful;
case SafeBrowsingErrorUI::SB_REASON_PHISHING:
return kEventNamePhishing;
default:
return kEventNameOther;
}
}
// static
std::unique_ptr<security_interstitials::SecurityInterstitialControllerClient>
SafeBrowsingBlockingPage::CreateControllerClient(
WebContents* web_contents,
const UnsafeResourceList& unsafe_resources) {
SafeBrowsingErrorUI::SBInterstitialReason interstitial_reason =
GetInterstitialReason(unsafe_resources);
GURL request_url(unsafe_resources[0].url);
security_interstitials::MetricsHelper::ReportDetails reporting_info;
reporting_info.metric_prefix =
GetMetricPrefix(unsafe_resources, interstitial_reason);
reporting_info.extra_suffix = GetExtraMetricsSuffix(unsafe_resources);
std::unique_ptr<ChromeMetricsHelper> metrics_helper =
base::MakeUnique<ChromeMetricsHelper>(
web_contents, request_url, reporting_info,
GetSamplingEventName(interstitial_reason));
Profile* profile = Profile::FromBrowserContext(
web_contents->GetBrowserContext());
DCHECK(profile);
return base::MakeUnique<
security_interstitials::SecurityInterstitialControllerClient>(
web_contents,
std::move(metrics_helper),
profile->GetPrefs(),
g_browser_process->GetApplicationLocale(),
GURL(chrome::kChromeUINewTabURL));
}
} // namespace safe_browsing