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/renderer/core/execution_context/execution_context.h"
34 #include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
35 #include "third_party/blink/renderer/core/html/parser/text_resource_decoder.h"
36 #include "third_party/blink/renderer/core/inspector/console_message.h"
37 #include "third_party/blink/renderer/core/loader/resource/script_resource.h"
38 #include "third_party/blink/renderer/core/origin_trials/origin_trial_context.h"
39 #include "third_party/blink/renderer/core/workers/worker_global_scope.h"
40 #include "third_party/blink/renderer/platform/loader/fetch/detachable_use_counter.h"
41 #include "third_party/blink/renderer/platform/loader/fetch/fetch_client_settings_object.h"
42 #include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
43 #include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher_properties.h"
44 #include "third_party/blink/renderer/platform/loader/fetch/resource_loader_options.h"
45 #include "third_party/blink/renderer/platform/loader/fetch/resource_response.h"
46 #include "third_party/blink/renderer/platform/loader/fetch/text_resource_decoder_options.h"
47 #include "third_party/blink/renderer/platform/network/content_security_policy_response_headers.h"
48 #include "third_party/blink/renderer/platform/network/http_names.h"
49 #include "third_party/blink/renderer/platform/network/network_utils.h"
50 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
51 #include "third_party/blink/renderer/platform/weborigin/security_origin.h"
52 
53 namespace blink {
54 
55 namespace {
56 
57 // CheckSameOriginEnforcement() functions return non-null String on error.
58 //
59 // WorkerGlobalScope's SecurityOrigin is initialized to request URL's
60 // origin at the construction of WorkerGlobalScope, while
61 // WorkerGlobalScope's URL is set to response URL
62 // (ResourceResponse::ResponseURL()).
63 // These functions are used to ensure the SecurityOrigin and the URL to be
64 // consistent. https://crbug.com/861564
65 //
66 // TODO(hiroshige): Merge with similar code in other places.
CheckSameOriginEnforcement(const KURL & request_url,const KURL & response_url)67 String CheckSameOriginEnforcement(const KURL& request_url,
68                                   const KURL& response_url) {
69   if (request_url != response_url &&
70       !SecurityOrigin::AreSameOrigin(request_url, response_url)) {
71     return "Refused to load the top-level worker script from '" +
72            response_url.ElidedString() +
73            "' because it doesn't match the origin of the request URL '" +
74            request_url.ElidedString() + "'";
75   }
76   return String();
77 }
78 
CheckSameOriginEnforcement(const KURL & request_url,const ResourceResponse & response)79 String CheckSameOriginEnforcement(const KURL& request_url,
80                                   const ResourceResponse& response) {
81   // While this check is not strictly necessary as CurrentRequestUrl() is not
82   // used as WorkerGlobalScope's URL, it is probably safer to reject cases like
83   // Origin A(request_url)
84   //   =(cross-origin redirects)=> Origin B(CurrentRequestUrl())
85   //   =(ServiceWorker interception)=> Origin A(ResponseUrl())
86   // which doesn't seem to have valid use cases.
87   String error =
88       CheckSameOriginEnforcement(request_url, response.CurrentRequestUrl());
89   if (!error.IsNull())
90     return error;
91 
92   // This check is directly required to ensure the consistency between
93   // WorkerGlobalScope's SecurityOrigin and URL.
94   return CheckSameOriginEnforcement(request_url, response.ResponseUrl());
95 }
96 
97 }  // namespace
98 
WorkerClassicScriptLoader()99 WorkerClassicScriptLoader::WorkerClassicScriptLoader()
100     : response_address_space_(network::mojom::IPAddressSpace::kPublic) {}
101 
LoadSynchronously(ExecutionContext & execution_context,ResourceFetcher * fetch_client_settings_object_fetcher,const KURL & url,mojom::RequestContextType request_context,network::mojom::RequestDestination destination)102 void WorkerClassicScriptLoader::LoadSynchronously(
103     ExecutionContext& execution_context,
104     ResourceFetcher* fetch_client_settings_object_fetcher,
105     const KURL& url,
106     mojom::RequestContextType request_context,
107     network::mojom::RequestDestination destination) {
108   DCHECK(fetch_client_settings_object_fetcher);
109   url_ = url;
110   fetch_client_settings_object_fetcher_ = fetch_client_settings_object_fetcher;
111 
112   ResourceRequest request(url);
113   request.SetHttpMethod(http_names::kGET);
114   request.SetExternalRequestStateFromRequestorAddressSpace(
115       fetch_client_settings_object_fetcher_->GetProperties()
116           .GetFetchClientSettingsObject()
117           .GetAddressSpace());
118   request.SetRequestContext(request_context);
119   request.SetRequestDestination(destination);
120 
121   SECURITY_DCHECK(execution_context.IsWorkerGlobalScope());
122 
123   ResourceLoaderOptions resource_loader_options;
124   resource_loader_options.parser_disposition =
125       ParserDisposition::kNotParserInserted;
126   resource_loader_options.synchronous_policy = kRequestSynchronously;
127 
128   threadable_loader_ = MakeGarbageCollected<ThreadableLoader>(
129       execution_context, this, resource_loader_options,
130       fetch_client_settings_object_fetcher);
131   threadable_loader_->Start(request);
132 }
133 
LoadTopLevelScriptAsynchronously(ExecutionContext & execution_context,ResourceFetcher * fetch_client_settings_object_fetcher,const KURL & url,mojom::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)134 void WorkerClassicScriptLoader::LoadTopLevelScriptAsynchronously(
135     ExecutionContext& execution_context,
136     ResourceFetcher* fetch_client_settings_object_fetcher,
137     const KURL& url,
138     mojom::RequestContextType request_context,
139     network::mojom::RequestDestination destination,
140     network::mojom::RequestMode request_mode,
141     network::mojom::CredentialsMode credentials_mode,
142     base::OnceClosure response_callback,
143     base::OnceClosure finished_callback,
144     RejectCoepUnsafeNone reject_coep_unsafe_none,
145     mojo::PendingRemote<network::mojom::blink::URLLoaderFactory>
146         blob_url_loader_factory) {
147   DCHECK(fetch_client_settings_object_fetcher);
148   DCHECK(response_callback || finished_callback);
149   response_callback_ = std::move(response_callback);
150   finished_callback_ = std::move(finished_callback);
151   url_ = url;
152   fetch_client_settings_object_fetcher_ = fetch_client_settings_object_fetcher;
153   is_top_level_script_ = true;
154 
155   ResourceRequest request(url);
156   request.SetHttpMethod(http_names::kGET);
157   request.SetExternalRequestStateFromRequestorAddressSpace(
158       fetch_client_settings_object_fetcher_->GetProperties()
159           .GetFetchClientSettingsObject()
160           .GetAddressSpace());
161   request.SetRequestContext(request_context);
162   request.SetRequestDestination(destination);
163   request.SetMode(request_mode);
164   request.SetCredentialsMode(credentials_mode);
165 
166   need_to_cancel_ = true;
167   ResourceLoaderOptions resource_loader_options;
168   resource_loader_options.reject_coep_unsafe_none = reject_coep_unsafe_none;
169   if (blob_url_loader_factory) {
170     resource_loader_options.url_loader_factory =
171         base::MakeRefCounted<base::RefCountedData<
172             mojo::PendingRemote<network::mojom::blink::URLLoaderFactory>>>(
173             std::move(blob_url_loader_factory));
174   }
175   threadable_loader_ = MakeGarbageCollected<ThreadableLoader>(
176       execution_context, this, resource_loader_options,
177       fetch_client_settings_object_fetcher);
178   threadable_loader_->Start(request);
179   if (failed_)
180     NotifyFinished();
181 }
182 
ResponseURL() const183 const KURL& WorkerClassicScriptLoader::ResponseURL() const {
184   DCHECK(!Failed());
185   return response_url_;
186 }
187 
DidReceiveResponse(uint64_t identifier,const ResourceResponse & response)188 void WorkerClassicScriptLoader::DidReceiveResponse(
189     uint64_t identifier,
190     const ResourceResponse& response) {
191   if (response.HttpStatusCode() / 100 != 2 && response.HttpStatusCode()) {
192     NotifyError();
193     return;
194   }
195   if (!AllowedByNosniff::MimeTypeAsScript(
196           fetch_client_settings_object_fetcher_->GetUseCounter(),
197           &fetch_client_settings_object_fetcher_->GetConsoleLogger(), response,
198           fetch_client_settings_object_fetcher_->GetProperties()
199               .GetFetchClientSettingsObject()
200               .MimeTypeCheckForClassicWorkerScript())) {
201     NotifyError();
202     return;
203   }
204 
205   if (is_top_level_script_) {
206     String error = CheckSameOriginEnforcement(url_, response);
207     if (!error.IsNull()) {
208       fetch_client_settings_object_fetcher_->GetConsoleLogger()
209           .AddConsoleMessage(mojom::ConsoleMessageSource::kSecurity,
210                              mojom::ConsoleMessageLevel::kError, error);
211       NotifyError();
212       return;
213     }
214   }
215 
216   identifier_ = identifier;
217   response_url_ = response.ResponseUrl();
218   response_encoding_ = response.TextEncodingName();
219   app_cache_id_ = response.AppCacheID();
220 
221   referrer_policy_ = response.HttpHeaderField(http_names::kReferrerPolicy);
222   ProcessContentSecurityPolicy(response);
223   origin_trial_tokens_ = OriginTrialContext::ParseHeaderValue(
224       response.HttpHeaderField(http_names::kOriginTrial));
225 
226   if (network_utils::IsReservedIPAddress(response.RemoteIPAddress())) {
227     response_address_space_ =
228         SecurityOrigin::Create(response_url_)->IsLocalhost()
229             ? network::mojom::IPAddressSpace::kLocal
230             : network::mojom::IPAddressSpace::kPrivate;
231   }
232 
233   if (response_callback_)
234     std::move(response_callback_).Run();
235 }
236 
DidReceiveData(const char * data,unsigned len)237 void WorkerClassicScriptLoader::DidReceiveData(const char* data, unsigned len) {
238   if (failed_)
239     return;
240 
241   if (!decoder_) {
242     decoder_ = std::make_unique<TextResourceDecoder>(TextResourceDecoderOptions(
243         TextResourceDecoderOptions::kPlainTextContent,
244         response_encoding_.IsEmpty() ? UTF8Encoding()
245                                      : WTF::TextEncoding(response_encoding_)));
246   }
247 
248   if (!len)
249     return;
250 
251   source_text_.Append(decoder_->Decode(data, len));
252 }
253 
DidReceiveCachedMetadata(const char * data,int size)254 void WorkerClassicScriptLoader::DidReceiveCachedMetadata(const char* data,
255                                                          int size) {
256   cached_metadata_ = std::make_unique<Vector<uint8_t>>(size);
257   memcpy(cached_metadata_->data(), data, size);
258 }
259 
DidFinishLoading(uint64_t identifier)260 void WorkerClassicScriptLoader::DidFinishLoading(uint64_t identifier) {
261   need_to_cancel_ = false;
262   if (!failed_ && decoder_)
263     source_text_.Append(decoder_->Flush());
264 
265   NotifyFinished();
266 }
267 
DidFail(const ResourceError & error)268 void WorkerClassicScriptLoader::DidFail(const ResourceError& error) {
269   need_to_cancel_ = false;
270   canceled_ = error.IsCancellation();
271   NotifyError();
272 }
273 
DidFailRedirectCheck()274 void WorkerClassicScriptLoader::DidFailRedirectCheck() {
275   // When didFailRedirectCheck() is called, the ResourceLoader for the script
276   // is not canceled yet. So we don't reset |m_needToCancel| here.
277   NotifyError();
278 }
279 
Trace(Visitor * visitor)280 void WorkerClassicScriptLoader::Trace(Visitor* visitor) {
281   visitor->Trace(threadable_loader_);
282   visitor->Trace(content_security_policy_);
283   visitor->Trace(fetch_client_settings_object_fetcher_);
284   ThreadableLoaderClient::Trace(visitor);
285 }
286 
Cancel()287 void WorkerClassicScriptLoader::Cancel() {
288   if (!need_to_cancel_)
289     return;
290   need_to_cancel_ = false;
291   if (threadable_loader_)
292     threadable_loader_->Cancel();
293 }
294 
SourceText()295 String WorkerClassicScriptLoader::SourceText() {
296   return source_text_.ToString();
297 }
298 
NotifyError()299 void WorkerClassicScriptLoader::NotifyError() {
300   failed_ = true;
301   // NotifyError() could be called before ThreadableLoader::Create() returns
302   // e.g. from DidFail(), and in that case threadable_loader_ is not yet set
303   // (i.e. still null).
304   // Since the callback invocation in NotifyFinished() potentially delete
305   // |this| object, the callback invocation should be postponed until the
306   // create() call returns. See LoadAsynchronously() for the postponed call.
307   if (threadable_loader_)
308     NotifyFinished();
309 }
310 
NotifyFinished()311 void WorkerClassicScriptLoader::NotifyFinished() {
312   if (!finished_callback_)
313     return;
314 
315   std::move(finished_callback_).Run();
316 }
317 
ProcessContentSecurityPolicy(const ResourceResponse & response)318 void WorkerClassicScriptLoader::ProcessContentSecurityPolicy(
319     const ResourceResponse& response) {
320   // Per http://www.w3.org/TR/CSP2/#processing-model-workers, if the Worker's
321   // URL is not a GUID, then it grabs its CSP from the response headers
322   // directly.  Otherwise, the Worker inherits the policy from the parent
323   // document (which is implemented in WorkerMessagingProxy, and
324   // m_contentSecurityPolicy should be left as nullptr to inherit the policy).
325   if (!response.CurrentRequestUrl().ProtocolIs("blob") &&
326       !response.CurrentRequestUrl().ProtocolIs("file") &&
327       !response.CurrentRequestUrl().ProtocolIs("filesystem")) {
328     content_security_policy_ = MakeGarbageCollected<ContentSecurityPolicy>();
329     content_security_policy_->SetOverrideURLForSelf(
330         response.CurrentRequestUrl());
331     content_security_policy_->DidReceiveHeaders(
332         ContentSecurityPolicyResponseHeaders(response));
333   }
334 }
335 
336 }  // namespace blink
337