1 /*
2 * Copyright (C) 2009 Apple Inc. All Rights Reserved.
3 * Copyright (C) 2009, 2011 Google Inc. All Rights Reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 *
26 */
27
28 #include "third_party/blink/renderer/core/workers/worker_classic_script_loader.h"
29
30 #include <memory>
31 #include "base/memory/scoped_refptr.h"
32 #include "services/network/public/mojom/ip_address_space.mojom-blink.h"
33 #include "third_party/blink/public/common/features.h"
34 #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-blink.h"
35 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
36 #include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
37 #include "third_party/blink/renderer/core/html/parser/text_resource_decoder.h"
38 #include "third_party/blink/renderer/core/inspector/console_message.h"
39 #include "third_party/blink/renderer/core/loader/resource/script_resource.h"
40 #include "third_party/blink/renderer/core/origin_trials/origin_trial_context.h"
41 #include "third_party/blink/renderer/core/workers/worker_global_scope.h"
42 #include "third_party/blink/renderer/platform/loader/fetch/detachable_use_counter.h"
43 #include "third_party/blink/renderer/platform/loader/fetch/fetch_client_settings_object.h"
44 #include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
45 #include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher_properties.h"
46 #include "third_party/blink/renderer/platform/loader/fetch/resource_loader_options.h"
47 #include "third_party/blink/renderer/platform/loader/fetch/resource_response.h"
48 #include "third_party/blink/renderer/platform/loader/fetch/text_resource_decoder_options.h"
49 #include "third_party/blink/renderer/platform/loader/fetch/unique_identifier.h"
50 #include "third_party/blink/renderer/platform/network/content_security_policy_response_headers.h"
51 #include "third_party/blink/renderer/platform/network/http_names.h"
52 #include "third_party/blink/renderer/platform/network/network_utils.h"
53 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
54 #include "third_party/blink/renderer/platform/weborigin/referrer.h"
55 #include "third_party/blink/renderer/platform/weborigin/security_origin.h"
56
57 namespace blink {
58
59 namespace {
60
61 // CheckSameOriginEnforcement() functions return non-null String on error.
62 //
63 // WorkerGlobalScope's SecurityOrigin is initialized to request URL's
64 // origin at the construction of WorkerGlobalScope, while
65 // WorkerGlobalScope's URL is set to response URL
66 // (ResourceResponse::ResponseURL()).
67 // These functions are used to ensure the SecurityOrigin and the URL to be
68 // consistent. https://crbug.com/861564
69 //
70 // TODO(hiroshige): Merge with similar code in other places.
CheckSameOriginEnforcement(const KURL & request_url,const KURL & response_url)71 String CheckSameOriginEnforcement(const KURL& request_url,
72 const KURL& response_url) {
73 if (request_url != response_url &&
74 !SecurityOrigin::AreSameOrigin(request_url, response_url)) {
75 return "Refused to load the top-level worker script from '" +
76 response_url.ElidedString() +
77 "' because it doesn't match the origin of the request URL '" +
78 request_url.ElidedString() + "'";
79 }
80 return String();
81 }
82
CheckSameOriginEnforcement(const KURL & request_url,const ResourceResponse & response)83 String CheckSameOriginEnforcement(const KURL& request_url,
84 const ResourceResponse& response) {
85 // While this check is not strictly necessary as CurrentRequestUrl() is not
86 // used as WorkerGlobalScope's URL, it is probably safer to reject cases like
87 // Origin A(request_url)
88 // =(cross-origin redirects)=> Origin B(CurrentRequestUrl())
89 // =(ServiceWorker interception)=> Origin A(ResponseUrl())
90 // which doesn't seem to have valid use cases.
91 String error =
92 CheckSameOriginEnforcement(request_url, response.CurrentRequestUrl());
93 if (!error.IsNull())
94 return error;
95
96 // This check is directly required to ensure the consistency between
97 // WorkerGlobalScope's SecurityOrigin and URL.
98 return CheckSameOriginEnforcement(request_url, response.ResponseUrl());
99 }
100
101 } // namespace
102
WorkerClassicScriptLoader()103 WorkerClassicScriptLoader::WorkerClassicScriptLoader()
104 : response_address_space_(network::mojom::IPAddressSpace::kPublic) {}
105
LoadSynchronously(ExecutionContext & execution_context,ResourceFetcher * fetch_client_settings_object_fetcher,const KURL & url,mojom::blink::RequestContextType request_context,network::mojom::RequestDestination destination)106 void WorkerClassicScriptLoader::LoadSynchronously(
107 ExecutionContext& execution_context,
108 ResourceFetcher* fetch_client_settings_object_fetcher,
109 const KURL& url,
110 mojom::blink::RequestContextType request_context,
111 network::mojom::RequestDestination destination) {
112 DCHECK(fetch_client_settings_object_fetcher);
113 url_ = url;
114 fetch_client_settings_object_fetcher_ = fetch_client_settings_object_fetcher;
115
116 ResourceRequest request(url);
117 request.SetHttpMethod(http_names::kGET);
118 request.SetExternalRequestStateFromRequestorAddressSpace(
119 fetch_client_settings_object_fetcher_->GetProperties()
120 .GetFetchClientSettingsObject()
121 .GetAddressSpace());
122 request.SetRequestContext(request_context);
123 request.SetRequestDestination(destination);
124
125 SECURITY_DCHECK(execution_context.IsWorkerGlobalScope());
126
127 ResourceLoaderOptions resource_loader_options(
128 execution_context.GetCurrentWorld());
129 resource_loader_options.parser_disposition =
130 ParserDisposition::kNotParserInserted;
131 resource_loader_options.synchronous_policy = kRequestSynchronously;
132
133 threadable_loader_ = MakeGarbageCollected<ThreadableLoader>(
134 execution_context, this, resource_loader_options,
135 fetch_client_settings_object_fetcher);
136 threadable_loader_->Start(std::move(request));
137 }
138
LoadTopLevelScriptAsynchronously(ExecutionContext & execution_context,ResourceFetcher * fetch_client_settings_object_fetcher,const KURL & url,std::unique_ptr<WorkerMainScriptLoadParameters> worker_main_script_load_params,mojom::blink::RequestContextType request_context,network::mojom::RequestDestination destination,network::mojom::RequestMode request_mode,network::mojom::CredentialsMode credentials_mode,base::OnceClosure response_callback,base::OnceClosure finished_callback,RejectCoepUnsafeNone reject_coep_unsafe_none,mojo::PendingRemote<network::mojom::blink::URLLoaderFactory> blob_url_loader_factory)139 void WorkerClassicScriptLoader::LoadTopLevelScriptAsynchronously(
140 ExecutionContext& execution_context,
141 ResourceFetcher* fetch_client_settings_object_fetcher,
142 const KURL& url,
143 std::unique_ptr<WorkerMainScriptLoadParameters>
144 worker_main_script_load_params,
145 mojom::blink::RequestContextType request_context,
146 network::mojom::RequestDestination destination,
147 network::mojom::RequestMode request_mode,
148 network::mojom::CredentialsMode credentials_mode,
149 base::OnceClosure response_callback,
150 base::OnceClosure finished_callback,
151 RejectCoepUnsafeNone reject_coep_unsafe_none,
152 mojo::PendingRemote<network::mojom::blink::URLLoaderFactory>
153 blob_url_loader_factory) {
154 DCHECK(fetch_client_settings_object_fetcher);
155 DCHECK(response_callback || finished_callback);
156 response_callback_ = std::move(response_callback);
157 finished_callback_ = std::move(finished_callback);
158 url_ = url;
159 fetch_client_settings_object_fetcher_ = fetch_client_settings_object_fetcher;
160 is_top_level_script_ = true;
161 ResourceRequest request(url);
162 request.SetHttpMethod(http_names::kGET);
163 request.SetExternalRequestStateFromRequestorAddressSpace(
164 fetch_client_settings_object_fetcher_->GetProperties()
165 .GetFetchClientSettingsObject()
166 .GetAddressSpace());
167 request.SetRequestContext(request_context);
168 request.SetRequestDestination(destination);
169 request.SetMode(request_mode);
170 request.SetCredentialsMode(credentials_mode);
171
172 // Use WorkerMainScriptLoader to load the main script for dedicated workers
173 // (PlzDedicatedWorker) and shared workers.
174 if (worker_main_script_load_params) {
175 request.SetInspectorId(CreateUniqueIdentifier());
176 request.SetReferrerString(Referrer::NoReferrer());
177 request.SetPriority(ResourceLoadPriority::kHigh);
178 FetchParameters fetch_params(
179 std::move(request),
180 ResourceLoaderOptions(execution_context.GetCurrentWorld()));
181 worker_main_script_loader_ = MakeGarbageCollected<WorkerMainScriptLoader>();
182 worker_main_script_loader_->Start(
183 fetch_params, std::move(worker_main_script_load_params),
184 &fetch_client_settings_object_fetcher_->Context(),
185 fetch_client_settings_object_fetcher->GetResourceLoadObserver(), this);
186 return;
187 }
188
189 ResourceLoaderOptions resource_loader_options(
190 execution_context.GetCurrentWorld());
191 need_to_cancel_ = true;
192 resource_loader_options.reject_coep_unsafe_none = reject_coep_unsafe_none;
193 if (blob_url_loader_factory) {
194 resource_loader_options.url_loader_factory =
195 base::MakeRefCounted<base::RefCountedData<
196 mojo::PendingRemote<network::mojom::blink::URLLoaderFactory>>>(
197 std::move(blob_url_loader_factory));
198 }
199 threadable_loader_ = MakeGarbageCollected<ThreadableLoader>(
200 execution_context, this, resource_loader_options,
201 fetch_client_settings_object_fetcher);
202 threadable_loader_->Start(std::move(request));
203 if (failed_)
204 NotifyFinished();
205 }
206
ResponseURL() const207 const KURL& WorkerClassicScriptLoader::ResponseURL() const {
208 DCHECK(!Failed());
209 return response_url_;
210 }
211
DidReceiveResponse(uint64_t identifier,const ResourceResponse & response)212 void WorkerClassicScriptLoader::DidReceiveResponse(
213 uint64_t identifier,
214 const ResourceResponse& response) {
215 if (response.HttpStatusCode() / 100 != 2 && response.HttpStatusCode()) {
216 NotifyError();
217 return;
218 }
219 if (!AllowedByNosniff::MimeTypeAsScript(
220 fetch_client_settings_object_fetcher_->GetUseCounter(),
221 &fetch_client_settings_object_fetcher_->GetConsoleLogger(), response,
222 fetch_client_settings_object_fetcher_->GetProperties()
223 .GetFetchClientSettingsObject()
224 .MimeTypeCheckForClassicWorkerScript())) {
225 NotifyError();
226 return;
227 }
228
229 if (is_top_level_script_) {
230 String error = CheckSameOriginEnforcement(url_, response);
231 if (!error.IsNull()) {
232 fetch_client_settings_object_fetcher_->GetConsoleLogger()
233 .AddConsoleMessage(mojom::ConsoleMessageSource::kSecurity,
234 mojom::ConsoleMessageLevel::kError, error);
235 NotifyError();
236 return;
237 }
238 }
239
240 identifier_ = identifier;
241 response_url_ = response.ResponseUrl();
242 response_encoding_ = response.TextEncodingName();
243 app_cache_id_ = response.AppCacheID();
244 response_address_space_ = response.AddressSpace();
245
246 referrer_policy_ = response.HttpHeaderField(http_names::kReferrerPolicy);
247 ProcessContentSecurityPolicy(response);
248 origin_trial_tokens_ = OriginTrialContext::ParseHeaderValue(
249 response.HttpHeaderField(http_names::kOriginTrial));
250
251 if (response_callback_)
252 std::move(response_callback_).Run();
253 }
254
DidReceiveData(const char * data,unsigned len)255 void WorkerClassicScriptLoader::DidReceiveData(const char* data, unsigned len) {
256 if (failed_)
257 return;
258
259 if (!decoder_) {
260 decoder_ = std::make_unique<TextResourceDecoder>(TextResourceDecoderOptions(
261 TextResourceDecoderOptions::kPlainTextContent,
262 response_encoding_.IsEmpty() ? UTF8Encoding()
263 : WTF::TextEncoding(response_encoding_)));
264 }
265
266 if (!len)
267 return;
268
269 source_text_.Append(decoder_->Decode(data, len));
270 }
271
DidReceiveCachedMetadata(const char * data,int size)272 void WorkerClassicScriptLoader::DidReceiveCachedMetadata(const char* data,
273 int size) {
274 cached_metadata_ = std::make_unique<Vector<uint8_t>>(size);
275 memcpy(cached_metadata_->data(), data, size);
276 }
277
DidFinishLoading(uint64_t identifier)278 void WorkerClassicScriptLoader::DidFinishLoading(uint64_t identifier) {
279 need_to_cancel_ = false;
280 if (!failed_ && decoder_)
281 source_text_.Append(decoder_->Flush());
282
283 NotifyFinished();
284 }
285
DidFail(const ResourceError & error)286 void WorkerClassicScriptLoader::DidFail(const ResourceError& error) {
287 need_to_cancel_ = false;
288 canceled_ = error.IsCancellation();
289 NotifyError();
290 }
291
DidFailRedirectCheck()292 void WorkerClassicScriptLoader::DidFailRedirectCheck() {
293 // When didFailRedirectCheck() is called, the ResourceLoader for the script
294 // is not canceled yet. So we don't reset |m_needToCancel| here.
295 NotifyError();
296 }
297
DidReceiveData(base::span<const char> span)298 void WorkerClassicScriptLoader::DidReceiveData(base::span<const char> span) {
299 if (!decoder_) {
300 decoder_ = std::make_unique<TextResourceDecoder>(TextResourceDecoderOptions(
301 TextResourceDecoderOptions::kPlainTextContent,
302 worker_main_script_loader_->GetScriptEncoding()));
303 }
304 if (!span.size())
305 return;
306 source_text_.Append(decoder_->Decode(span.data(), span.size()));
307 }
308
OnFinishedLoadingWorkerMainScript()309 void WorkerClassicScriptLoader::OnFinishedLoadingWorkerMainScript() {
310 DidReceiveResponse(0 /*identifier*/,
311 worker_main_script_loader_->GetResponse());
312 if (decoder_)
313 source_text_.Append(decoder_->Flush());
314 NotifyFinished();
315 }
316
OnFailedLoadingWorkerMainScript()317 void WorkerClassicScriptLoader::OnFailedLoadingWorkerMainScript() {
318 failed_ = true;
319 NotifyFinished();
320 }
321
Trace(Visitor * visitor) const322 void WorkerClassicScriptLoader::Trace(Visitor* visitor) const {
323 visitor->Trace(threadable_loader_);
324 visitor->Trace(worker_main_script_loader_);
325 visitor->Trace(content_security_policy_);
326 visitor->Trace(fetch_client_settings_object_fetcher_);
327 ThreadableLoaderClient::Trace(visitor);
328 }
329
Cancel()330 void WorkerClassicScriptLoader::Cancel() {
331 if (!need_to_cancel_)
332 return;
333 need_to_cancel_ = false;
334 if (threadable_loader_)
335 threadable_loader_->Cancel();
336 }
337
SourceText()338 String WorkerClassicScriptLoader::SourceText() {
339 return source_text_.ToString();
340 }
341
NotifyError()342 void WorkerClassicScriptLoader::NotifyError() {
343 failed_ = true;
344 // NotifyError() could be called before ThreadableLoader::Create() returns
345 // e.g. from DidFail(), and in that case threadable_loader_ is not yet set
346 // (i.e. still null).
347 // Since the callback invocation in NotifyFinished() potentially delete
348 // |this| object, the callback invocation should be postponed until the
349 // create() call returns. See LoadAsynchronously() for the postponed call.
350 if (threadable_loader_)
351 NotifyFinished();
352 }
353
NotifyFinished()354 void WorkerClassicScriptLoader::NotifyFinished() {
355 if (!finished_callback_)
356 return;
357
358 std::move(finished_callback_).Run();
359 }
360
ProcessContentSecurityPolicy(const ResourceResponse & response)361 void WorkerClassicScriptLoader::ProcessContentSecurityPolicy(
362 const ResourceResponse& response) {
363 // Per http://www.w3.org/TR/CSP2/#processing-model-workers, if the Worker's
364 // URL is not a GUID, then it grabs its CSP from the response headers
365 // directly. Otherwise, the Worker inherits the policy from the parent
366 // document (which is implemented in WorkerMessagingProxy, and
367 // m_contentSecurityPolicy should be left as nullptr to inherit the policy).
368 if (!response.CurrentRequestUrl().ProtocolIs("blob") &&
369 !response.CurrentRequestUrl().ProtocolIs("file") &&
370 !response.CurrentRequestUrl().ProtocolIs("filesystem")) {
371 content_security_policy_ = MakeGarbageCollected<ContentSecurityPolicy>();
372 content_security_policy_->SetOverrideURLForSelf(
373 response.CurrentRequestUrl());
374 content_security_policy_->DidReceiveHeaders(
375 ContentSecurityPolicyResponseHeaders(response));
376 }
377 }
378
379 } // namespace blink
380