blob: cae99219a24ab67c7835507f235227e91deb4cfc [file] [log] [blame]
// Copyright 2017 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 "components/subresource_filter/content/browser/subresource_filter_safe_browsing_activation_throttle.h"
#include <sstream>
#include <utility>
#include <vector>
#include "base/metrics/histogram_macros.h"
#include "base/timer/timer.h"
#include "base/trace_event/trace_event.h"
#include "base/trace_event/trace_event_argument.h"
#include "components/subresource_filter/content/browser/content_activation_list_utils.h"
#include "components/subresource_filter/content/browser/navigation_console_logger.h"
#include "components/subresource_filter/content/browser/subresource_filter_client.h"
#include "components/subresource_filter/content/browser/subresource_filter_observer_manager.h"
#include "components/subresource_filter/content/browser/subresource_filter_safe_browsing_client.h"
#include "components/subresource_filter/core/browser/subresource_filter_constants.h"
#include "components/ukm/ukm_source.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/console_message_level.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
#include "ui/base/page_transition_types.h"
#include "url/gurl.h"
namespace subresource_filter {
SubresourceFilterSafeBrowsingActivationThrottle::
SubresourceFilterSafeBrowsingActivationThrottle(
content::NavigationHandle* handle,
SubresourceFilterClient* client,
scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
scoped_refptr<safe_browsing::SafeBrowsingDatabaseManager>
database_manager)
: NavigationThrottle(handle),
io_task_runner_(std::move(io_task_runner)),
database_client_(new SubresourceFilterSafeBrowsingClient(
std::move(database_manager),
AsWeakPtr(),
io_task_runner_,
base::ThreadTaskRunnerHandle::Get()),
base::OnTaskRunnerDeleter(io_task_runner_)),
client_(client) {
DCHECK(handle->IsInMainFrame());
CheckCurrentUrl();
DCHECK(!check_results_.empty());
}
SubresourceFilterSafeBrowsingActivationThrottle::
~SubresourceFilterSafeBrowsingActivationThrottle() = default;
content::NavigationThrottle::ThrottleCheckResult
SubresourceFilterSafeBrowsingActivationThrottle::WillRedirectRequest() {
CheckCurrentUrl();
return PROCEED;
}
content::NavigationThrottle::ThrottleCheckResult
SubresourceFilterSafeBrowsingActivationThrottle::WillProcessResponse() {
// No need to defer the navigation if the check already happened.
if (HasFinishedAllSafeBrowsingChecks()) {
NotifyResult();
return PROCEED;
}
CHECK(!deferring_);
deferring_ = true;
defer_time_ = base::TimeTicks::Now();
return DEFER;
}
const char*
SubresourceFilterSafeBrowsingActivationThrottle::GetNameForLogging() {
return "SubresourceFilterSafeBrowsingActivationThrottle";
}
void SubresourceFilterSafeBrowsingActivationThrottle::OnCheckUrlResultOnUI(
const SubresourceFilterSafeBrowsingClient::CheckResult& result) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
size_t request_id = result.request_id;
DCHECK_LT(request_id, check_results_.size());
DCHECK_LT(request_id, check_start_times_.size());
auto& stored_result = check_results_.at(request_id);
CHECK(!stored_result.finished);
stored_result = result;
UMA_HISTOGRAM_TIMES("SubresourceFilter.SafeBrowsing.TotalCheckTime",
base::TimeTicks::Now() - check_start_times_[request_id]);
if (deferring_ && HasFinishedAllSafeBrowsingChecks()) {
NotifyResult();
deferring_ = false;
Resume();
}
}
SubresourceFilterSafeBrowsingActivationThrottle::ConfigResult::ConfigResult(
Configuration config,
bool warning,
bool matched_valid_configuration,
ActivationList matched_list)
: config(config),
warning(warning),
matched_valid_configuration(matched_valid_configuration),
matched_list(matched_list) {}
SubresourceFilterSafeBrowsingActivationThrottle::ConfigResult::ConfigResult() =
default;
SubresourceFilterSafeBrowsingActivationThrottle::ConfigResult::ConfigResult(
const ConfigResult&) = default;
SubresourceFilterSafeBrowsingActivationThrottle::ConfigResult::~ConfigResult() =
default;
void SubresourceFilterSafeBrowsingActivationThrottle::CheckCurrentUrl() {
DCHECK(database_client_);
check_start_times_.push_back(base::TimeTicks::Now());
check_results_.emplace_back();
size_t id = check_results_.size() - 1;
io_task_runner_->PostTask(
FROM_HERE, base::Bind(&SubresourceFilterSafeBrowsingClient::CheckUrlOnIO,
base::Unretained(database_client_.get()),
navigation_handle()->GetURL(), id));
}
void SubresourceFilterSafeBrowsingActivationThrottle::NotifyResult() {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("loading"),
"SubresourceFilterSafeBrowsingActivationThrottle::NotifyResult");
DCHECK(!check_results_.empty());
const std::vector<SubresourceFilterSafeBrowsingClient::CheckResult>
last_result_array = {check_results_.back()};
const bool consider_redirects = base::FeatureList::IsEnabled(
kSafeBrowsingSubresourceFilterConsiderRedirects);
const auto& check_results_to_consider =
consider_redirects ? check_results_ : last_result_array;
// Find the ConfigResult for each safe browsing check.
std::vector<ConfigResult> matched_configurations;
for (const auto& current_result : check_results_to_consider) {
matched_configurations.push_back(
GetHighestPriorityConfiguration(current_result));
}
// Get the activation decision with the associated ConfigResult.
ConfigResult selection;
ActivationDecision activation_decision =
GetActivationDecision(matched_configurations, &selection);
DCHECK_NE(activation_decision, ActivationDecision::UNKNOWN);
// Notify the observers of the check results.
SubresourceFilterObserverManager::FromWebContents(
navigation_handle()->GetWebContents())
->NotifySafeBrowsingChecksComplete(navigation_handle(),
check_results_to_consider);
// Compute the activation level.
ActivationLevel activation_level =
selection.config.activation_options.activation_level;
if (selection.warning && activation_level == ActivationLevel::ENABLED) {
NavigationConsoleLogger::LogMessageOnCommit(
navigation_handle(), content::CONSOLE_MESSAGE_LEVEL_WARNING,
kActivationWarningConsoleMessage);
activation_level = ActivationLevel::DISABLED;
}
// Let the embedder get the last word when it comes to activation level.
// TODO(csharrison): Move all ActivationDecision code to the embedder.
activation_level = client_->OnPageActivationComputed(
navigation_handle(), activation_level, &activation_decision);
LogMetricsOnChecksComplete(selection.matched_list, activation_decision,
activation_level);
SubresourceFilterObserverManager::FromWebContents(
navigation_handle()->GetWebContents())
->NotifyPageActivationComputed(
navigation_handle(),
selection.config.GetActivationState(activation_level));
}
void SubresourceFilterSafeBrowsingActivationThrottle::
LogMetricsOnChecksComplete(ActivationList matched_list,
ActivationDecision decision,
ActivationLevel level) const {
DCHECK(HasFinishedAllSafeBrowsingChecks());
base::TimeDelta delay = defer_time_.is_null()
? base::TimeDelta::FromMilliseconds(0)
: base::TimeTicks::Now() - defer_time_;
UMA_HISTOGRAM_TIMES("SubresourceFilter.PageLoad.SafeBrowsingDelay", delay);
ukm::SourceId source_id = ukm::ConvertToSourceId(
navigation_handle()->GetNavigationId(), ukm::SourceIdType::NAVIGATION_ID);
ukm::builders::SubresourceFilter builder(source_id);
builder.SetActivationDecision(static_cast<int64_t>(decision));
if (level == subresource_filter::ActivationLevel::DRYRUN) {
DCHECK_EQ(subresource_filter::ActivationDecision::ACTIVATED, decision);
builder.SetDryRun(true);
}
builder.Record(ukm::UkmRecorder::Get());
UMA_HISTOGRAM_ENUMERATION("SubresourceFilter.PageLoad.ActivationDecision",
decision,
ActivationDecision::ACTIVATION_DECISION_MAX);
UMA_HISTOGRAM_ENUMERATION("SubresourceFilter.PageLoad.ActivationList",
matched_list,
static_cast<int>(ActivationList::LAST) + 1);
}
bool SubresourceFilterSafeBrowsingActivationThrottle::
HasFinishedAllSafeBrowsingChecks() const {
for (const auto& check_result : check_results_) {
if (!check_result.finished) {
return false;
}
}
return true;
}
SubresourceFilterSafeBrowsingActivationThrottle::ConfigResult
SubresourceFilterSafeBrowsingActivationThrottle::
GetHighestPriorityConfiguration(
const SubresourceFilterSafeBrowsingClient::CheckResult& result) {
DCHECK(result.finished);
Configuration selected_config;
bool warning = false;
bool matched = false;
ActivationList matched_list = GetListForThreatTypeAndMetadata(
result.threat_type, result.threat_metadata, &warning);
// If it's http or https, find the best config.
if (navigation_handle()->GetURL().SchemeIsHTTPOrHTTPS()) {
const auto& decreasing_configs =
GetEnabledConfigurations()->configs_by_decreasing_priority();
const auto selected_config_itr =
std::find_if(decreasing_configs.begin(), decreasing_configs.end(),
[matched_list, this](const Configuration& config) {
return DoesMainFrameURLSatisfyActivationConditions(
config.activation_conditions, matched_list);
});
if (selected_config_itr != decreasing_configs.end()) {
selected_config = *selected_config_itr;
matched = true;
}
}
TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("loading"),
"SubresourceFilterSafeBrowsingActivationThrottle::"
"GetHighestPriorityConfiguration",
"selected_config",
!matched ? selected_config.ToTracedValue()
: std::make_unique<base::trace_event::TracedValue>());
return ConfigResult(selected_config, warning, matched, matched_list);
}
ActivationDecision
SubresourceFilterSafeBrowsingActivationThrottle::GetActivationDecision(
const std::vector<ConfigResult>& configurations,
ConfigResult* selected_config) {
auto selected_itr = configurations.begin();
for (auto current_itr = configurations.begin();
current_itr != configurations.end(); current_itr++) {
// Prefer later configs when there's a tie.
// Rank no matching config slightly below priority zero.
const auto selected_priority =
selected_itr->matched_valid_configuration
? selected_itr->config.activation_conditions.priority
: -1;
const auto current_priority =
current_itr->matched_valid_configuration
? current_itr->config.activation_conditions.priority
: -1;
if (current_priority >= selected_priority) {
selected_itr = current_itr;
}
}
// Ensure that the list was not empty, and assign the configuration.
DCHECK(selected_itr != configurations.end());
*selected_config = *selected_itr;
if (!selected_itr->matched_valid_configuration) {
return ActivationDecision::ACTIVATION_CONDITIONS_NOT_MET;
}
auto activation_level =
selected_config->config.activation_options.activation_level;
return activation_level == ActivationLevel::DISABLED
? ActivationDecision::ACTIVATION_DISABLED
: ActivationDecision::ACTIVATED;
}
bool SubresourceFilterSafeBrowsingActivationThrottle::
DoesMainFrameURLSatisfyActivationConditions(
const Configuration::ActivationConditions& conditions,
ActivationList matched_list) const {
// Avoid copies when tracing disabled.
auto list_to_string = [](ActivationList activation_list) {
std::ostringstream matched_list_stream;
matched_list_stream << activation_list;
return matched_list_stream.str();
};
TRACE_EVENT2(TRACE_DISABLED_BY_DEFAULT("loading"),
"SubresourceFilterSafeBrowsingActivationThrottle::"
"DoesMainFrameURLSatisfyActivationConditions",
"matched_list", list_to_string(matched_list), "conditions",
conditions.ToTracedValue());
switch (conditions.activation_scope) {
case ActivationScope::ALL_SITES:
return true;
case ActivationScope::ACTIVATION_LIST:
if (matched_list == ActivationList::NONE)
return false;
if (conditions.activation_list == matched_list)
return true;
if (conditions.activation_list == ActivationList::PHISHING_INTERSTITIAL &&
matched_list == ActivationList::SOCIAL_ENG_ADS_INTERSTITIAL) {
// Handling special case, where activation on the phishing sites also
// mean the activation on the sites with social engineering metadata.
return true;
}
return false;
case ActivationScope::NO_SITES:
return false;
}
NOTREACHED();
return false;
}
} // namespace subresource_filter