1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "content/renderer/loader/sync_load_context.h"
6
7 #include <string>
8
9 #include "base/bind.h"
10 #include "base/check_op.h"
11 #include "base/memory/ptr_util.h"
12 #include "base/optional.h"
13 #include "base/synchronization/waitable_event.h"
14 #include "base/time/time.h"
15 #include "mojo/public/cpp/bindings/associated_remote.h"
16 #include "net/url_request/redirect_info.h"
17 #include "services/network/public/cpp/resource_request.h"
18 #include "services/network/public/mojom/url_response_head.mojom.h"
19 #include "third_party/blink/public/common/client_hints/client_hints.h"
20 #include "third_party/blink/public/common/loader/url_loader_throttle.h"
21 #include "third_party/blink/public/platform/resource_load_info_notifier_wrapper.h"
22 #include "third_party/blink/public/platform/sync_load_response.h"
23
24 namespace content {
25
26 // An inner helper class to manage the SyncLoadContext's events and timeouts,
27 // so that we can stop or resumse all of them at once.
28 class SyncLoadContext::SignalHelper final {
29 public:
SignalHelper(SyncLoadContext * context,base::WaitableEvent * redirect_or_response_event,base::WaitableEvent * abort_event,base::TimeDelta timeout)30 SignalHelper(SyncLoadContext* context,
31 base::WaitableEvent* redirect_or_response_event,
32 base::WaitableEvent* abort_event,
33 base::TimeDelta timeout)
34 : context_(context),
35 redirect_or_response_event_(redirect_or_response_event),
36 abort_event_(abort_event) {
37 // base::TimeDelta::Max() means no timeout.
38 if (timeout != base::TimeDelta::Max()) {
39 // Instantiate a base::OneShotTimer instance.
40 timeout_timer_.emplace();
41 }
42 Start(timeout);
43 }
44
SignalRedirectOrResponseComplete()45 void SignalRedirectOrResponseComplete() {
46 abort_watcher_.StopWatching();
47 if (timeout_timer_)
48 timeout_timer_->AbandonAndStop();
49 redirect_or_response_event_->Signal();
50 }
51
RestartAfterRedirect()52 bool RestartAfterRedirect() {
53 if (abort_event_ && abort_event_->IsSignaled())
54 return false;
55
56 base::TimeDelta timeout_remainder = base::TimeDelta::Max();
57 if (timeout_timer_) {
58 timeout_remainder =
59 timeout_timer_->desired_run_time() - base::TimeTicks::Now();
60 if (timeout_remainder <= base::TimeDelta())
61 return false;
62 }
63 Start(timeout_remainder);
64 return true;
65 }
66
67 private:
Start(base::TimeDelta timeout)68 void Start(base::TimeDelta timeout) {
69 DCHECK(!redirect_or_response_event_->IsSignaled());
70 if (abort_event_) {
71 abort_watcher_.StartWatching(
72 abort_event_,
73 base::BindOnce(&SyncLoadContext::OnAbort, base::Unretained(context_)),
74 context_->task_runner_);
75 }
76 if (timeout_timer_) {
77 DCHECK_NE(base::TimeDelta::Max(), timeout);
78 timeout_timer_->Start(FROM_HERE, timeout, context_,
79 &SyncLoadContext::OnTimeout);
80 }
81 }
82
83 SyncLoadContext* context_;
84 base::WaitableEvent* redirect_or_response_event_;
85 base::WaitableEvent* abort_event_;
86 base::WaitableEventWatcher abort_watcher_;
87 base::Optional<base::OneShotTimer> timeout_timer_;
88 };
89
90 // static
StartAsyncWithWaitableEvent(std::unique_ptr<network::ResourceRequest> request,int routing_id,scoped_refptr<base::SingleThreadTaskRunner> loading_task_runner,const net::NetworkTrafficAnnotationTag & traffic_annotation,uint32_t loader_options,std::unique_ptr<network::PendingSharedURLLoaderFactory> pending_url_loader_factory,std::vector<std::unique_ptr<blink::URLLoaderThrottle>> throttles,blink::SyncLoadResponse * response,SyncLoadContext ** context_for_redirect,base::WaitableEvent * redirect_or_response_event,base::WaitableEvent * abort_event,base::TimeDelta timeout,mojo::PendingRemote<blink::mojom::BlobRegistry> download_to_blob_registry,const std::vector<std::string> & cors_exempt_header_list,std::unique_ptr<blink::ResourceLoadInfoNotifierWrapper> resource_load_info_notifier_wrapper)91 void SyncLoadContext::StartAsyncWithWaitableEvent(
92 std::unique_ptr<network::ResourceRequest> request,
93 int routing_id,
94 scoped_refptr<base::SingleThreadTaskRunner> loading_task_runner,
95 const net::NetworkTrafficAnnotationTag& traffic_annotation,
96 uint32_t loader_options,
97 std::unique_ptr<network::PendingSharedURLLoaderFactory>
98 pending_url_loader_factory,
99 std::vector<std::unique_ptr<blink::URLLoaderThrottle>> throttles,
100 blink::SyncLoadResponse* response,
101 SyncLoadContext** context_for_redirect,
102 base::WaitableEvent* redirect_or_response_event,
103 base::WaitableEvent* abort_event,
104 base::TimeDelta timeout,
105 mojo::PendingRemote<blink::mojom::BlobRegistry> download_to_blob_registry,
106 const std::vector<std::string>& cors_exempt_header_list,
107 std::unique_ptr<blink::ResourceLoadInfoNotifierWrapper>
108 resource_load_info_notifier_wrapper) {
109 auto* context = new SyncLoadContext(
110 request.get(), std::move(pending_url_loader_factory), response,
111 context_for_redirect, redirect_or_response_event, abort_event, timeout,
112 std::move(download_to_blob_registry), loading_task_runner,
113 cors_exempt_header_list);
114 context->request_id_ = context->resource_dispatcher_->StartAsync(
115 std::move(request), routing_id, std::move(loading_task_runner),
116 traffic_annotation, loader_options, base::WrapUnique(context),
117 context->url_loader_factory_, std::move(throttles),
118 std::move(resource_load_info_notifier_wrapper));
119 }
120
SyncLoadContext(network::ResourceRequest * request,std::unique_ptr<network::PendingSharedURLLoaderFactory> url_loader_factory,blink::SyncLoadResponse * response,SyncLoadContext ** context_for_redirect,base::WaitableEvent * redirect_or_response_event,base::WaitableEvent * abort_event,base::TimeDelta timeout,mojo::PendingRemote<blink::mojom::BlobRegistry> download_to_blob_registry,scoped_refptr<base::SingleThreadTaskRunner> task_runner,const std::vector<std::string> & cors_exempt_header_list)121 SyncLoadContext::SyncLoadContext(
122 network::ResourceRequest* request,
123 std::unique_ptr<network::PendingSharedURLLoaderFactory> url_loader_factory,
124 blink::SyncLoadResponse* response,
125 SyncLoadContext** context_for_redirect,
126 base::WaitableEvent* redirect_or_response_event,
127 base::WaitableEvent* abort_event,
128 base::TimeDelta timeout,
129 mojo::PendingRemote<blink::mojom::BlobRegistry> download_to_blob_registry,
130 scoped_refptr<base::SingleThreadTaskRunner> task_runner,
131 const std::vector<std::string>& cors_exempt_header_list)
132 : response_(response),
133 context_for_redirect_(context_for_redirect),
134 body_watcher_(FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::MANUAL),
135 download_to_blob_registry_(std::move(download_to_blob_registry)),
136 task_runner_(std::move(task_runner)),
137 signals_(std::make_unique<SignalHelper>(this,
138 redirect_or_response_event,
139 abort_event,
140 timeout)) {
141 if (download_to_blob_registry_)
142 mode_ = Mode::kBlob;
143
144 url_loader_factory_ =
145 network::SharedURLLoaderFactory::Create(std::move(url_loader_factory));
146
147 // Constructs a new ResourceDispatcher specifically for this request.
148 resource_dispatcher_ = std::make_unique<ResourceDispatcher>();
149 resource_dispatcher_->SetCorsExemptHeaderList(cors_exempt_header_list);
150
151 // Initialize the final URL with the original request URL. It will be
152 // overwritten on redirects.
153 response_->url = request->url;
154 }
155
~SyncLoadContext()156 SyncLoadContext::~SyncLoadContext() {}
157
OnUploadProgress(uint64_t position,uint64_t size)158 void SyncLoadContext::OnUploadProgress(uint64_t position, uint64_t size) {}
159
OnReceivedRedirect(const net::RedirectInfo & redirect_info,network::mojom::URLResponseHeadPtr head,std::vector<std::string> * removed_headers)160 bool SyncLoadContext::OnReceivedRedirect(
161 const net::RedirectInfo& redirect_info,
162 network::mojom::URLResponseHeadPtr head,
163 std::vector<std::string>* removed_headers) {
164 DCHECK(!Completed());
165 if (removed_headers) {
166 // TODO(yoav): Get the actual FeaturePolicy here to support selective
167 // removal for sync XHR.
168 blink::FindClientHintsToRemove(nullptr /* feature_policy */,
169 redirect_info.new_url, removed_headers);
170 }
171
172 response_->url = redirect_info.new_url;
173 response_->head = std::move(head);
174 response_->redirect_info = redirect_info;
175 *context_for_redirect_ = this;
176 resource_dispatcher_->SetDefersLoading(
177 request_id_, blink::WebURLLoader::DeferType::kDeferred);
178 signals_->SignalRedirectOrResponseComplete();
179 return true;
180 }
181
EvictFromBackForwardCache(blink::mojom::RendererEvictionReason reason)182 void SyncLoadContext::EvictFromBackForwardCache(
183 blink::mojom::RendererEvictionReason reason) {
184 return;
185 }
186
FollowRedirect()187 void SyncLoadContext::FollowRedirect() {
188 if (!signals_->RestartAfterRedirect()) {
189 CancelRedirect();
190 return;
191 }
192
193 response_->redirect_info = net::RedirectInfo();
194 *context_for_redirect_ = nullptr;
195
196 resource_dispatcher_->SetDefersLoading(
197 request_id_, blink::WebURLLoader::DeferType::kNotDeferred);
198 }
199
CancelRedirect()200 void SyncLoadContext::CancelRedirect() {
201 response_->redirect_info = net::RedirectInfo();
202 *context_for_redirect_ = nullptr;
203
204 response_->error_code = net::ERR_ABORTED;
205 CompleteRequest();
206 }
207
OnReceivedResponse(network::mojom::URLResponseHeadPtr head)208 void SyncLoadContext::OnReceivedResponse(
209 network::mojom::URLResponseHeadPtr head) {
210 DCHECK(!Completed());
211 response_->head = std::move(head);
212 }
213
OnStartLoadingResponseBody(mojo::ScopedDataPipeConsumerHandle body)214 void SyncLoadContext::OnStartLoadingResponseBody(
215 mojo::ScopedDataPipeConsumerHandle body) {
216 if (mode_ == Mode::kBlob) {
217 DCHECK(download_to_blob_registry_);
218 DCHECK(!blob_response_started_);
219
220 blob_response_started_ = true;
221
222 download_to_blob_registry_->RegisterFromStream(
223 response_->head->mime_type, "",
224 std::max<int64_t>(0, response_->head->content_length), std::move(body),
225 mojo::NullAssociatedRemote(),
226 base::BindOnce(&SyncLoadContext::OnFinishCreatingBlob,
227 base::Unretained(this)));
228 return;
229 }
230 DCHECK_EQ(Mode::kInitial, mode_);
231 mode_ = Mode::kDataPipe;
232 // setup datapipe to read.
233 body_handle_ = std::move(body);
234 body_watcher_.Watch(
235 body_handle_.get(),
236 MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
237 MOJO_TRIGGER_CONDITION_SIGNALS_SATISFIED,
238 base::BindRepeating(&SyncLoadContext::OnBodyReadable,
239 base::Unretained(this)));
240 body_watcher_.ArmOrNotify();
241 }
242
OnTransferSizeUpdated(int transfer_size_diff)243 void SyncLoadContext::OnTransferSizeUpdated(int transfer_size_diff) {}
244
OnCompletedRequest(const network::URLLoaderCompletionStatus & status)245 void SyncLoadContext::OnCompletedRequest(
246 const network::URLLoaderCompletionStatus& status) {
247 if (Completed()) {
248 // It means the response has been aborted due to an error before finishing
249 // the response.
250 return;
251 }
252 request_completed_ = true;
253 response_->error_code = status.error_code;
254 response_->extended_error_code = status.extended_error_code;
255 response_->resolve_error_info = status.resolve_error_info;
256 response_->cors_error = status.cors_error_status;
257 response_->head->encoded_data_length = status.encoded_data_length;
258 response_->head->encoded_body_length = status.encoded_body_length;
259 if ((blob_response_started_ && !blob_finished_) || body_handle_.is_valid()) {
260 // The body is still begin downloaded as a Blob, or being read through the
261 // handle. Wait until it's completed.
262 return;
263 }
264 CompleteRequest();
265 }
266
OnFinishCreatingBlob(blink::mojom::SerializedBlobPtr blob)267 void SyncLoadContext::OnFinishCreatingBlob(
268 blink::mojom::SerializedBlobPtr blob) {
269 DCHECK(!Completed());
270 blob_finished_ = true;
271 response_->downloaded_blob = std::move(blob);
272 if (request_completed_)
273 CompleteRequest();
274 }
275
OnBodyReadable(MojoResult,const mojo::HandleSignalsState &)276 void SyncLoadContext::OnBodyReadable(MojoResult,
277 const mojo::HandleSignalsState&) {
278 DCHECK_EQ(Mode::kDataPipe, mode_);
279 DCHECK(body_handle_.is_valid());
280 const void* buffer = nullptr;
281 uint32_t read_bytes = 0;
282 MojoResult result = body_handle_->BeginReadData(&buffer, &read_bytes,
283 MOJO_READ_DATA_FLAG_NONE);
284 if (result == MOJO_RESULT_SHOULD_WAIT) {
285 body_watcher_.ArmOrNotify();
286 return;
287 }
288 if (result == MOJO_RESULT_FAILED_PRECONDITION) {
289 // Whole body has been read.
290 body_handle_.reset();
291 body_watcher_.Cancel();
292 if (request_completed_)
293 CompleteRequest();
294 return;
295 }
296 if (result != MOJO_RESULT_OK) {
297 // Something went wrong.
298 body_handle_.reset();
299 body_watcher_.Cancel();
300 response_->error_code = net::ERR_FAILED;
301 CompleteRequest();
302 return;
303 }
304
305 response_->data.Append(static_cast<const char*>(buffer), read_bytes);
306 body_handle_->EndReadData(read_bytes);
307 body_watcher_.ArmOrNotify();
308 }
309
OnAbort(base::WaitableEvent * event)310 void SyncLoadContext::OnAbort(base::WaitableEvent* event) {
311 DCHECK(!Completed());
312 response_->error_code = net::ERR_ABORTED;
313 CompleteRequest();
314 }
315
OnTimeout()316 void SyncLoadContext::OnTimeout() {
317 // OnTimeout() must not be called after CompleteRequest() was called, because
318 // the OneShotTimer must have been stopped.
319 DCHECK(!Completed());
320 response_->error_code = net::ERR_TIMED_OUT;
321 CompleteRequest();
322 }
323
CompleteRequest()324 void SyncLoadContext::CompleteRequest() {
325 DCHECK(blob_finished_ || (mode_ != Mode::kBlob));
326 DCHECK(!body_handle_.is_valid());
327 body_watcher_.Cancel();
328 signals_->SignalRedirectOrResponseComplete();
329 signals_ = nullptr;
330 response_ = nullptr;
331
332 // This will indirectly cause this object to be deleted.
333 resource_dispatcher_->RemovePendingRequest(request_id_, task_runner_);
334 }
335
Completed() const336 bool SyncLoadContext::Completed() const {
337 DCHECK_EQ(!signals_, !response_);
338 return !response_;
339 }
340
341 } // namespace content
342