1 // Copyright (c) 2012 The Chromium Embedded Framework Authors. All rights
2 // reserved. Use of this source code is governed by a BSD-style license that can
3 // be found in the LICENSE file.
4
5 #include "libcef/renderer/render_urlrequest_impl.h"
6
7 #include <stdint.h>
8
9 #include "libcef/common/request_impl.h"
10 #include "libcef/common/response_impl.h"
11 #include "libcef/renderer/blink_glue.h"
12 #include "libcef/renderer/frame_impl.h"
13 #include "libcef/renderer/thread_util.h"
14
15 #include "base/logging.h"
16 #include "net/base/request_priority.h"
17 #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom.h"
18 #include "third_party/blink/public/mojom/loader/resource_load_info.mojom.h"
19 #include "third_party/blink/public/platform/resource_load_info_notifier_wrapper.h"
20 #include "third_party/blink/public/platform/web_security_origin.h"
21 #include "third_party/blink/public/platform/web_string.h"
22 #include "third_party/blink/public/platform/web_url.h"
23 #include "third_party/blink/public/platform/web_url_error.h"
24 #include "third_party/blink/public/platform/web_url_loader.h"
25 #include "third_party/blink/public/platform/web_url_loader_client.h"
26 #include "third_party/blink/public/platform/web_url_request.h"
27 #include "third_party/blink/public/platform/web_url_request_extra_data.h"
28 #include "third_party/blink/public/platform/web_url_response.h"
29
30 using blink::WebString;
31 using blink::WebURL;
32 using blink::WebURLError;
33 using blink::WebURLLoader;
34 using blink::WebURLRequest;
35 using blink::WebURLResponse;
36
37 namespace {
38
39 class CefWebURLLoaderClient : public blink::WebURLLoaderClient {
40 public:
41 CefWebURLLoaderClient(CefRenderURLRequest::Context* context,
42 int request_flags);
43 ~CefWebURLLoaderClient() override;
44
45 // blink::WebURLLoaderClient methods.
46 void DidSendData(uint64_t bytes_sent,
47 uint64_t total_bytes_to_be_sent) override;
48 void DidReceiveResponse(const WebURLResponse& response) override;
49 void DidReceiveData(const char* data, int dataLength) override;
50 void DidFinishLoading(base::TimeTicks finish_time,
51 int64_t total_encoded_data_length,
52 int64_t total_encoded_body_length,
53 int64_t total_decoded_body_length,
54 bool should_report_corb_blocking) override;
55 void DidFail(const WebURLError&,
56 base::TimeTicks finish_time,
57 int64_t total_encoded_data_length,
58 int64_t total_encoded_body_length,
59 int64_t total_decoded_body_length) override;
60 void DidStartLoadingResponseBody(
61 mojo::ScopedDataPipeConsumerHandle response_body) override;
62 bool WillFollowRedirect(const WebURL& new_url,
63 const net::SiteForCookies& new_site_for_cookies,
64 const WebString& new_referrer,
65 network::mojom::ReferrerPolicy new_referrer_policy,
66 const WebString& new_method,
67 const WebURLResponse& passed_redirect_response,
68 bool& report_raw_headers,
69 std::vector<std::string>* removed_headers,
70 bool insecure_scheme_was_upgraded) override;
71
72 protected:
73 // The context_ pointer will outlive this object.
74 CefRenderURLRequest::Context* context_;
75 int request_flags_;
76 };
77
78 } // namespace
79
80 // CefRenderURLRequest::Context -----------------------------------------------
81
82 class CefRenderURLRequest::Context
83 : public base::RefCountedThreadSafe<CefRenderURLRequest::Context> {
84 public:
Context(CefRefPtr<CefRenderURLRequest> url_request,CefRefPtr<CefFrame> frame,CefRefPtr<CefRequest> request,CefRefPtr<CefURLRequestClient> client)85 Context(CefRefPtr<CefRenderURLRequest> url_request,
86 CefRefPtr<CefFrame> frame,
87 CefRefPtr<CefRequest> request,
88 CefRefPtr<CefURLRequestClient> client)
89 : url_request_(url_request),
90 frame_(frame),
91 request_(request),
92 client_(client),
93 status_(UR_IO_PENDING),
94 error_code_(ERR_NONE),
95 body_watcher_(FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::MANUAL),
96 response_was_cached_(false),
97 upload_data_size_(0),
98 got_upload_progress_complete_(false),
99 download_data_received_(0),
100 download_data_total_(-1) {
101 // Mark the request as read-only.
102 static_cast<CefRequestImpl*>(request_.get())->SetReadOnly(true);
103 }
104
Start()105 bool Start() {
106 GURL url = GURL(request_->GetURL().ToString());
107 if (!url.is_valid())
108 return false;
109
110 url_client_.reset(new CefWebURLLoaderClient(this, request_->GetFlags()));
111
112 std::unique_ptr<network::ResourceRequest> resource_request =
113 std::make_unique<network::ResourceRequest>();
114 static_cast<CefRequestImpl*>(request_.get())
115 ->Get(resource_request.get(), false);
116 resource_request->priority = net::MEDIUM;
117
118 // Behave the same as a subresource load.
119 resource_request->resource_type =
120 static_cast<int>(blink::mojom::ResourceType::kSubResource);
121
122 // Need load timing info for WebURLLoaderImpl::PopulateURLResponse to
123 // properly set cached status.
124 resource_request->enable_load_timing = true;
125
126 // Set the origin to match the request. The requirement for an origin is
127 // DCHECK'd in ResourceDispatcherHostImpl::ContinuePendingBeginRequest.
128 resource_request->request_initiator = url::Origin::Create(url);
129
130 if (request_->GetFlags() & UR_FLAG_ALLOW_STORED_CREDENTIALS) {
131 // Include SameSite cookies.
132 resource_request->site_for_cookies =
133 net::SiteForCookies::FromOrigin(*resource_request->request_initiator);
134 }
135
136 if (resource_request->request_body) {
137 const auto& elements = *resource_request->request_body->elements();
138 if (elements.size() > 0) {
139 const auto& element = elements[0];
140 if (element.type() == network::DataElement::Tag::kBytes) {
141 const auto& bytes_element = element.As<network::DataElementBytes>();
142 upload_data_size_ = bytes_element.bytes().size();
143 }
144 }
145 }
146
147 auto frame_impl = static_cast<CefFrameImpl*>(frame_.get());
148 loader_ = frame_impl->CreateURLLoader();
149 loader_->LoadAsynchronously(
150 std::move(resource_request), /*extra_data=*/nullptr,
151 /*no_mime_sniffing=*/false,
152 frame_impl->CreateResourceLoadInfoNotifierWrapper(), url_client_.get());
153 return true;
154 }
155
Cancel()156 void Cancel() {
157 // The request may already be complete.
158 if (!loader_.get() || status_ != UR_IO_PENDING)
159 return;
160
161 status_ = UR_CANCELED;
162 error_code_ = ERR_ABORTED;
163
164 // Will result in a call to OnError().
165 loader_->Cancel();
166 }
167
OnStopRedirect(const WebURL & redirect_url,const WebURLResponse & response)168 void OnStopRedirect(const WebURL& redirect_url,
169 const WebURLResponse& response) {
170 response_was_cached_ = blink_glue::ResponseWasCached(response);
171 response_ = CefResponse::Create();
172 CefResponseImpl* responseImpl =
173 static_cast<CefResponseImpl*>(response_.get());
174
175 // In case of StopOnRedirect we only set these fields. Everything else is
176 // left blank. This also replicates the behaviour of the browser urlrequest
177 // fetcher.
178 responseImpl->SetStatus(response.HttpStatusCode());
179 responseImpl->SetURL(redirect_url.GetString().Utf16());
180 responseImpl->SetReadOnly(true);
181
182 status_ = UR_CANCELED;
183 error_code_ = ERR_ABORTED;
184
185 OnComplete();
186 }
187
OnResponse(const WebURLResponse & response)188 void OnResponse(const WebURLResponse& response) {
189 response_was_cached_ = blink_glue::ResponseWasCached(response);
190 response_ = CefResponse::Create();
191 CefResponseImpl* responseImpl =
192 static_cast<CefResponseImpl*>(response_.get());
193 responseImpl->Set(response);
194 responseImpl->SetReadOnly(true);
195
196 download_data_total_ = response.ExpectedContentLength();
197 }
198
OnError(const WebURLError & error)199 void OnError(const WebURLError& error) {
200 if (status_ == UR_IO_PENDING) {
201 status_ = UR_FAILED;
202 error_code_ = static_cast<cef_errorcode_t>(error.reason());
203 }
204
205 OnComplete();
206 }
207
OnComplete()208 void OnComplete() {
209 if (body_handle_.is_valid()) {
210 return;
211 }
212
213 if (status_ == UR_IO_PENDING) {
214 status_ = UR_SUCCESS;
215 NotifyUploadProgressIfNecessary();
216 }
217
218 if (loader_.get())
219 loader_.reset(nullptr);
220
221 DCHECK(url_request_.get());
222 client_->OnRequestComplete(url_request_.get());
223
224 // This may result in the Context object being deleted.
225 url_request_ = nullptr;
226 }
227
OnBodyReadable(MojoResult,const mojo::HandleSignalsState &)228 void OnBodyReadable(MojoResult, const mojo::HandleSignalsState&) {
229 const void* buffer = nullptr;
230 uint32_t read_bytes = 0;
231 MojoResult result = body_handle_->BeginReadData(&buffer, &read_bytes,
232 MOJO_READ_DATA_FLAG_NONE);
233 if (result == MOJO_RESULT_SHOULD_WAIT) {
234 body_watcher_.ArmOrNotify();
235 return;
236 }
237
238 if (result == MOJO_RESULT_FAILED_PRECONDITION) {
239 // Whole body has been read.
240 body_handle_.reset();
241 body_watcher_.Cancel();
242 OnComplete();
243 return;
244 }
245
246 if (result != MOJO_RESULT_OK) {
247 // Something went wrong.
248 body_handle_.reset();
249 body_watcher_.Cancel();
250 OnComplete();
251 return;
252 }
253
254 download_data_received_ += read_bytes;
255
256 client_->OnDownloadProgress(url_request_.get(), download_data_received_,
257 download_data_total_);
258
259 if (!(request_->GetFlags() & UR_FLAG_NO_DOWNLOAD_DATA)) {
260 client_->OnDownloadData(url_request_.get(), buffer, read_bytes);
261 }
262
263 body_handle_->EndReadData(read_bytes);
264 body_watcher_.ArmOrNotify();
265 }
266
OnStartLoadingResponseBody(mojo::ScopedDataPipeConsumerHandle response_body)267 void OnStartLoadingResponseBody(
268 mojo::ScopedDataPipeConsumerHandle response_body) {
269 DCHECK(response_body);
270 DCHECK(!body_handle_);
271 body_handle_ = std::move(response_body);
272
273 body_watcher_.Watch(
274 body_handle_.get(),
275 MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
276 MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
277 base::BindRepeating(&CefRenderURLRequest::Context::OnBodyReadable,
278 base::Unretained(this)));
279 body_watcher_.ArmOrNotify();
280 }
281
OnDownloadProgress(int64_t current)282 void OnDownloadProgress(int64_t current) {
283 DCHECK(url_request_.get());
284
285 NotifyUploadProgressIfNecessary();
286
287 download_data_received_ += current;
288 client_->OnDownloadProgress(url_request_.get(), download_data_received_,
289 download_data_total_);
290 }
291
OnDownloadData(const char * data,int dataLength)292 void OnDownloadData(const char* data, int dataLength) {
293 DCHECK(url_request_.get());
294 client_->OnDownloadData(url_request_.get(), data, dataLength);
295 }
296
OnUploadProgress(int64_t current,int64_t total)297 void OnUploadProgress(int64_t current, int64_t total) {
298 DCHECK(url_request_.get());
299 if (current == total)
300 got_upload_progress_complete_ = true;
301 client_->OnUploadProgress(url_request_.get(), current, total);
302 }
303
request() const304 CefRefPtr<CefRequest> request() const { return request_; }
client() const305 CefRefPtr<CefURLRequestClient> client() const { return client_; }
status() const306 CefURLRequest::Status status() const { return status_; }
error_code() const307 CefURLRequest::ErrorCode error_code() const { return error_code_; }
response() const308 CefRefPtr<CefResponse> response() const { return response_; }
response_was_cached() const309 bool response_was_cached() const { return response_was_cached_; }
310
311 private:
312 friend class base::RefCountedThreadSafe<CefRenderURLRequest::Context>;
313
~Context()314 virtual ~Context() {}
315
NotifyUploadProgressIfNecessary()316 void NotifyUploadProgressIfNecessary() {
317 if (!got_upload_progress_complete_ && upload_data_size_ > 0) {
318 // Upload notifications are sent using a timer and may not occur if the
319 // request completes too quickly. We therefore send the notification here
320 // if necessary.
321 url_client_->DidSendData(upload_data_size_, upload_data_size_);
322 got_upload_progress_complete_ = true;
323 }
324 }
325
326 // Members only accessed on the initialization thread.
327 CefRefPtr<CefRenderURLRequest> url_request_;
328 CefRefPtr<CefFrame> frame_;
329 CefRefPtr<CefRequest> request_;
330 CefRefPtr<CefURLRequestClient> client_;
331 CefURLRequest::Status status_;
332 CefURLRequest::ErrorCode error_code_;
333 CefRefPtr<CefResponse> response_;
334 mojo::ScopedDataPipeConsumerHandle body_handle_;
335 mojo::SimpleWatcher body_watcher_;
336 bool response_was_cached_;
337 std::unique_ptr<blink::WebURLLoader> loader_;
338 std::unique_ptr<CefWebURLLoaderClient> url_client_;
339 int64_t upload_data_size_;
340 bool got_upload_progress_complete_;
341 int64_t download_data_received_;
342 int64_t download_data_total_;
343 };
344
345 // CefWebURLLoaderClient --------------------------------------------------
346
347 namespace {
348
CefWebURLLoaderClient(CefRenderURLRequest::Context * context,int request_flags)349 CefWebURLLoaderClient::CefWebURLLoaderClient(
350 CefRenderURLRequest::Context* context,
351 int request_flags)
352 : context_(context), request_flags_(request_flags) {}
353
~CefWebURLLoaderClient()354 CefWebURLLoaderClient::~CefWebURLLoaderClient() {}
355
DidSendData(uint64_t bytes_sent,uint64_t total_bytes_to_be_sent)356 void CefWebURLLoaderClient::DidSendData(uint64_t bytes_sent,
357 uint64_t total_bytes_to_be_sent) {
358 if (request_flags_ & UR_FLAG_REPORT_UPLOAD_PROGRESS)
359 context_->OnUploadProgress(bytes_sent, total_bytes_to_be_sent);
360 }
361
DidReceiveResponse(const WebURLResponse & response)362 void CefWebURLLoaderClient::DidReceiveResponse(const WebURLResponse& response) {
363 context_->OnResponse(response);
364 }
365
DidReceiveData(const char * data,int dataLength)366 void CefWebURLLoaderClient::DidReceiveData(const char* data, int dataLength) {
367 context_->OnDownloadProgress(dataLength);
368
369 if (!(request_flags_ & UR_FLAG_NO_DOWNLOAD_DATA))
370 context_->OnDownloadData(data, dataLength);
371 }
372
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)373 void CefWebURLLoaderClient::DidFinishLoading(base::TimeTicks finish_time,
374 int64_t total_encoded_data_length,
375 int64_t total_encoded_body_length,
376 int64_t total_decoded_body_length,
377 bool should_report_corb_blocking) {
378 context_->OnComplete();
379 }
380
DidFail(const WebURLError & error,base::TimeTicks finish_time,int64_t total_encoded_data_length,int64_t total_encoded_body_length,int64_t total_decoded_body_length)381 void CefWebURLLoaderClient::DidFail(const WebURLError& error,
382 base::TimeTicks finish_time,
383 int64_t total_encoded_data_length,
384 int64_t total_encoded_body_length,
385 int64_t total_decoded_body_length) {
386 context_->OnError(error);
387 }
388
DidStartLoadingResponseBody(mojo::ScopedDataPipeConsumerHandle response_body)389 void CefWebURLLoaderClient::DidStartLoadingResponseBody(
390 mojo::ScopedDataPipeConsumerHandle response_body) {
391 context_->OnStartLoadingResponseBody(std::move(response_body));
392 }
393
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,std::vector<std::string> * removed_headers,bool insecure_scheme_was_upgraded)394 bool CefWebURLLoaderClient::WillFollowRedirect(
395 const WebURL& new_url,
396 const net::SiteForCookies& new_site_for_cookies,
397 const WebString& new_referrer,
398 network::mojom::ReferrerPolicy new_referrer_policy,
399 const WebString& new_method,
400 const WebURLResponse& passed_redirect_response,
401 bool& report_raw_headers,
402 std::vector<std::string>* removed_headers,
403 bool insecure_scheme_was_upgraded) {
404 if (request_flags_ & UR_FLAG_STOP_ON_REDIRECT) {
405 context_->OnStopRedirect(new_url, passed_redirect_response);
406 return false;
407 }
408 return true;
409 }
410
411 } // namespace
412
413 // CefRenderURLRequest --------------------------------------------------------
414
CefRenderURLRequest(CefRefPtr<CefFrame> frame,CefRefPtr<CefRequest> request,CefRefPtr<CefURLRequestClient> client)415 CefRenderURLRequest::CefRenderURLRequest(
416 CefRefPtr<CefFrame> frame,
417 CefRefPtr<CefRequest> request,
418 CefRefPtr<CefURLRequestClient> client) {
419 DCHECK(frame);
420 DCHECK(request);
421 DCHECK(client);
422 context_ = new Context(this, frame, request, client);
423 }
424
~CefRenderURLRequest()425 CefRenderURLRequest::~CefRenderURLRequest() {}
426
Start()427 bool CefRenderURLRequest::Start() {
428 if (!VerifyContext())
429 return false;
430 return context_->Start();
431 }
432
GetRequest()433 CefRefPtr<CefRequest> CefRenderURLRequest::GetRequest() {
434 if (!VerifyContext())
435 return nullptr;
436 return context_->request();
437 }
438
GetClient()439 CefRefPtr<CefURLRequestClient> CefRenderURLRequest::GetClient() {
440 if (!VerifyContext())
441 return nullptr;
442 return context_->client();
443 }
444
GetRequestStatus()445 CefURLRequest::Status CefRenderURLRequest::GetRequestStatus() {
446 if (!VerifyContext())
447 return UR_UNKNOWN;
448 return context_->status();
449 }
450
GetRequestError()451 CefURLRequest::ErrorCode CefRenderURLRequest::GetRequestError() {
452 if (!VerifyContext())
453 return ERR_NONE;
454 return context_->error_code();
455 }
456
GetResponse()457 CefRefPtr<CefResponse> CefRenderURLRequest::GetResponse() {
458 if (!VerifyContext())
459 return nullptr;
460 return context_->response();
461 }
462
ResponseWasCached()463 bool CefRenderURLRequest::ResponseWasCached() {
464 if (!VerifyContext())
465 return false;
466 return context_->response_was_cached();
467 }
468
Cancel()469 void CefRenderURLRequest::Cancel() {
470 if (!VerifyContext())
471 return;
472 return context_->Cancel();
473 }
474
VerifyContext()475 bool CefRenderURLRequest::VerifyContext() {
476 DCHECK(context_.get());
477 if (!CEF_CURRENTLY_ON_RT()) {
478 NOTREACHED() << "called on invalid thread";
479 return false;
480 }
481
482 return true;
483 }
484