| // 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; |
| } |