#include "content/browser/loader/keep_alive_url_loader.h"
#include <vector>
#include "base/check_is_test.h"
#include "base/functional/bind.h"
#include "base/trace_event/trace_event.h"
#include "base/trace_event/typed_macros.h"
#include "content/browser/renderer_host/policy_container_host.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/common/url_utils.h"
#include "net/base/load_flags.h"
#include "net/http/http_request_headers.h"
#include "services/network/public/cpp/content_security_policy/content_security_policy.h"
#include "services/network/public/cpp/content_security_policy/csp_context.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/url_loader_completion_status.h"
#include "services/network/public/mojom/content_security_policy.mojom.h"
#include "services/network/public/mojom/early_hints.mojom.h"
#include "services/network/public/mojom/fetch_api.mojom-shared.h"
#include "services/network/public/mojom/url_request.mojom.h"
#include "third_party/blink/public/common/features.h"
namespace content {
namespace {
class KeepAliveURLLoaderCSPContext final : public network::CSPContext {
public:
void ReportContentSecurityPolicyViolation(
network::mojom::CSPViolationPtr violation_params) final {
}
void SanitizeDataForUseInCspViolation(
network::mojom::CSPDirectiveName directive,
GURL* blocked_url,
network::mojom::SourceLocation* source_location) const final {
}
};
bool IsRedirectAllowedByCSP(
const std::vector<network::mojom::ContentSecurityPolicyPtr>& policies,
const GURL& url,
const GURL& url_before_redirects,
bool has_followed_redirect) {
auto directive = network::mojom::CSPDirectiveName::ConnectSrc;
auto empty_source_location = network::mojom::SourceLocation::New();
auto disposition = network::CSPContext::CheckCSPDisposition::CHECK_ALL_CSP;
return KeepAliveURLLoaderCSPContext().IsAllowedByCsp(
policies, directive, url, url_before_redirects, has_followed_redirect,
false, empty_source_location, disposition,
false);
}
}
KeepAliveURLLoader::KeepAliveURLLoader(
int32_t request_id,
uint32_t options,
const network::ResourceRequest& resource_request,
mojo::PendingRemote<network::mojom::URLLoaderClient> forwarding_client,
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation,
scoped_refptr<network::SharedURLLoaderFactory> network_loader_factory,
scoped_refptr<PolicyContainerHost> policy_container_host,
base::PassKey<KeepAliveURLLoaderService>)
: request_id_(request_id),
resource_request_(resource_request),
forwarding_client_(std::move(forwarding_client)),
policy_container_host_(std::move(policy_container_host)),
initial_url_(resource_request.url),
last_url_(resource_request.url) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
CHECK(network_loader_factory);
CHECK(policy_container_host_);
CHECK(!resource_request.trusted_params);
TRACE_EVENT("loading", "KeepAliveURLLoader::KeepAliveURLLoader", "request_id",
request_id_, "url", last_url_);
TRACE_EVENT_NESTABLE_ASYNC_BEGIN1("loading", "KeepAliveURLLoader",
request_id_, "url", last_url_);
network_loader_factory->CreateLoaderAndStart(
loader_.BindNewPipeAndPassReceiver(), request_id, options,
resource_request, loader_receiver_.BindNewPipeAndPassRemote(),
traffic_annotation);
loader_receiver_.set_disconnect_handler(base::BindOnce(
&KeepAliveURLLoader::OnNetworkConnectionError, base::Unretained(this)));
forwarding_client_.set_disconnect_handler(base::BindOnce(
&KeepAliveURLLoader::OnRendererConnectionError, base::Unretained(this)));
}
KeepAliveURLLoader::~KeepAliveURLLoader() {
TRACE_EVENT("loading", "KeepAliveURLLoader::~KeepAliveURLLoader",
"request_id", request_id_);
TRACE_EVENT_NESTABLE_ASYNC_END0("loading", "KeepAliveURLLoader", request_id_);
}
void KeepAliveURLLoader::set_on_delete_callback(
OnDeleteCallback on_delete_callback) {
on_delete_callback_ = std::move(on_delete_callback);
}
void KeepAliveURLLoader::FollowRedirect(
const std::vector<std::string>& removed_headers,
const net::HttpRequestHeaders& modified_headers,
const net::HttpRequestHeaders& modified_cors_exempt_headers,
const absl::optional<GURL>& new_url) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
TRACE_EVENT("loading", "KeepAliveURLLoader::FollowRedirect", "request_id",
request_id_, "url", new_url);
loader_->FollowRedirect(removed_headers, modified_headers,
modified_cors_exempt_headers, new_url);
}
void KeepAliveURLLoader::SetPriority(net::RequestPriority priority,
int intra_priority_value) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
TRACE_EVENT("loading", "KeepAliveURLLoader::SetPriority", "request_id",
request_id_);
loader_->SetPriority(priority, intra_priority_value);
}
void KeepAliveURLLoader::PauseReadingBodyFromNet() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
TRACE_EVENT("loading", "KeepAliveURLLoader::FollowRedirect", "request_id",
request_id_);
loader_->PauseReadingBodyFromNet();
}
void KeepAliveURLLoader::ResumeReadingBodyFromNet() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
TRACE_EVENT("loading", "KeepAliveURLLoader::ResumeReadingBodyFromNet",
"request_id", request_id_);
loader_->ResumeReadingBodyFromNet();
}
void KeepAliveURLLoader::OnReceiveEarlyHints(
network::mojom::EarlyHintsPtr early_hints) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
TRACE_EVENT("loading", "KeepAliveURLLoader::OnReceiveEarlyHints",
"request_id", request_id_);
if (forwarding_client_) {
forwarding_client_->OnReceiveEarlyHints(std::move(early_hints));
return;
}
}
void KeepAliveURLLoader::OnReceiveResponse(
network::mojom::URLResponseHeadPtr response,
mojo::ScopedDataPipeConsumerHandle body,
absl::optional<mojo_base::BigBuffer> cached_metadata) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
TRACE_EVENT("loading", "KeepAliveURLLoader::OnReceiveResponse", "request_id",
request_id_, "url", last_url_);
has_received_response_ = true;
if (forwarding_client_) {
forwarding_client_->OnReceiveResponse(std::move(response), std::move(body),
std::move(cached_metadata));
if (observer_for_testing_) {
CHECK_IS_TEST();
observer_for_testing_->OnReceiveResponseForwarded(this);
}
return;
}
if (observer_for_testing_) {
CHECK_IS_TEST();
observer_for_testing_->OnReceiveResponseProcessed(this);
}
DeleteSelf();
}
void KeepAliveURLLoader::OnReceiveRedirect(
const net::RedirectInfo& redirect_info,
network::mojom::URLResponseHeadPtr head) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
TRACE_EVENT("loading", "KeepAliveURLLoader::OnReceiveRedirect", "request_id",
request_id_);
if (forwarding_client_) {
forwarding_client_->OnReceiveRedirect(redirect_info, std::move(head));
if (observer_for_testing_) {
CHECK_IS_TEST();
observer_for_testing_->OnReceiveRedirectForwarded(this);
}
return;
}
if (net::Error err = WillFollowRedirect(redirect_info); err != net::OK) {
OnComplete(network::URLLoaderCompletionStatus(err));
return;
}
resource_request_.url = redirect_info.new_url;
resource_request_.site_for_cookies = redirect_info.new_site_for_cookies;
resource_request_.referrer = GURL(redirect_info.new_referrer);
resource_request_.referrer_policy = redirect_info.new_referrer_policy;
last_url_ = GURL(redirect_info.new_url);
if (observer_for_testing_) {
CHECK_IS_TEST();
observer_for_testing_->OnReceiveRedirectProcessed(this);
}
FollowRedirect({}, {},
{},
absl::nullopt);
}
void KeepAliveURLLoader::OnUploadProgress(int64_t current_position,
int64_t total_size,
base::OnceCallback<void()> callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
TRACE_EVENT("loading", "KeepAliveURLLoader::OnUploadProgress", "request_id",
request_id_);
if (forwarding_client_) {
forwarding_client_->OnUploadProgress(current_position, total_size,
std::move(callback));
return;
}
}
void KeepAliveURLLoader::OnTransferSizeUpdated(int32_t transfer_size_diff) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
TRACE_EVENT("loading", "KeepAliveURLLoader::OnTransferSizeUpdated",
"request_id", request_id_);
if (forwarding_client_) {
forwarding_client_->OnTransferSizeUpdated(transfer_size_diff);
return;
}
}
void KeepAliveURLLoader::OnComplete(
const network::URLLoaderCompletionStatus& completion_status) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
TRACE_EVENT("loading", "KeepAliveURLLoader::OnComplete", "request_id",
request_id_);
if (forwarding_client_) {
forwarding_client_->OnComplete(completion_status);
if (observer_for_testing_) {
CHECK_IS_TEST();
observer_for_testing_->OnCompleteForwarded(this, completion_status);
}
DeleteSelf();
return;
}
if (observer_for_testing_) {
CHECK_IS_TEST();
observer_for_testing_->OnCompleteProcessed(this, completion_status);
}
DeleteSelf();
}
net::Error KeepAliveURLLoader::WillFollowRedirect(
const net::RedirectInfo& redirect_info) const {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!IsSafeRedirectTarget(last_url_, redirect_info.new_url)) {
return net::ERR_UNSAFE_REDIRECT;
}
if (resource_request_.redirect_mode == network::mojom::RedirectMode::kError) {
return net::ERR_FAILED;
}
if (resource_request_.redirect_mode !=
network::mojom::RedirectMode::kManual) {
if (!IsRedirectAllowedByCSP(
policy_container_host_->policies().content_security_policies,
redirect_info.new_url, initial_url_, last_url_ != initial_url_)) {
return net::ERR_BLOCKED_BY_CSP;
}
}
return net::OK;
}
void KeepAliveURLLoader::OnNetworkConnectionError() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
TRACE_EVENT("loading", "KeepAliveURLLoader::OnNetworkConnectionError",
"request_id", request_id_);
forwarding_client_.reset();
}
void KeepAliveURLLoader::OnRendererConnectionError() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
TRACE_EVENT("loading", "KeepAliveURLLoader::OnRendererConnectionError",
"request_id", request_id_);
if (has_received_response_) {
DeleteSelf();
return;
}
forwarding_client_.reset();
}
void KeepAliveURLLoader::DeleteSelf() {
CHECK(on_delete_callback_);
std::move(on_delete_callback_).Run();
}
void KeepAliveURLLoader::SetObserverForTesting(
scoped_refptr<TestObserver> observer) {
observer_for_testing_ = observer;
}
}