blob: 4c5d25b03ae38cfb91f7fcc832aee8b6fc713bc1 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Embedded Framework 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 "libcef/renderer/render_urlrequest_impl.h"
#include <stdint.h>
#include "libcef/common/request_impl.h"
#include "libcef/common/response_impl.h"
#include "libcef/common/task_runner_impl.h"
#include "libcef/renderer/blink_glue.h"
#include "libcef/renderer/content_renderer_client.h"
#include "base/logging.h"
#include "base/message_loop/message_loop.h"
#include "net/base/request_priority.h"
#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom.h"
#include "third_party/blink/public/platform/web_security_origin.h"
#include "third_party/blink/public/platform/web_string.h"
#include "third_party/blink/public/platform/web_url.h"
#include "third_party/blink/public/platform/web_url_error.h"
#include "third_party/blink/public/platform/web_url_loader.h"
#include "third_party/blink/public/platform/web_url_loader_client.h"
#include "third_party/blink/public/platform/web_url_loader_factory.h"
#include "third_party/blink/public/platform/web_url_request.h"
#include "third_party/blink/public/platform/web_url_response.h"
using blink::WebString;
using blink::WebURL;
using blink::WebURLError;
using blink::WebURLLoader;
using blink::WebURLRequest;
using blink::WebURLResponse;
namespace {
class CefWebURLLoaderClient : public blink::WebURLLoaderClient {
public:
CefWebURLLoaderClient(CefRenderURLRequest::Context* context,
int request_flags);
~CefWebURLLoaderClient() override;
// blink::WebURLLoaderClient methods.
void DidSendData(uint64_t bytes_sent,
uint64_t total_bytes_to_be_sent) override;
void DidReceiveResponse(const WebURLResponse& response) override;
void DidReceiveData(const char* data, int dataLength) override;
void DidFinishLoading(base::TimeTicks finish_time,
int64_t total_encoded_data_length,
int64_t total_encoded_body_length,
int64_t total_decoded_body_length,
bool should_report_corb_blocking) override;
void DidFail(const WebURLError&,
int64_t total_encoded_data_length,
int64_t total_encoded_body_length,
int64_t total_decoded_body_length) override;
void DidStartLoadingResponseBody(
mojo::ScopedDataPipeConsumerHandle response_body) override;
bool WillFollowRedirect(const WebURL& new_url,
const net::SiteForCookies& new_site_for_cookies,
const WebString& new_referrer,
network::mojom::ReferrerPolicy new_referrer_policy,
const WebString& new_method,
const WebURLResponse& passed_redirect_response,
bool& report_raw_headers) override;
protected:
// The context_ pointer will outlive this object.
CefRenderURLRequest::Context* context_;
int request_flags_;
};
} // namespace
// CefRenderURLRequest::Context -----------------------------------------------
class CefRenderURLRequest::Context
: public base::RefCountedThreadSafe<CefRenderURLRequest::Context> {
public:
Context(CefRefPtr<CefRenderURLRequest> url_request,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request,
CefRefPtr<CefURLRequestClient> client)
: url_request_(url_request),
frame_(frame),
request_(request),
client_(client),
task_runner_(CefTaskRunnerImpl::GetCurrentTaskRunner()),
status_(UR_IO_PENDING),
error_code_(ERR_NONE),
body_watcher_(FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::MANUAL),
response_was_cached_(false),
upload_data_size_(0),
got_upload_progress_complete_(false),
download_data_received_(0),
download_data_total_(-1) {
// Mark the request as read-only.
static_cast<CefRequestImpl*>(request_.get())->SetReadOnly(true);
}
inline bool CalledOnValidThread() {
return task_runner_->RunsTasksInCurrentSequence();
}
bool Start() {
DCHECK(CalledOnValidThread());
GURL url = GURL(request_->GetURL().ToString());
if (!url.is_valid())
return false;
url_client_.reset(new CefWebURLLoaderClient(this, request_->GetFlags()));
std::unique_ptr<network::ResourceRequest> resource_request =
std::make_unique<network::ResourceRequest>();
static_cast<CefRequestImpl*>(request_.get())
->Get(resource_request.get(), false);
resource_request->priority = net::MEDIUM;
// Behave the same as a subresource load.
resource_request->fetch_request_context_type =
static_cast<int>(blink::mojom::RequestContextType::SUBRESOURCE);
resource_request->resource_type =
static_cast<int>(blink::mojom::ResourceType::kSubResource);
// Need load timing info for WebURLLoaderImpl::PopulateURLResponse to
// properly set cached status.
resource_request->enable_load_timing = true;
// Set the origin to match the request. The requirement for an origin is
// DCHECK'd in ResourceDispatcherHostImpl::ContinuePendingBeginRequest.
resource_request->request_initiator = url::Origin::Create(url);
if (request_->GetFlags() & UR_FLAG_ALLOW_STORED_CREDENTIALS) {
// Include SameSite cookies.
resource_request->attach_same_site_cookies = true;
resource_request->site_for_cookies =
net::SiteForCookies::FromOrigin(*resource_request->request_initiator);
}
if (resource_request->request_body) {
const auto& elements = *resource_request->request_body->elements();
if (elements.size() > 0) {
const auto& element = elements[0];
if (element.type() == network::mojom::DataElementType::kBytes) {
upload_data_size_ = element.length() - element.offset();
}
}
}
blink::WebURLLoaderFactory* factory = nullptr;
if (frame_) {
// This factory supports all requests.
factory = static_cast<CefFrameImpl*>(frame_.get())->GetURLLoaderFactory();
}
if (!factory) {
// This factory only supports unintercepted http(s) and blob requests.
factory = CefContentRendererClient::Get()->GetDefaultURLLoaderFactory();
}
loader_ = factory->CreateURLLoader(
blink::WebURLRequest(),
blink::scheduler::WebResourceLoadingTaskRunnerHandle::
CreateUnprioritized(task_runner_.get()));
loader_->LoadAsynchronously(
std::move(resource_request), nullptr /* extra_data */,
0 /* requestor_id */, false /* download_to_network_cache_only */,
false /* no_mime_sniffing */, url_client_.get());
return true;
}
void Cancel() {
DCHECK(CalledOnValidThread());
// The request may already be complete.
if (!loader_.get() || status_ != UR_IO_PENDING)
return;
status_ = UR_CANCELED;
error_code_ = ERR_ABORTED;
// Will result in a call to OnError().
loader_->Cancel();
}
void OnStopRedirect(const WebURL& redirect_url,
const WebURLResponse& response) {
DCHECK(CalledOnValidThread());
response_was_cached_ = blink_glue::ResponseWasCached(response);
response_ = CefResponse::Create();
CefResponseImpl* responseImpl =
static_cast<CefResponseImpl*>(response_.get());
// In case of StopOnRedirect we only set these fields. Everything else is
// left blank. This also replicates the behaviour of the browser urlrequest
// fetcher.
responseImpl->SetStatus(response.HttpStatusCode());
responseImpl->SetURL(redirect_url.GetString().Utf16());
responseImpl->SetReadOnly(true);
status_ = UR_CANCELED;
error_code_ = ERR_ABORTED;
OnComplete();
}
void OnResponse(const WebURLResponse& response) {
DCHECK(CalledOnValidThread());
response_was_cached_ = blink_glue::ResponseWasCached(response);
response_ = CefResponse::Create();
CefResponseImpl* responseImpl =
static_cast<CefResponseImpl*>(response_.get());
responseImpl->Set(response);
responseImpl->SetReadOnly(true);
download_data_total_ = response.ExpectedContentLength();
}
void OnError(const WebURLError& error) {
DCHECK(CalledOnValidThread());
if (status_ == UR_IO_PENDING) {
status_ = UR_FAILED;
error_code_ = static_cast<cef_errorcode_t>(error.reason());
}
OnComplete();
}
void OnComplete() {
DCHECK(CalledOnValidThread());
if (body_handle_.is_valid()) {
return;
}
if (status_ == UR_IO_PENDING) {
status_ = UR_SUCCESS;
NotifyUploadProgressIfNecessary();
}
if (loader_.get())
loader_.reset(nullptr);
DCHECK(url_request_.get());
client_->OnRequestComplete(url_request_.get());
// This may result in the Context object being deleted.
url_request_ = nullptr;
}
void OnBodyReadable(MojoResult, const mojo::HandleSignalsState&) {
const void* buffer = nullptr;
uint32_t read_bytes = 0;
MojoResult result = body_handle_->BeginReadData(&buffer, &read_bytes,
MOJO_READ_DATA_FLAG_NONE);
if (result == MOJO_RESULT_SHOULD_WAIT) {
body_watcher_.ArmOrNotify();
return;
}
if (result == MOJO_RESULT_FAILED_PRECONDITION) {
// Whole body has been read.
body_handle_.reset();
body_watcher_.Cancel();
OnComplete();
return;
}
if (result != MOJO_RESULT_OK) {
// Something went wrong.
body_handle_.reset();
body_watcher_.Cancel();
OnComplete();
return;
}
download_data_received_ += read_bytes;
client_->OnDownloadProgress(url_request_.get(), download_data_received_,
download_data_total_);
if (!(request_->GetFlags() & UR_FLAG_NO_DOWNLOAD_DATA)) {
client_->OnDownloadData(url_request_.get(), buffer, read_bytes);
}
body_handle_->EndReadData(read_bytes);
body_watcher_.ArmOrNotify();
}
void OnStartLoadingResponseBody(
mojo::ScopedDataPipeConsumerHandle response_body) {
DCHECK(CalledOnValidThread());
DCHECK(response_body);
DCHECK(!body_handle_);
body_handle_ = std::move(response_body);
body_watcher_.Watch(
body_handle_.get(),
MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
base::BindRepeating(&CefRenderURLRequest::Context::OnBodyReadable,
base::Unretained(this)));
body_watcher_.ArmOrNotify();
}
void OnDownloadProgress(int64_t current) {
DCHECK(CalledOnValidThread());
DCHECK(url_request_.get());
NotifyUploadProgressIfNecessary();
download_data_received_ += current;
client_->OnDownloadProgress(url_request_.get(), download_data_received_,
download_data_total_);
}
void OnDownloadData(const char* data, int dataLength) {
DCHECK(CalledOnValidThread());
DCHECK(url_request_.get());
client_->OnDownloadData(url_request_.get(), data, dataLength);
}
void OnUploadProgress(int64_t current, int64_t total) {
DCHECK(CalledOnValidThread());
DCHECK(url_request_.get());
if (current == total)
got_upload_progress_complete_ = true;
client_->OnUploadProgress(url_request_.get(), current, total);
}
CefRefPtr<CefRequest> request() const { return request_; }
CefRefPtr<CefURLRequestClient> client() const { return client_; }
CefURLRequest::Status status() const { return status_; }
CefURLRequest::ErrorCode error_code() const { return error_code_; }
CefRefPtr<CefResponse> response() const { return response_; }
bool response_was_cached() const { return response_was_cached_; }
private:
friend class base::RefCountedThreadSafe<CefRenderURLRequest::Context>;
virtual ~Context() {}
void NotifyUploadProgressIfNecessary() {
if (!got_upload_progress_complete_ && upload_data_size_ > 0) {
// Upload notifications are sent using a timer and may not occur if the
// request completes too quickly. We therefore send the notification here
// if necessary.
url_client_->DidSendData(upload_data_size_, upload_data_size_);
got_upload_progress_complete_ = true;
}
}
// Members only accessed on the initialization thread.
CefRefPtr<CefRenderURLRequest> url_request_;
CefRefPtr<CefFrame> frame_;
CefRefPtr<CefRequest> request_;
CefRefPtr<CefURLRequestClient> client_;
scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
CefURLRequest::Status status_;
CefURLRequest::ErrorCode error_code_;
CefRefPtr<CefResponse> response_;
mojo::ScopedDataPipeConsumerHandle body_handle_;
mojo::SimpleWatcher body_watcher_;
bool response_was_cached_;
std::unique_ptr<blink::WebURLLoader> loader_;
std::unique_ptr<CefWebURLLoaderClient> url_client_;
int64_t upload_data_size_;
bool got_upload_progress_complete_;
int64_t download_data_received_;
int64_t download_data_total_;
};
// CefWebURLLoaderClient --------------------------------------------------
namespace {
CefWebURLLoaderClient::CefWebURLLoaderClient(
CefRenderURLRequest::Context* context,
int request_flags)
: context_(context), request_flags_(request_flags) {}
CefWebURLLoaderClient::~CefWebURLLoaderClient() {}
void CefWebURLLoaderClient::DidSendData(uint64_t bytes_sent,
uint64_t total_bytes_to_be_sent) {
if (request_flags_ & UR_FLAG_REPORT_UPLOAD_PROGRESS)
context_->OnUploadProgress(bytes_sent, total_bytes_to_be_sent);
}
void CefWebURLLoaderClient::DidReceiveResponse(const WebURLResponse& response) {
context_->OnResponse(response);
}
void CefWebURLLoaderClient::DidReceiveData(const char* data, int dataLength) {
context_->OnDownloadProgress(dataLength);
if (!(request_flags_ & UR_FLAG_NO_DOWNLOAD_DATA))
context_->OnDownloadData(data, dataLength);
}
void CefWebURLLoaderClient::DidFinishLoading(base::TimeTicks finish_time,
int64_t total_encoded_data_length,
int64_t total_encoded_body_length,
int64_t total_decoded_body_length,
bool should_report_corb_blocking) {
context_->OnComplete();
}
void CefWebURLLoaderClient::DidFail(const WebURLError& error,
int64_t total_encoded_data_length,
int64_t total_encoded_body_length,
int64_t total_decoded_body_length) {
context_->OnError(error);
}
void CefWebURLLoaderClient::DidStartLoadingResponseBody(
mojo::ScopedDataPipeConsumerHandle response_body) {
context_->OnStartLoadingResponseBody(std::move(response_body));
}
bool CefWebURLLoaderClient::WillFollowRedirect(
const WebURL& new_url,
const net::SiteForCookies& new_site_for_cookies,
const WebString& new_referrer,
network::mojom::ReferrerPolicy new_referrer_policy,
const WebString& new_method,
const WebURLResponse& passed_redirect_response,
bool& report_raw_headers) {
if (request_flags_ & UR_FLAG_STOP_ON_REDIRECT) {
context_->OnStopRedirect(new_url, passed_redirect_response);
return false;
}
return true;
}
} // namespace
// CefRenderURLRequest --------------------------------------------------------
CefRenderURLRequest::CefRenderURLRequest(
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request,
CefRefPtr<CefURLRequestClient> client) {
context_ = new Context(this, frame, request, client);
}
CefRenderURLRequest::~CefRenderURLRequest() {}
bool CefRenderURLRequest::Start() {
if (!VerifyContext())
return false;
return context_->Start();
}
CefRefPtr<CefRequest> CefRenderURLRequest::GetRequest() {
if (!VerifyContext())
return nullptr;
return context_->request();
}
CefRefPtr<CefURLRequestClient> CefRenderURLRequest::GetClient() {
if (!VerifyContext())
return nullptr;
return context_->client();
}
CefURLRequest::Status CefRenderURLRequest::GetRequestStatus() {
if (!VerifyContext())
return UR_UNKNOWN;
return context_->status();
}
CefURLRequest::ErrorCode CefRenderURLRequest::GetRequestError() {
if (!VerifyContext())
return ERR_NONE;
return context_->error_code();
}
CefRefPtr<CefResponse> CefRenderURLRequest::GetResponse() {
if (!VerifyContext())
return nullptr;
return context_->response();
}
bool CefRenderURLRequest::ResponseWasCached() {
if (!VerifyContext())
return false;
return context_->response_was_cached();
}
void CefRenderURLRequest::Cancel() {
if (!VerifyContext())
return;
return context_->Cancel();
}
bool CefRenderURLRequest::VerifyContext() {
DCHECK(context_.get());
if (!context_->CalledOnValidThread()) {
NOTREACHED() << "called on invalid thread";
return false;
}
return true;
}