1/*
2 * Copyright (C) 2009-2017 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 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 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#include "config.h"
28#include "WorkerScriptLoader.h"
29
30#include "ContentSecurityPolicy.h"
31#include "Exception.h"
32#include "FetchIdioms.h"
33#include "MIMETypeRegistry.h"
34#include "ResourceResponse.h"
35#include "ScriptExecutionContext.h"
36#include "ServiceWorker.h"
37#include "ServiceWorkerGlobalScope.h"
38#include "TextResourceDecoder.h"
39#include "WorkerGlobalScope.h"
40#include "WorkerScriptLoaderClient.h"
41#include "WorkerThreadableLoader.h"
42#include <wtf/Ref.h>
43
44namespace WebCore {
45
46WorkerScriptLoader::WorkerScriptLoader() = default;
47
48WorkerScriptLoader::~WorkerScriptLoader() = default;
49
50Optional<Exception> WorkerScriptLoader::loadSynchronously(ScriptExecutionContext* scriptExecutionContext, const URL& url, FetchOptions::Mode mode, FetchOptions::Cache cachePolicy, ContentSecurityPolicyEnforcement contentSecurityPolicyEnforcement, const String& initiatorIdentifier)
51{
52 ASSERT(scriptExecutionContext);
53 auto& workerGlobalScope = downcast<WorkerGlobalScope>(*scriptExecutionContext);
54
55 m_url = url;
56 m_destination = FetchOptions::Destination::Script;
57
58#if ENABLE(SERVICE_WORKER)
59 bool isServiceWorkerGlobalScope = is<ServiceWorkerGlobalScope>(workerGlobalScope);
60
61 if (isServiceWorkerGlobalScope) {
62 if (auto* scriptResource = downcast<ServiceWorkerGlobalScope>(workerGlobalScope).scriptResource(url)) {
63 m_script.append(scriptResource->script);
64 m_responseURL = URL { URL { }, scriptResource->responseURL };
65 m_responseMIMEType = scriptResource->mimeType;
66 return WTF::nullopt;
67 }
68 }
69#endif
70
71 std::unique_ptr<ResourceRequest> request(createResourceRequest(initiatorIdentifier));
72 if (!request)
73 return WTF::nullopt;
74
75 ASSERT_WITH_SECURITY_IMPLICATION(is<WorkerGlobalScope>(scriptExecutionContext));
76
77 // Only used for importScripts that prescribes NoCors mode.
78 ASSERT(mode == FetchOptions::Mode::NoCors);
79 request->setRequester(ResourceRequest::Requester::ImportScripts);
80
81 ThreadableLoaderOptions options;
82 options.credentials = FetchOptions::Credentials::Include;
83 options.mode = mode;
84 options.cache = cachePolicy;
85 options.sendLoadCallbacks = SendCallbackPolicy::SendCallbacks;
86 options.contentSecurityPolicyEnforcement = contentSecurityPolicyEnforcement;
87 options.destination = m_destination;
88#if ENABLE(SERVICE_WORKER)
89 options.serviceWorkersMode = isServiceWorkerGlobalScope ? ServiceWorkersMode::None : ServiceWorkersMode::All;
90 if (auto* activeServiceWorker = workerGlobalScope.activeServiceWorker())
91 options.serviceWorkerRegistrationIdentifier = activeServiceWorker->registrationIdentifier();
92#endif
93 WorkerThreadableLoader::loadResourceSynchronously(workerGlobalScope, WTFMove(*request), *this, options);
94
95 // If the fetching attempt failed, throw a NetworkError exception and abort all these steps.
96 if (failed())
97 return Exception { NetworkError, error().localizedDescription() };
98
99#if ENABLE(SERVICE_WORKER)
100 if (isServiceWorkerGlobalScope) {
101 if (!MIMETypeRegistry::isSupportedJavaScriptMIMEType(responseMIMEType()))
102 return Exception { NetworkError, "mime type is not a supported JavaScript mime type"_s };
103
104 downcast<ServiceWorkerGlobalScope>(workerGlobalScope).setScriptResource(url, ServiceWorkerContextData::ImportedScript { script(), m_responseURL, m_responseMIMEType });
105 }
106#endif
107 return WTF::nullopt;
108}
109
110void WorkerScriptLoader::loadAsynchronously(ScriptExecutionContext& scriptExecutionContext, ResourceRequest&& scriptRequest, FetchOptions&& fetchOptions, ContentSecurityPolicyEnforcement contentSecurityPolicyEnforcement, ServiceWorkersMode serviceWorkerMode, WorkerScriptLoaderClient& client)
111{
112 m_client = &client;
113 m_url = scriptRequest.url();
114 m_destination = fetchOptions.destination;
115
116 ASSERT(scriptRequest.httpMethod() == "GET");
117
118 auto request = std::make_unique<ResourceRequest>(WTFMove(scriptRequest));
119 if (!request)
120 return;
121
122 // Only used for loading worker scripts in classic mode.
123 // FIXME: We should add an option to set credential mode.
124 ASSERT(fetchOptions.mode == FetchOptions::Mode::SameOrigin);
125
126 ThreadableLoaderOptions options { WTFMove(fetchOptions) };
127 options.credentials = FetchOptions::Credentials::SameOrigin;
128 options.sendLoadCallbacks = SendCallbackPolicy::SendCallbacks;
129 options.contentSecurityPolicyEnforcement = contentSecurityPolicyEnforcement;
130 // A service worker job can be executed from a worker context or a document context.
131 options.serviceWorkersMode = serviceWorkerMode;
132#if ENABLE(SERVICE_WORKER)
133 if (auto* activeServiceWorker = scriptExecutionContext.activeServiceWorker())
134 options.serviceWorkerRegistrationIdentifier = activeServiceWorker->registrationIdentifier();
135#endif
136
137 // During create, callbacks may happen which remove the last reference to this object.
138 Ref<WorkerScriptLoader> protectedThis(*this);
139 m_threadableLoader = ThreadableLoader::create(scriptExecutionContext, *this, WTFMove(*request), options);
140}
141
142const URL& WorkerScriptLoader::responseURL() const
143{
144 ASSERT(!failed());
145 return m_responseURL;
146}
147
148std::unique_ptr<ResourceRequest> WorkerScriptLoader::createResourceRequest(const String& initiatorIdentifier)
149{
150 auto request = std::make_unique<ResourceRequest>(m_url);
151 request->setHTTPMethod("GET"_s);
152 request->setInitiatorIdentifier(initiatorIdentifier);
153 return request;
154}
155
156void WorkerScriptLoader::didReceiveResponse(unsigned long identifier, const ResourceResponse& response)
157{
158 if (response.httpStatusCode() / 100 != 2 && response.httpStatusCode()) {
159 m_failed = true;
160 return;
161 }
162
163 if (!isScriptAllowedByNosniff(response)) {
164 String message = makeString("Refused to execute ", response.url().stringCenterEllipsizedToLength(), " as script because \"X-Content-Type: nosniff\" was given and its Content-Type is not a script MIME type.");
165 m_error = ResourceError { errorDomainWebKitInternal, 0, url(), message, ResourceError::Type::General };
166 m_failed = true;
167 return;
168 }
169
170 if (shouldBlockResponseDueToMIMEType(response, m_destination)) {
171 String message = makeString("Refused to execute ", response.url().stringCenterEllipsizedToLength(), " as script because ", response.mimeType(), " is not a script MIME type.");
172 m_error = ResourceError { errorDomainWebKitInternal, 0, response.url(), message, ResourceError::Type::General };
173 m_failed = true;
174 return;
175 }
176
177 m_responseURL = response.url();
178 m_responseMIMEType = response.mimeType();
179 m_responseEncoding = response.textEncodingName();
180 m_contentSecurityPolicy = ContentSecurityPolicyResponseHeaders { response };
181 m_referrerPolicy = response.httpHeaderField(HTTPHeaderName::ReferrerPolicy);
182 if (m_client)
183 m_client->didReceiveResponse(identifier, response);
184}
185
186void WorkerScriptLoader::didReceiveData(const char* data, int len)
187{
188 if (m_failed)
189 return;
190
191 if (!m_decoder) {
192 if (!m_responseEncoding.isEmpty())
193 m_decoder = TextResourceDecoder::create("text/javascript"_s, m_responseEncoding);
194 else
195 m_decoder = TextResourceDecoder::create("text/javascript"_s, "UTF-8");
196 }
197
198 if (!len)
199 return;
200
201 if (len == -1)
202 len = strlen(data);
203
204 m_script.append(m_decoder->decode(data, len));
205}
206
207void WorkerScriptLoader::didFinishLoading(unsigned long identifier)
208{
209 if (m_failed) {
210 notifyError();
211 return;
212 }
213
214 if (m_decoder)
215 m_script.append(m_decoder->flush());
216
217 m_identifier = identifier;
218 notifyFinished();
219}
220
221void WorkerScriptLoader::didFail(const ResourceError& error)
222{
223 m_error = error;
224 notifyError();
225}
226
227void WorkerScriptLoader::notifyError()
228{
229 m_failed = true;
230 if (m_error.isNull())
231 m_error = ResourceError { errorDomainWebKitInternal, 0, url(), "Failed to load script", ResourceError::Type::General };
232 notifyFinished();
233}
234
235String WorkerScriptLoader::script()
236{
237 return m_script.toString();
238}
239
240void WorkerScriptLoader::notifyFinished()
241{
242 m_threadableLoader = nullptr;
243 if (!m_client || m_finishing)
244 return;
245
246 m_finishing = true;
247 m_client->notifyFinished();
248}
249
250void WorkerScriptLoader::cancel()
251{
252 if (!m_threadableLoader)
253 return;
254
255 m_client = nullptr;
256 m_threadableLoader->cancel();
257 m_threadableLoader = nullptr;
258}
259
260} // namespace WebCore
261