// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/components/security_interstitials/safe_browsing/safe_browsing_tab_helper.h"
#import <Foundation/Foundation.h>
#import "base/containers/contains.h"
#import "base/feature_list.h"
#import "base/functional/bind.h"
#import "base/metrics/histogram_functions.h"
#import "base/metrics/histogram_macros.h"
#import "base/strings/sys_string_conversions.h"
#import "components/safe_browsing/core/browser/safe_browsing_url_checker_impl.h"
#import "components/safe_browsing/core/common/features.h"
#import "components/safe_browsing/core/common/safebrowsing_constants.h"
#import "components/safe_browsing/ios/browser/safe_browsing_url_allow_list.h"
#import "ios/components/security_interstitials/safe_browsing/safe_browsing_client.h"
#import "ios/components/security_interstitials/safe_browsing/safe_browsing_error.h"
#import "ios/components/security_interstitials/safe_browsing/safe_browsing_service.h"
#import "ios/components/security_interstitials/safe_browsing/safe_browsing_tab_helper_delegate.h"
#import "ios/components/security_interstitials/safe_browsing/safe_browsing_unsafe_resource_container.h"
#import "ios/web/public/navigation/navigation_context.h"
#import "ios/web/public/navigation/navigation_item.h"
#import "ios/web/public/navigation/navigation_manager.h"
#import "ios/web/public/thread/web_task_traits.h"
#import "net/base/apple/url_conversions.h"
using safe_browsing::SafeBrowsingUrlCheckerImpl;
using security_interstitials::UnsafeResource;
namespace {
// Creates a PolicyDecision that cancels a navigation to show a safe browsing
// error.
web::WebStatePolicyDecider::PolicyDecision CreateSafeBrowsingErrorDecision() {
return web::WebStatePolicyDecider::PolicyDecision::CancelAndDisplayError(
[NSError errorWithDomain:kSafeBrowsingErrorDomain
code:kUnsafeResourceErrorCode
userInfo:nil]);
}
// Returns a canonicalized version of `url` as used by the SafeBrowsingService.
GURL GetCanonicalizedUrl(const GURL& url) {
std::string hostname;
std::string path;
std::string query;
safe_browsing::V4ProtocolManagerUtil::CanonicalizeUrl(url, &hostname, &path,
&query);
GURL::Replacements replacements;
if (hostname.length())
replacements.SetHostStr(hostname);
if (path.length())
replacements.SetPathStr(path);
if (query.length())
replacements.SetQueryStr(query);
replacements.ClearRef();
return url.ReplaceComponents(replacements);
}
// Records a histogram tracking how often Safe Browsing delays navigations.
void RecordCheckCompletedOnResponseMetric(bool check_completed) {
base::UmaHistogramBoolean(
"SafeBrowsing.IOS.IsCheckCompletedOnShouldAllowResponse",
check_completed);
}
// Records histograms tracking the amount of time that navigations are delayed
// by Safe Browsing, broken down by the type of Safe Browsing check that was
// performed. Unlike `RecordTotalDelayMetricForDelayedAllowedNavigation`, this
// should be called for all completed checks, even those that don't cause any
// delay and those that are blocked by Safe Browsing.
void RecordTotalDelay2MetricForNavigation(
base::TimeDelta delay,
SafeBrowsingUrlCheckerImpl::PerformedCheck performed_check) {
std::string performed_check_str;
switch (performed_check) {
case SafeBrowsingUrlCheckerImpl::PerformedCheck::kUrlRealTimeCheck:
performed_check_str = ".FullUrlLookup";
break;
case SafeBrowsingUrlCheckerImpl::PerformedCheck::kHashDatabaseCheck:
performed_check_str = ".HashPrefixDatabaseCheck";
break;
case SafeBrowsingUrlCheckerImpl::PerformedCheck::kCheckSkipped:
performed_check_str = ".SkippedCheck";
break;
case SafeBrowsingUrlCheckerImpl::PerformedCheck::kHashRealTimeCheck:
performed_check_str = ".HashPrefixRealTimeCheck";
break;
case SafeBrowsingUrlCheckerImpl::PerformedCheck::kUnknown:
NOTREACHED_IN_MIGRATION();
}
base::UmaHistogramTimes("SafeBrowsing.IOS.TotalDelay2" + performed_check_str,
delay);
}
// Records a histogram tracking the amount of time that navigations are delayed
// by Safe Browsing but ultimately allowed to proceed.
void RecordTotalDelayMetricForDelayedAllowedNavigation(
base::TimeTicks delay_start_time,
SafeBrowsingUrlCheckerImpl::PerformedCheck performed_check) {
base::TimeDelta delay = base::TimeTicks::Now() - delay_start_time;
base::UmaHistogramTimes("SafeBrowsing.IOS.TotalDelay", delay);
RecordTotalDelay2MetricForNavigation(delay, performed_check);
}
} // namespace
#pragma mark - SafeBrowsingTabHelper
SafeBrowsingTabHelper::SafeBrowsingTabHelper(web::WebState* web_state,
SafeBrowsingClient* client)
: policy_decider_(web_state, client),
query_observer_(web_state, &policy_decider_),
navigation_observer_(web_state, &policy_decider_) {}
SafeBrowsingTabHelper::~SafeBrowsingTabHelper() = default;
WEB_STATE_USER_DATA_KEY_IMPL(SafeBrowsingTabHelper)
void SafeBrowsingTabHelper::SetDelegate(
id<SafeBrowsingTabHelperDelegate> delegate) {
delegate_ = delegate;
}
void SafeBrowsingTabHelper::RemoveDelegate() {
delegate_ = nil;
}
void SafeBrowsingTabHelper::OpenSafeBrowsingSettings() {
if (delegate_) {
[delegate_ openSafeBrowsingSettings];
}
}
void SafeBrowsingTabHelper::ShowEnhancedSafeBrowsingInfobar() {
if (delegate_) {
[delegate_ showEnhancedSafeBrowsingInfobar];
}
}
#pragma mark - SafeBrowsingTabHelper::PolicyDecider
SafeBrowsingTabHelper::PolicyDecider::PolicyDecider(web::WebState* web_state,
SafeBrowsingClient* client)
: web::WebStatePolicyDecider(web_state),
query_manager_(SafeBrowsingQueryManager::FromWebState(web_state)),
client_(client) {}
SafeBrowsingTabHelper::PolicyDecider::~PolicyDecider() = default;
bool SafeBrowsingTabHelper::PolicyDecider::IsQueryStale(
const SafeBrowsingQueryManager::Query& query) {
const GURL& url = query.url;
return !GetOldestPendingMainFrameQuery(url);
}
bool SafeBrowsingTabHelper::PolicyDecider::IsQueryStale(
const SafeBrowsingQueryManager::QueryData& query_data) {
if (query_data.type == QueryType::kSync) {
return !GetOldestPendingMainFrameQuery(query_data);
}
return !GetOldestPendingMainFrameQuery(query_data) &&
!GetOldestPendingToBeCommittedQuery(query_data) &&
!GetOldestPendingCommittedQuery(query_data);
}
web::WebStatePolicyDecider::PolicyDecision
SafeBrowsingTabHelper::PolicyDecider::CreatePolicyDecision(
const SafeBrowsingQueryManager::Query& query,
const SafeBrowsingQueryManager::Result& result,
web::WebState* web_state) {
// Create a policy decision using the query result.
web::WebStatePolicyDecider::PolicyDecision policy_decision =
web::WebStatePolicyDecider::PolicyDecision::Allow();
if (result.show_error_page) {
policy_decision = CreateSafeBrowsingErrorDecision();
} else if (!result.proceed) {
policy_decision = web::WebStatePolicyDecider::PolicyDecision::Cancel();
}
// If an error page needs to be displayed, record the pending decision and
// store the unsafe resource.
if (policy_decision.ShouldDisplayError()) {
DCHECK(result.resource);
// Store the navigation URL for the resource.
UnsafeResource resource = *result.resource;
resource.navigation_url = query.url;
SafeBrowsingUrlAllowList::FromWebState(web_state)
->AddPendingUnsafeNavigationDecision(resource.navigation_url,
resource.threat_type);
// Store the UnsafeResource to be fetched later to populate the error page.
SafeBrowsingUnsafeResourceContainer* container =
SafeBrowsingUnsafeResourceContainer::FromWebState(web_state);
container->StoreMainFrameUnsafeResource(resource);
}
return policy_decision;
}
void SafeBrowsingTabHelper::PolicyDecider::HandlePolicyDecision(
const SafeBrowsingQueryManager::Query& query,
const web::WebStatePolicyDecider::PolicyDecision& policy_decision,
SafeBrowsingUrlCheckerImpl::PerformedCheck performed_check) {
DCHECK(!IsQueryStale(query));
OnMainFrameUrlQueryDecided(query.url, policy_decision, performed_check);
}
void SafeBrowsingTabHelper::PolicyDecider::HandlePolicyDecision(
const SafeBrowsingQueryManager::QueryData& query_data,
const web::WebStatePolicyDecider::PolicyDecision& policy_decision) {
DCHECK(!IsQueryStale(query_data));
if (query_data.type == QueryType::kAsync) {
OnMainFrameUrlAsyncQueryDecided(query_data, policy_decision);
} else if (query_data.type == QueryType::kSync) {
OnMainFrameUrlSyncQueryDecided(query_data, policy_decision);
}
}
void SafeBrowsingTabHelper::PolicyDecider::UpdateForMainFrameDocumentChange() {
// TODO(crbug.com/41491791): Update state for async checks on document change.
}
void SafeBrowsingTabHelper::PolicyDecider::UpdateForMainFrameServerRedirect() {
// The current `pending_main_frame_query_` is a server redirect from
// `previous_main_frame_query_`, so add the latter to the pending redirect
// chain. However, when a URL redirects to itself, ShouldAllowRequest may not
// be called again, and in that case `previous_main_frame_query_` will not
// have a new URL to add to the redirect chain.
if (previous_main_frame_query_) {
pending_main_frame_redirect_chain_.push_back(
std::move(*previous_main_frame_query_));
previous_main_frame_query_ = std::nullopt;
}
}
#pragma mark web::WebStatePolicyDecider
void SafeBrowsingTabHelper::PolicyDecider::ShouldAllowRequest(
NSURLRequest* request,
web::WebStatePolicyDecider::RequestInfo request_info,
web::WebStatePolicyDecider::PolicyDecisionCallback callback) {
bool is_main_frame = request_info.target_frame_is_main;
if (!is_main_frame) {
std::move(callback).Run(
web::WebStatePolicyDecider::PolicyDecision::Allow());
return;
}
// Allow navigations for URLs that cannot be checked by the service.
GURL request_url = GetCanonicalizedUrl(net::GURLWithNSURL(request.URL));
SafeBrowsingService* safe_browsing_service =
client_->GetSafeBrowsingService();
if (!safe_browsing_service->CanCheckUrl(request_url)) {
return std::move(callback).Run(
web::WebStatePolicyDecider::PolicyDecision::Allow());
}
// Track all pending URL queries.
if (pending_main_frame_query_) {
previous_main_frame_query_ = std::move(pending_main_frame_query_);
}
pending_main_frame_query_ = MainFrameUrlQuery(request_url);
// If there is a pre-existing main frame unsafe resource for `request_url`
// that haven't yet resulted in an error page, this resource can be used to
// show the error page for the current load. This can occur in back/forward
// navigations to safe browsing error pages, where ShouldAllowRequest() is
// called multiple times consecutively for the same URL.
SafeBrowsingUnsafeResourceContainer* unsafe_resource_container =
SafeBrowsingUnsafeResourceContainer::FromWebState(web_state());
const security_interstitials::UnsafeResource* main_frame_resource =
unsafe_resource_container->GetMainFrameUnsafeResource();
if (main_frame_resource && main_frame_resource->url == request_url) {
// TODO(crbug.com/40681490): This should directly return the safe browsing
// error decision once error pages for cancelled requests are supported.
// For now, only cancelled response errors are displayed properly.
pending_main_frame_query_->decision = CreateSafeBrowsingErrorDecision();
return std::move(callback).Run(
web::WebStatePolicyDecider::PolicyDecision::Allow());
}
// Start the URL check.
query_manager_->StartQuery(SafeBrowsingQueryManager::Query(
request_url, base::SysNSStringToUTF8([request HTTPMethod])));
// Allow all requests to continue. If a safe browsing error is detected, the
// navigation will be cancelled for using the response policy decision.
std::move(callback).Run(web::WebStatePolicyDecider::PolicyDecision::Allow());
}
void SafeBrowsingTabHelper::PolicyDecider::ShouldAllowResponse(
NSURLResponse* response,
web::WebStatePolicyDecider::ResponseInfo response_info,
web::WebStatePolicyDecider::PolicyDecisionCallback callback) {
if (!response_info.for_main_frame) {
std::move(callback).Run(
web::WebStatePolicyDecider::PolicyDecision::Allow());
return;
}
// Allow navigations for URLs that cannot be checked by the service.
SafeBrowsingService* safe_browsing_service =
client_->GetSafeBrowsingService();
GURL response_url = GetCanonicalizedUrl(net::GURLWithNSURL(response.URL));
if (!safe_browsing_service->CanCheckUrl(response_url)) {
return std::move(callback).Run(
web::WebStatePolicyDecider::PolicyDecision::Allow());
}
DCHECK(pending_main_frame_query_);
// When there's a server redirect, a ShouldAllowRequest call sometimes
// doesn't happen for the target of the redirection. This seems to be fixed
// in trunk WebKit.
if (!pending_main_frame_redirect_chain_.empty()) {
bool matching_hosts =
pending_main_frame_query_->url.host() == response_url.host();
UMA_HISTOGRAM_BOOLEAN(
"IOS.SafeBrowsing.RedirectedRequestResponseHostsMatch", matching_hosts);
}
// If the previous query wasn't added to a pending redirect chain, the
// pending chain is no longer active, since DidRedirectNavigation() is
// guaranteed to be called before ShouldAllowResponse() is called for the
// redirection target.
if (previous_main_frame_query_) {
// The previous query was never added to a redirect chain, so the current
// query is not a redirect.
previous_main_frame_query_ = std::nullopt;
pending_main_frame_redirect_chain_.clear();
}
std::optional<web::WebStatePolicyDecider::PolicyDecision> decision;
if (base::FeatureList::IsEnabled(
safe_browsing::kSafeBrowsingAsyncRealTimeCheck)) {
// Logic only needs to check if sync queries in the redirect chain are
// completed since async queries can respond after navigation.
decision =
RedirectChainDecisionWithFilter(RedirectChainFilter::kSyncQueries);
} else {
decision = MainFrameRedirectChainDecision();
}
if (decision) {
RecordCheckCompletedOnResponseMetric(/*check_completed=*/true);
std::move(callback).Run(*decision);
if (decision->ShouldAllowNavigation()) {
UpdateToBeCommittedRedirectChain();
}
pending_main_frame_redirect_chain_.clear();
} else {
RecordCheckCompletedOnResponseMetric(/*check_completed=*/false);
pending_main_frame_query_->response_callback = std::move(callback);
pending_main_frame_query_->delay_start_time = base::TimeTicks::Now();
}
}
#pragma mark URL Check Completion Helpers
SafeBrowsingTabHelper::PolicyDecider::MainFrameUrlQuery*
SafeBrowsingTabHelper::PolicyDecider::GetOldestPendingMainFrameQuery(
const GURL& url) {
for (auto& query : pending_main_frame_redirect_chain_) {
if (query.url == url && !query.decision) {
return &query;
}
}
if (pending_main_frame_query_ && pending_main_frame_query_->url == url &&
!pending_main_frame_query_->decision) {
return &pending_main_frame_query_.value();
}
return nullptr;
}
SafeBrowsingTabHelper::PolicyDecider::MainFrameUrlQuery*
SafeBrowsingTabHelper::PolicyDecider::GetOldestPendingMainFrameQuery(
const SafeBrowsingQueryManager::QueryData& query_data) {
const GURL& url = query_data.query.url;
MainFrameUrlQuery* redirect_chain_query = GetUnansweredQueryForRedirectChain(
pending_main_frame_redirect_chain_, query_data);
if (redirect_chain_query) {
return redirect_chain_query;
}
if (pending_main_frame_query_ && pending_main_frame_query_->url == url) {
if (query_data.type == QueryType::kSync &&
!pending_main_frame_query_->sync_check_complete) {
return &pending_main_frame_query_.value();
}
if (query_data.type == QueryType::kAsync &&
!pending_main_frame_query_->async_check_complete) {
return &pending_main_frame_query_.value();
}
}
return nullptr;
}
SafeBrowsingTabHelper::PolicyDecider::MainFrameUrlQuery*
SafeBrowsingTabHelper::PolicyDecider::GetOldestPendingToBeCommittedQuery(
const SafeBrowsingQueryManager::QueryData& query_data) {
MainFrameUrlQuery* redirect_chain_query = GetUnansweredQueryForRedirectChain(
to_be_committed_redirect_chain_, query_data);
return redirect_chain_query;
}
SafeBrowsingTabHelper::PolicyDecider::MainFrameUrlQuery*
SafeBrowsingTabHelper::PolicyDecider::GetOldestPendingCommittedQuery(
const SafeBrowsingQueryManager::QueryData& query_data) {
MainFrameUrlQuery* redirect_chain_query =
GetUnansweredQueryForRedirectChain(committed_redirect_chain_, query_data);
return redirect_chain_query;
}
SafeBrowsingTabHelper::PolicyDecider::MainFrameUrlQuery*
SafeBrowsingTabHelper::PolicyDecider::GetUnansweredQueryForRedirectChain(
std::list<SafeBrowsingTabHelper::PolicyDecider::MainFrameUrlQuery>&
redirect_chain,
const SafeBrowsingQueryManager::QueryData& query_data) {
const GURL& url = query_data.query.url;
for (auto& query : redirect_chain) {
if (query.url == url) {
if (query_data.type == QueryType::kAsync && !query.async_check_complete) {
return &query;
}
if (query_data.type == QueryType::kSync && !query.sync_check_complete) {
return &query;
}
}
}
return nullptr;
}
void SafeBrowsingTabHelper::PolicyDecider::OnMainFrameUrlQueryDecided(
const GURL& url,
web::WebStatePolicyDecider::PolicyDecision decision,
SafeBrowsingUrlCheckerImpl::PerformedCheck performed_check) {
GetOldestPendingMainFrameQuery(url)->decision = decision;
// If ShouldAllowResponse() has already been called for this URL, and if
// an overall decision for the redirect chain can be computed, invoke this
// URL's callback with the overall decision.
auto& response_callback = pending_main_frame_query_->response_callback;
if (!response_callback.is_null()) {
std::optional<web::WebStatePolicyDecider::PolicyDecision> overall_decision =
MainFrameRedirectChainDecision();
if (overall_decision) {
if (overall_decision->ShouldAllowNavigation()) {
RecordTotalDelayMetricForDelayedAllowedNavigation(
pending_main_frame_query_->delay_start_time, performed_check);
} else {
base::TimeDelta delay = base::TimeTicks::Now() -
pending_main_frame_query_->delay_start_time;
RecordTotalDelay2MetricForNavigation(delay, performed_check);
}
std::move(response_callback).Run(*overall_decision);
pending_main_frame_redirect_chain_.clear();
}
} else {
RecordTotalDelay2MetricForNavigation(base::TimeDelta(), performed_check);
}
if (decision.ShouldCancelNavigation()) {
client_->OnMainFrameUrlQueryCancellationDecided(web_state(), url);
}
}
void SafeBrowsingTabHelper::PolicyDecider::OnMainFrameUrlSyncQueryDecided(
const SafeBrowsingQueryManager::QueryData& query_data,
web::WebStatePolicyDecider::PolicyDecision decision) {
const GURL& url = query_data.query.url;
SafeBrowsingUrlCheckerImpl::PerformedCheck performed_check =
query_data.performed_check;
MainFrameUrlQuery* query = GetOldestPendingMainFrameQuery(query_data);
query->decision = decision;
query->sync_check_complete = true;
// If ShouldAllowResponse() has already been called for this URL, and if
// an overall decision for the redirect chain can be computed, invoke this
// URL's callback with the overall decision.
auto& response_callback = pending_main_frame_query_->response_callback;
if (!response_callback.is_null()) {
std::optional<web::WebStatePolicyDecider::PolicyDecision> sync_decision =
RedirectChainDecisionWithFilter(RedirectChainFilter::kSyncQueries);
if (sync_decision) {
if (sync_decision->ShouldAllowNavigation()) {
RecordTotalDelayMetricForDelayedAllowedNavigation(
pending_main_frame_query_->delay_start_time, performed_check);
UpdateToBeCommittedRedirectChain();
} else {
base::TimeDelta delay = base::TimeTicks::Now() -
pending_main_frame_query_->delay_start_time;
RecordTotalDelay2MetricForNavigation(delay, performed_check);
}
std::move(response_callback).Run(*sync_decision);
// TODO(crbug.com/337243708): Move updating and clearing redirect chains
// to be behind a guard that checks if both sync and async checks are
// complete.
pending_main_frame_redirect_chain_.clear();
}
} else {
RecordTotalDelay2MetricForNavigation(base::TimeDelta(), performed_check);
}
if (decision.ShouldCancelNavigation()) {
client_->OnMainFrameUrlQueryCancellationDecided(web_state(), url);
}
}
void SafeBrowsingTabHelper::PolicyDecider::OnMainFrameUrlAsyncQueryDecided(
const SafeBrowsingQueryManager::QueryData& query_data,
web::WebStatePolicyDecider::PolicyDecision decision) {
MainFrameUrlQuery* query = GetOldestPendingMainFrameQuery(query_data);
query->async_check_complete = true;
std::optional<web::WebStatePolicyDecider::PolicyDecision> query_decision =
query->decision;
if (query_decision->ShouldAllowNavigation() || !query_decision) {
query_decision = decision;
}
// TODO(crbug.com/337243708): Add async logic.
}
std::optional<web::WebStatePolicyDecider::PolicyDecision>
SafeBrowsingTabHelper::PolicyDecider::MainFrameRedirectChainDecision() {
if (pending_main_frame_query_->decision &&
pending_main_frame_query_->decision->ShouldCancelNavigation()) {
return pending_main_frame_query_->decision;
}
// If some query has received a decision to cancel the navigation or if
// every query has received a decision to allow the navigation, there is
// enough information to make an overall decision.
std::optional<web::WebStatePolicyDecider::PolicyDecision> decision =
pending_main_frame_query_->decision;
for (auto& query : pending_main_frame_redirect_chain_) {
if (!query.decision) {
decision = std::nullopt;
} else if (query.decision->ShouldCancelNavigation()) {
decision = query.decision;
break;
}
}
return decision;
}
std::optional<web::WebStatePolicyDecider::PolicyDecision>
SafeBrowsingTabHelper::PolicyDecider::RedirectChainDecisionWithFilter(
RedirectChainFilter filter) {
if (pending_main_frame_query_->decision &&
pending_main_frame_query_->decision->ShouldCancelNavigation()) {
return pending_main_frame_query_->decision;
}
std::optional<web::WebStatePolicyDecider::PolicyDecision> decision =
pending_main_frame_query_->decision;
for (auto& query : pending_main_frame_redirect_chain_) {
if (!query.decision) {
decision = std::nullopt;
} else if (query.decision->ShouldCancelNavigation()) {
decision = query.decision;
break;
} else {
decision = QueryDecisionFromFilter(query, decision, filter);
}
}
return decision;
}
std::optional<web::WebStatePolicyDecider::PolicyDecision>
SafeBrowsingTabHelper::PolicyDecider::QueryDecisionFromFilter(
const MainFrameUrlQuery& query,
std::optional<web::WebStatePolicyDecider::PolicyDecision> decision,
RedirectChainFilter filter) {
switch (filter) {
case RedirectChainFilter::kSyncQueries:
if (!query.sync_check_complete) {
return std::nullopt;
}
break;
case RedirectChainFilter::kAllQueries:
if (!query.sync_check_complete || !query.async_check_complete) {
return std::nullopt;
}
break;
default:
break;
}
return decision;
}
void SafeBrowsingTabHelper::PolicyDecider::UpdateToBeCommittedRedirectChain() {
if (base::FeatureList::IsEnabled(
safe_browsing::kSafeBrowsingAsyncRealTimeCheck)) {
to_be_committed_redirect_chain_.clear();
for (auto& query : pending_main_frame_redirect_chain_) {
to_be_committed_redirect_chain_.push_back(std::move(query));
}
}
}
#pragma mark SafeBrowsingTabHelper::PolicyDecider::MainFrameUrlQuery
SafeBrowsingTabHelper::PolicyDecider::MainFrameUrlQuery::MainFrameUrlQuery(
const GURL& url)
: url(url) {}
SafeBrowsingTabHelper::PolicyDecider::MainFrameUrlQuery::MainFrameUrlQuery(
MainFrameUrlQuery&& query) = default;
SafeBrowsingTabHelper::PolicyDecider::MainFrameUrlQuery&
SafeBrowsingTabHelper::PolicyDecider::MainFrameUrlQuery::operator=(
MainFrameUrlQuery&& other) = default;
SafeBrowsingTabHelper::PolicyDecider::MainFrameUrlQuery::~MainFrameUrlQuery() {
if (!response_callback.is_null()) {
std::move(response_callback)
.Run(web::WebStatePolicyDecider::PolicyDecision::Cancel());
}
}
#pragma mark - SafeBrowsingTabHelper::QueryObserver
SafeBrowsingTabHelper::QueryObserver::QueryObserver(web::WebState* web_state,
PolicyDecider* decider)
: web_state_(web_state), policy_decider_(decider) {
DCHECK(policy_decider_);
scoped_observation_.Observe(
SafeBrowsingQueryManager::FromWebState(web_state));
}
SafeBrowsingTabHelper::QueryObserver::~QueryObserver() = default;
void SafeBrowsingTabHelper::QueryObserver::SafeBrowsingQueryFinished(
SafeBrowsingQueryManager* manager,
const SafeBrowsingQueryManager::Query& query,
const SafeBrowsingQueryManager::Result& result,
SafeBrowsingUrlCheckerImpl::PerformedCheck performed_check) {
if (policy_decider_->IsQueryStale(query))
return;
// Create a policy decision using the query result.
web::WebStatePolicyDecider::PolicyDecision policy_decision =
policy_decider_->CreatePolicyDecision(query, result, web_state_);
policy_decider_->HandlePolicyDecision(query, policy_decision,
performed_check);
}
void SafeBrowsingTabHelper::QueryObserver::SafeBrowsingSyncQueryFinished(
const SafeBrowsingQueryManager::QueryData& query_data) {
if (policy_decider_->IsQueryStale(query_data)) {
return;
}
web::WebStatePolicyDecider::PolicyDecision policy_decision =
policy_decider_->CreatePolicyDecision(query_data.query, query_data.result,
web_state_);
policy_decider_->HandlePolicyDecision(query_data, policy_decision);
}
void SafeBrowsingTabHelper::QueryObserver::SafeBrowsingAsyncQueryFinished(
const SafeBrowsingQueryManager::QueryData& query_data) {
if (policy_decider_->IsQueryStale(query_data)) {
return;
}
web::WebStatePolicyDecider::PolicyDecision policy_decision =
policy_decider_->CreatePolicyDecision(query_data.query, query_data.result,
web_state_);
policy_decider_->HandlePolicyDecision(query_data, policy_decision);
}
void SafeBrowsingTabHelper::QueryObserver::SafeBrowsingQueryManagerDestroyed(
SafeBrowsingQueryManager* manager) {
DCHECK(scoped_observation_.IsObservingSource(manager));
scoped_observation_.Reset();
}
#pragma mark - SafeBrowsingTabHelper::NavigationObserver
SafeBrowsingTabHelper::NavigationObserver::NavigationObserver(
web::WebState* web_state,
PolicyDecider* policy_decider)
: policy_decider_(policy_decider) {
DCHECK(policy_decider_);
scoped_observation_.Observe(web_state);
}
SafeBrowsingTabHelper::NavigationObserver::~NavigationObserver() = default;
void SafeBrowsingTabHelper::NavigationObserver::DidRedirectNavigation(
web::WebState* web_state,
web::NavigationContext* navigation_context) {
policy_decider_->UpdateForMainFrameServerRedirect();
}
void SafeBrowsingTabHelper::NavigationObserver::DidFinishNavigation(
web::WebState* web_state,
web::NavigationContext* navigation_context) {
if (navigation_context->HasCommitted() &&
!navigation_context->IsSameDocument()) {
policy_decider_->UpdateForMainFrameDocumentChange();
}
// TODO(crbug.com/338214983): Save committed redirect chain.
}
void SafeBrowsingTabHelper::NavigationObserver::WebStateDestroyed(
web::WebState* web_state) {
DCHECK(scoped_observation_.IsObservingSource(web_state));
scoped_observation_.Reset();
}