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 | |
44 | namespace WebCore { |
45 | |
46 | namespace ServiceWorkerFetch { |
47 | |
48 | // https://fetch.spec.whatwg.org/#http-fetch step 3.3 |
49 | static 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 | |
67 | static 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 | |
135 | void dispatchFetchEvent(Ref<Client>&& client, ServiceWorkerGlobalScope& globalScope, Optional<ServiceWorkerClientIdentifier> clientId, ResourceRequest&& request, String&& referrer, FetchOptions&& options) |
136 | { |
137 | auto = 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 | |