1/*
2 * Copyright (C) 2017 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "ServiceWorkerJob.h"
28
29#if ENABLE(SERVICE_WORKER)
30
31#include "HTTPHeaderNames.h"
32#include "JSDOMPromiseDeferred.h"
33#include "MIMETypeRegistry.h"
34#include "ResourceError.h"
35#include "ResourceResponse.h"
36#include "ScriptExecutionContext.h"
37#include "ServiceWorkerJobData.h"
38#include "ServiceWorkerRegistration.h"
39
40namespace WebCore {
41
42ServiceWorkerJob::ServiceWorkerJob(ServiceWorkerJobClient& client, RefPtr<DeferredPromise>&& promise, ServiceWorkerJobData&& jobData)
43 : m_client(client)
44 , m_jobData(WTFMove(jobData))
45 , m_promise(WTFMove(promise))
46 , m_contextIdentifier(client.contextIdentifier())
47{
48}
49
50ServiceWorkerJob::~ServiceWorkerJob()
51{
52 ASSERT(m_creationThread.ptr() == &Thread::current());
53}
54
55void ServiceWorkerJob::failedWithException(const Exception& exception)
56{
57 ASSERT(m_creationThread.ptr() == &Thread::current());
58 ASSERT(!m_completed);
59
60 m_completed = true;
61 m_client.jobFailedWithException(*this, exception);
62}
63
64void ServiceWorkerJob::resolvedWithRegistration(ServiceWorkerRegistrationData&& data, ShouldNotifyWhenResolved shouldNotifyWhenResolved)
65{
66 ASSERT(m_creationThread.ptr() == &Thread::current());
67 ASSERT(!m_completed);
68
69 m_completed = true;
70 m_client.jobResolvedWithRegistration(*this, WTFMove(data), shouldNotifyWhenResolved);
71}
72
73void ServiceWorkerJob::resolvedWithUnregistrationResult(bool unregistrationResult)
74{
75 ASSERT(m_creationThread.ptr() == &Thread::current());
76 ASSERT(!m_completed);
77
78 m_completed = true;
79 m_client.jobResolvedWithUnregistrationResult(*this, unregistrationResult);
80}
81
82void ServiceWorkerJob::startScriptFetch(FetchOptions::Cache cachePolicy)
83{
84 ASSERT(m_creationThread.ptr() == &Thread::current());
85 ASSERT(!m_completed);
86
87 m_client.startScriptFetchForJob(*this, cachePolicy);
88}
89
90void ServiceWorkerJob::fetchScriptWithContext(ScriptExecutionContext& context, FetchOptions::Cache cachePolicy)
91{
92 ASSERT(m_creationThread.ptr() == &Thread::current());
93 ASSERT(!m_completed);
94
95 // FIXME: WorkerScriptLoader is the wrong loader class to use here, but there's nothing else better right now.
96 m_scriptLoader = WorkerScriptLoader::create();
97
98 ResourceRequest request { m_jobData.scriptURL };
99 request.setInitiatorIdentifier(context.resourceRequestIdentifier());
100 request.addHTTPHeaderField("Service-Worker"_s, "script"_s);
101
102 FetchOptions options;
103 options.mode = FetchOptions::Mode::SameOrigin;
104 options.cache = cachePolicy;
105 options.redirect = FetchOptions::Redirect::Error;
106 options.destination = FetchOptions::Destination::Serviceworker;
107 m_scriptLoader->loadAsynchronously(context, WTFMove(request), WTFMove(options), ContentSecurityPolicyEnforcement::DoNotEnforce, ServiceWorkersMode::None, *this);
108}
109
110void ServiceWorkerJob::didReceiveResponse(unsigned long, const ResourceResponse& response)
111{
112 ASSERT(m_creationThread.ptr() == &Thread::current());
113 ASSERT(!m_completed);
114 ASSERT(m_scriptLoader);
115
116 // Extract a MIME type from the response's header list. If this MIME type (ignoring parameters) is not a JavaScript MIME type, then:
117 if (!MIMETypeRegistry::isSupportedJavaScriptMIMEType(response.mimeType())) {
118 m_scriptLoader->cancel();
119 m_scriptLoader = nullptr;
120
121 // Invoke Reject Job Promise with job and "SecurityError" DOMException.
122 Exception exception { SecurityError, "MIME Type is not a JavaScript MIME type"_s };
123 // Asynchronously complete these steps with a network error.
124 ResourceError error { errorDomainWebKitInternal, 0, response.url(), "Unexpected MIME type"_s };
125 m_client.jobFailedLoadingScript(*this, WTFMove(error), WTFMove(exception));
126 return;
127 }
128
129 String serviceWorkerAllowed = response.httpHeaderField(HTTPHeaderName::ServiceWorkerAllowed);
130 String maxScopeString;
131 if (serviceWorkerAllowed.isNull()) {
132 String path = m_jobData.scriptURL.path();
133 // Last part of the path is the script's filename.
134 maxScopeString = path.substring(0, path.reverseFind('/') + 1);
135 } else {
136 auto maxScope = URL(m_jobData.scriptURL, serviceWorkerAllowed);
137 maxScopeString = maxScope.path();
138 }
139
140 String scopeString = m_jobData.scopeURL.path();
141 if (scopeString.startsWith(maxScopeString))
142 return;
143
144 m_scriptLoader->cancel();
145 m_scriptLoader = nullptr;
146
147 Exception exception { SecurityError, "Scope URL should start with the given script URL"_s };
148 ResourceError error { errorDomainWebKitInternal, 0, response.url(), "Scope URL should start with the given script URL"_s };
149 m_client.jobFailedLoadingScript(*this, WTFMove(error), WTFMove(exception));
150}
151
152void ServiceWorkerJob::notifyFinished()
153{
154 ASSERT(m_creationThread.ptr() == &Thread::current());
155 ASSERT(m_scriptLoader);
156
157 auto scriptLoader = WTFMove(m_scriptLoader);
158
159 if (!scriptLoader->failed()) {
160 m_client.jobFinishedLoadingScript(*this, scriptLoader->script(), scriptLoader->contentSecurityPolicy(), scriptLoader->referrerPolicy());
161 return;
162 }
163
164 auto& error = scriptLoader->error();
165 ASSERT(!error.isNull());
166
167 m_client.jobFailedLoadingScript(*this, error, Exception { error.isAccessControl() ? SecurityError : TypeError, makeString("Script ", scriptLoader->url().string(), " load failed") });
168}
169
170bool ServiceWorkerJob::cancelPendingLoad()
171{
172 if (!m_scriptLoader)
173 return false;
174
175 m_scriptLoader->cancel();
176 m_scriptLoader = nullptr;
177 return true;
178}
179
180} // namespace WebCore
181
182#endif // ENABLE(SERVICE_WORKER)
183