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 "ServiceWorkerFetch.h"
28
29#if ENABLE(SERVICE_WORKER)
30
31#include "CrossOriginAccessControl.h"
32#include "EventNames.h"
33#include "FetchEvent.h"
34#include "FetchRequest.h"
35#include "FetchResponse.h"
36#include "MIMETypeRegistry.h"
37#include "ReadableStreamChunk.h"
38#include "ResourceRequest.h"
39#include "ServiceWorker.h"
40#include "ServiceWorkerClientIdentifier.h"
41#include "ServiceWorkerGlobalScope.h"
42#include "WorkerGlobalScope.h"
43
44namespace WebCore {
45
46namespace ServiceWorkerFetch {
47
48// https://fetch.spec.whatwg.org/#http-fetch step 3.3
49static inline Optional<ResourceError> validateResponse(const ResourceResponse& response, FetchOptions::Mode mode, FetchOptions::Redirect redirect)
50{
51 if (response.type() == ResourceResponse::Type::Error)
52 return ResourceError { errorDomainWebKitInternal, 0, response.url(), "Response served by service worker is an error"_s, ResourceError::Type::General };
53
54 if (mode != FetchOptions::Mode::NoCors && response.tainting() == ResourceResponse::Tainting::Opaque)
55 return ResourceError { errorDomainWebKitInternal, 0, response.url(), "Response served by service worker is opaque"_s, ResourceError::Type::AccessControl };
56
57 // Navigate mode induces manual redirect.
58 if (redirect != FetchOptions::Redirect::Manual && mode != FetchOptions::Mode::Navigate && response.tainting() == ResourceResponse::Tainting::Opaqueredirect)
59 return ResourceError { errorDomainWebKitInternal, 0, response.url(), "Response served by service worker is opaque redirect"_s, ResourceError::Type::AccessControl };
60
61 if ((redirect != FetchOptions::Redirect::Follow || mode == FetchOptions::Mode::Navigate) && response.isRedirected())
62 return ResourceError { errorDomainWebKitInternal, 0, response.url(), "Response served by service worker has redirections"_s, ResourceError::Type::AccessControl };
63
64 return { };
65}
66
67static void processResponse(Ref<Client>&& client, Expected<Ref<FetchResponse>, ResourceError>&& result, FetchOptions::Mode mode, FetchOptions::Redirect redirect, const URL& requestURL)
68{
69 if (!result.has_value()) {
70 client->didFail(result.error());
71 return;
72 }
73 auto response = WTFMove(result.value());
74
75 auto loadingError = response->loadingError();
76 if (!loadingError.isNull()) {
77 client->didFail(loadingError);
78 return;
79 }
80
81 auto resourceResponse = response->resourceResponse();
82 if (auto error = validateResponse(resourceResponse, mode, redirect)) {
83 client->didFail(error.value());
84 return;
85 }
86
87 if (resourceResponse.isRedirection() && resourceResponse.httpHeaderFields().contains(HTTPHeaderName::Location)) {
88 client->didReceiveRedirection(resourceResponse);
89 return;
90 }
91
92 resourceResponse.setSource(ResourceResponse::Source::ServiceWorker);
93
94 // In case of main resource and mime type is the default one, we set it to text/html to pass more service worker WPT tests.
95 // FIXME: We should refine our MIME type sniffing strategy for synthetic responses.
96 if (mode == FetchOptions::Mode::Navigate) {
97 if (resourceResponse.mimeType() == defaultMIMEType()) {
98 resourceResponse.setMimeType("text/html"_s);
99 resourceResponse.setTextEncodingName("UTF-8"_s);
100 }
101 }
102
103 // As per https://fetch.spec.whatwg.org/#main-fetch step 9, copy request's url list in response's url list if empty.
104 if (resourceResponse.url().isNull())
105 resourceResponse.setURL(requestURL);
106
107 client->didReceiveResponse(resourceResponse);
108
109 if (response->isBodyReceivedByChunk()) {
110 response->consumeBodyReceivedByChunk([client = WTFMove(client)] (auto&& result) mutable {
111 if (result.hasException()) {
112 client->didFail(FetchEvent::createResponseError(URL { }, result.exception().message()));
113 return;
114 }
115
116 if (auto chunk = result.returnValue())
117 client->didReceiveData(SharedBuffer::create(reinterpret_cast<const char*>(chunk->data), chunk->size));
118 else
119 client->didFinish();
120 });
121 return;
122 }
123
124 auto body = response->consumeBody();
125 WTF::switchOn(body, [&] (Ref<FormData>& formData) {
126 client->didReceiveFormDataAndFinish(WTFMove(formData));
127 }, [&] (Ref<SharedBuffer>& buffer) {
128 client->didReceiveData(WTFMove(buffer));
129 client->didFinish();
130 }, [&] (std::nullptr_t&) {
131 client->didFinish();
132 });
133}
134
135void dispatchFetchEvent(Ref<Client>&& client, ServiceWorkerGlobalScope& globalScope, Optional<ServiceWorkerClientIdentifier> clientId, ResourceRequest&& request, String&& referrer, FetchOptions&& options)
136{
137 auto requestHeaders = FetchHeaders::create(FetchHeaders::Guard::Immutable, HTTPHeaderMap { request.httpHeaderFields() });
138
139 FetchOptions::Mode mode = options.mode;
140 FetchOptions::Redirect redirect = options.redirect;
141
142 bool isNavigation = options.mode == FetchOptions::Mode::Navigate;
143 bool isNonSubresourceRequest = WebCore::isNonSubresourceRequest(options.destination);
144
145 ASSERT(globalScope.registration().active());
146 ASSERT(globalScope.registration().active()->identifier() == globalScope.thread().identifier());
147 ASSERT(globalScope.registration().active()->state() == ServiceWorkerState::Activated);
148
149 auto* formData = request.httpBody();
150 Optional<FetchBody> body;
151 if (formData && !formData->isEmpty()) {
152 body = FetchBody::fromFormData(*formData);
153 if (!body) {
154 client->didNotHandle();
155 return;
156 }
157 }
158 // FIXME: loading code should set redirect mode to manual.
159 if (isNavigation)
160 options.redirect = FetchOptions::Redirect::Manual;
161
162 URL requestURL = request.url();
163 auto fetchRequest = FetchRequest::create(globalScope, WTFMove(body), WTFMove(requestHeaders), WTFMove(request), WTFMove(options), WTFMove(referrer));
164
165 FetchEvent::Init init;
166 init.request = WTFMove(fetchRequest);
167 if (isNavigation) {
168 // FIXME: Set reservedClientId.
169 if (clientId)
170 init.targetClientId = clientId->toString();
171 } else if (clientId)
172 init.clientId = clientId->toString();
173 init.cancelable = true;
174 auto event = FetchEvent::create(eventNames().fetchEvent, WTFMove(init), Event::IsTrusted::Yes);
175
176 event->onResponse([client = client.copyRef(), mode, redirect, requestURL] (auto&& result) mutable {
177 processResponse(WTFMove(client), WTFMove(result), mode, redirect, requestURL);
178 });
179
180 globalScope.dispatchEvent(event);
181
182 if (!event->respondWithEntered()) {
183 if (event->defaultPrevented()) {
184 client->didFail(ResourceError { errorDomainWebKitInternal, 0, requestURL, "Fetch event was canceled"_s });
185 return;
186 }
187 client->didNotHandle();
188 }
189
190 globalScope.updateExtendedEventsSet(event.ptr());
191
192 auto& registration = globalScope.registration();
193 if (isNonSubresourceRequest || registration.needsUpdate())
194 registration.scheduleSoftUpdate();
195}
196
197} // namespace ServiceWorkerFetch
198
199} // namespace WebCore
200
201#endif // ENABLE(SERVICE_WORKER)
202