510 lines
17 KiB
C++
510 lines
17 KiB
C++
// 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;
|
|
}
|