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