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