blob: 144f3c794f6e962c385b7862eb96cd25669fe780 [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/safe_browsing/content/browser/browser_url_loader_throttle.h"
#include "base/bind.h"
#include "base/check_op.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/trace_event/trace_event.h"
#include "components/safe_browsing/content/browser/web_ui/safe_browsing_ui.h"
#include "components/safe_browsing/core/browser/realtime/policy_engine.h"
#include "components/safe_browsing/core/browser/realtime/url_lookup_service_base.h"
#include "components/safe_browsing/core/browser/safe_browsing_url_checker_impl.h"
#include "components/safe_browsing/core/browser/url_checker_delegate.h"
#include "components/safe_browsing/core/common/safebrowsing_constants.h"
#include "components/safe_browsing/core/common/utils.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/web_contents.h"
#include "net/log/net_log_event_type.h"
#include "net/url_request/redirect_info.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/mojom/fetch_api.mojom.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
namespace safe_browsing {
// TODO(http://crbug.com/824843): Remove this if safe browsing is moved to the
// UI thread.
class BrowserURLLoaderThrottle::CheckerOnIO
: public base::SupportsWeakPtr<BrowserURLLoaderThrottle::CheckerOnIO> {
public:
CheckerOnIO(
GetDelegateCallback delegate_getter,
int frame_tree_node_id,
base::RepeatingCallback<content::WebContents*()> web_contents_getter,
base::WeakPtr<BrowserURLLoaderThrottle> throttle,
bool real_time_lookup_enabled,
bool can_rt_check_subresource_url,
bool can_check_db,
base::WeakPtr<RealTimeUrlLookupServiceBase> url_lookup_service)
: delegate_getter_(std::move(delegate_getter)),
frame_tree_node_id_(frame_tree_node_id),
web_contents_getter_(web_contents_getter),
throttle_(std::move(throttle)),
real_time_lookup_enabled_(real_time_lookup_enabled),
can_rt_check_subresource_url_(can_rt_check_subresource_url),
can_check_db_(can_check_db),
url_lookup_service_(url_lookup_service) {
content::WebContents* contents = web_contents_getter_.Run();
if (!!contents) {
last_committed_url_ = contents->GetLastCommittedURL();
}
}
// Starts the initial safe browsing check. This check and future checks may be
// skipped after checking with the UrlCheckerDelegate.
void Start(const net::HttpRequestHeaders& headers,
int load_flags,
network::mojom::RequestDestination request_destination,
bool has_user_gesture,
bool originated_from_service_worker,
const GURL& url,
const std::string& method) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
scoped_refptr<UrlCheckerDelegate> url_checker_delegate =
std::move(delegate_getter_).Run();
skip_checks_ =
!url_checker_delegate ||
url_checker_delegate->ShouldSkipRequestCheck(
url, frame_tree_node_id_,
content::ChildProcessHost::kInvalidUniqueID /* render_process_id */,
MSG_ROUTING_NONE /* render_frame_id */,
originated_from_service_worker);
if (skip_checks_) {
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&BrowserURLLoaderThrottle::SkipChecks, throttle_));
return;
}
url_checker_ = std::make_unique<SafeBrowsingUrlCheckerImpl>(
headers, load_flags, request_destination, has_user_gesture,
url_checker_delegate, web_contents_getter_,
content::ChildProcessHost::kInvalidUniqueID, MSG_ROUTING_NONE,
frame_tree_node_id_, real_time_lookup_enabled_,
can_rt_check_subresource_url_, can_check_db_, last_committed_url_,
content::GetUIThreadTaskRunner({}), url_lookup_service_,
WebUIInfoSingleton::GetInstance());
CheckUrl(url, method);
}
// Checks the specified |url| using |url_checker_|.
void CheckUrl(const GURL& url, const std::string& method) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
if (skip_checks_) {
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&BrowserURLLoaderThrottle::SkipChecks, throttle_));
return;
}
DCHECK(url_checker_);
url_checker_->CheckUrl(
url, method,
base::BindOnce(&BrowserURLLoaderThrottle::CheckerOnIO::OnCheckUrlResult,
base::Unretained(this)));
}
private:
// If |slow_check_notifier| is non-null, it indicates that a "slow check" is
// ongoing, i.e., the URL may be unsafe and a more time-consuming process is
// required to get the final result. In that case, the rest of the callback
// arguments should be ignored. This method sets the |slow_check_notifier|
// output parameter to a callback to receive the final result.
void OnCheckUrlResult(NativeUrlCheckNotifier* slow_check_notifier,
bool proceed,
bool showed_interstitial) {
if (!slow_check_notifier) {
OnCompleteCheck(false /* slow_check */, proceed, showed_interstitial);
return;
}
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&BrowserURLLoaderThrottle::NotifySlowCheck, throttle_));
// In this case |proceed| and |showed_interstitial| should be ignored. The
// result will be returned by calling |*slow_check_notifier| callback.
*slow_check_notifier =
base::BindOnce(&BrowserURLLoaderThrottle::CheckerOnIO::OnCompleteCheck,
base::Unretained(this), true /* slow_check */);
}
// |slow_check| indicates whether it reports the result of a slow check.
// (Please see comments of OnCheckUrlResult() for what slow check means).
void OnCompleteCheck(bool slow_check,
bool proceed,
bool showed_interstitial) {
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&BrowserURLLoaderThrottle::OnCompleteCheck, throttle_,
slow_check, proceed, showed_interstitial));
}
// The following member stays valid until |url_checker_| is created.
GetDelegateCallback delegate_getter_;
std::unique_ptr<SafeBrowsingUrlCheckerImpl> url_checker_;
int frame_tree_node_id_;
base::RepeatingCallback<content::WebContents*()> web_contents_getter_;
bool skip_checks_ = false;
base::WeakPtr<BrowserURLLoaderThrottle> throttle_;
bool real_time_lookup_enabled_ = false;
bool can_rt_check_subresource_url_ = false;
bool can_check_db_ = true;
GURL last_committed_url_;
base::WeakPtr<RealTimeUrlLookupServiceBase> url_lookup_service_;
};
// static
std::unique_ptr<BrowserURLLoaderThrottle> BrowserURLLoaderThrottle::Create(
GetDelegateCallback delegate_getter,
const base::RepeatingCallback<content::WebContents*()>& web_contents_getter,
int frame_tree_node_id,
base::WeakPtr<RealTimeUrlLookupServiceBase> url_lookup_service) {
return base::WrapUnique<BrowserURLLoaderThrottle>(
new BrowserURLLoaderThrottle(std::move(delegate_getter),
web_contents_getter, frame_tree_node_id,
url_lookup_service));
}
BrowserURLLoaderThrottle::BrowserURLLoaderThrottle(
GetDelegateCallback delegate_getter,
const base::RepeatingCallback<content::WebContents*()>& web_contents_getter,
int frame_tree_node_id,
base::WeakPtr<RealTimeUrlLookupServiceBase> url_lookup_service) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// Decide whether to do real time URL lookups or not.
bool real_time_lookup_enabled =
url_lookup_service ? url_lookup_service->CanPerformFullURLLookup()
: false;
bool can_rt_check_subresource_url =
url_lookup_service && url_lookup_service->CanCheckSubresourceURL();
// Decide whether safe browsing database can be checked.
// If url_lookup_service is null, safe browsing database should be checked by
// default.
bool can_check_db =
url_lookup_service ? url_lookup_service->CanCheckSafeBrowsingDb() : true;
io_checker_ = std::make_unique<CheckerOnIO>(
std::move(delegate_getter), frame_tree_node_id, web_contents_getter,
weak_factory_.GetWeakPtr(), real_time_lookup_enabled,
can_rt_check_subresource_url, can_check_db, url_lookup_service);
}
BrowserURLLoaderThrottle::~BrowserURLLoaderThrottle() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (deferred_) {
TRACE_EVENT_NESTABLE_ASYNC_END0("safe_browsing", "Deferred",
TRACE_ID_LOCAL(this));
}
DeleteCheckerOnIO();
}
void BrowserURLLoaderThrottle::WillStartRequest(
network::ResourceRequest* request,
bool* defer) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK_EQ(0u, pending_checks_);
DCHECK(!blocked_);
original_url_ = request->url;
pending_checks_++;
content::GetIOThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&BrowserURLLoaderThrottle::CheckerOnIO::Start,
io_checker_->AsWeakPtr(), request->headers,
request->load_flags, request->destination,
request->has_user_gesture,
request->originated_from_service_worker,
request->url, request->method));
}
void BrowserURLLoaderThrottle::WillRedirectRequest(
net::RedirectInfo* redirect_info,
const network::mojom::URLResponseHead& /* response_head */,
bool* defer,
std::vector<std::string>* /* to_be_removed_headers */,
net::HttpRequestHeaders* /* modified_headers */,
net::HttpRequestHeaders* /* modified_cors_exempt_headers */) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (blocked_) {
// OnCheckUrlResult() has set |blocked_| to true and called
// |delegate_->CancelWithError|, but this method is called before the
// request is actually cancelled. In that case, simply defer the request.
*defer = true;
return;
}
if (skip_checks_)
return;
pending_checks_++;
content::GetIOThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&BrowserURLLoaderThrottle::CheckerOnIO::CheckUrl,
io_checker_->AsWeakPtr(), redirect_info->new_url,
redirect_info->new_method));
}
void BrowserURLLoaderThrottle::WillProcessResponse(
const GURL& response_url,
network::mojom::URLResponseHead* response_head,
bool* defer) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (blocked_) {
// OnCheckUrlResult() has set |blocked_| to true and called
// |delegate_->CancelWithError|, but this method is called before the
// request is actually cancelled. In that case, simply defer the request.
*defer = true;
return;
}
bool check_completed = (pending_checks_ == 0);
base::UmaHistogramBoolean(
"SafeBrowsing.BrowserThrottle.IsCheckCompletedOnProcessResponse",
check_completed);
if (check_completed)
return;
DCHECK(!deferred_);
deferred_ = true;
defer_start_time_ = base::TimeTicks::Now();
*defer = true;
TRACE_EVENT_NESTABLE_ASYNC_BEGIN1("safe_browsing", "Deferred",
TRACE_ID_LOCAL(this), "original_url",
original_url_.spec());
}
const char* BrowserURLLoaderThrottle::NameForLoggingWillProcessResponse() {
return "SafeBrowsingBrowserThrottle";
}
void BrowserURLLoaderThrottle::OnCompleteCheck(bool slow_check,
bool proceed,
bool showed_interstitial) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(!blocked_);
DCHECK_LT(0u, pending_checks_);
pending_checks_--;
if (slow_check) {
DCHECK_LT(0u, pending_slow_checks_);
pending_slow_checks_--;
}
// If the resource load is currently deferred and is going to exit that state
// (either being cancelled or resumed), record the total delay.
if (deferred_ && (!proceed || pending_checks_ == 0))
total_delay_ = base::TimeTicks::Now() - defer_start_time_;
if (proceed) {
if (pending_slow_checks_ == 0 && slow_check)
delegate_->ResumeReadingBodyFromNet();
if (pending_checks_ == 0 && deferred_) {
deferred_ = false;
TRACE_EVENT_NESTABLE_ASYNC_END0("safe_browsing", "Deferred",
TRACE_ID_LOCAL(this));
base::UmaHistogramTimes("SafeBrowsing.BrowserThrottle.TotalDelay",
total_delay_);
delegate_->Resume();
}
} else {
blocked_ = true;
DeleteCheckerOnIO();
pending_checks_ = 0;
pending_slow_checks_ = 0;
// If we didn't show an interstitial, we cancel with ERR_ABORTED to not show
// an error page either.
delegate_->CancelWithError(
showed_interstitial ? kNetErrorCodeForSafeBrowsing : net::ERR_ABORTED,
kCustomCancelReasonForURLLoader);
}
}
void BrowserURLLoaderThrottle::SkipChecks() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// Future checks for redirects will be skipped.
skip_checks_ = true;
pending_checks_--;
if (pending_checks_ == 0 && deferred_)
delegate_->Resume();
}
void BrowserURLLoaderThrottle::NotifySlowCheck() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
pending_slow_checks_++;
// Pending slow checks indicate that the resource may be unsafe. In that case,
// pause reading response body from network to minimize the chance of
// processing unsafe contents (e.g., writing unsafe contents into cache),
// until we get the results. According to the results, we may resume reading
// or cancel the resource load.
// For real time Safe Browsing checks, we continue reading the response body
// but, similar to hash-based checks, do not process it until we know it is
// SAFE.
if (pending_slow_checks_ == 1)
delegate_->PauseReadingBodyFromNet();
}
void BrowserURLLoaderThrottle::DeleteCheckerOnIO() {
content::GetIOThreadTaskRunner({})->DeleteSoon(FROM_HERE,
std::move(io_checker_));
}
} // namespace safe_browsing