1 | /* |
2 | * Copyright (C) 2011, 2012 Google 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 are |
6 | * met: |
7 | * |
8 | * * Redistributions of source code must retain the above copyright |
9 | * notice, this list of conditions and the following disclaimer. |
10 | * * Redistributions in binary form must reproduce the above |
11 | * copyright notice, this list of conditions and the following disclaimer |
12 | * in the documentation and/or other materials provided with the |
13 | * distribution. |
14 | * * Neither the name of Google Inc. nor the names of its |
15 | * contributors may be used to endorse or promote products derived from |
16 | * this software without specific prior written permission. |
17 | * |
18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
19 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
20 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
21 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
22 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
23 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
24 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
25 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
26 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
27 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
29 | */ |
30 | |
31 | #include "config.h" |
32 | #include "DocumentThreadableLoader.h" |
33 | |
34 | #include "CachedRawResource.h" |
35 | #include "CachedResourceLoader.h" |
36 | #include "CachedResourceRequest.h" |
37 | #include "CachedResourceRequestInitiators.h" |
38 | #include "CrossOriginAccessControl.h" |
39 | #include "CrossOriginPreflightChecker.h" |
40 | #include "CrossOriginPreflightResultCache.h" |
41 | #include "DOMWindow.h" |
42 | #include "Document.h" |
43 | #include "Frame.h" |
44 | #include "FrameLoader.h" |
45 | #include "InspectorInstrumentation.h" |
46 | #include "LoadTiming.h" |
47 | #include "LoaderStrategy.h" |
48 | #include "Performance.h" |
49 | #include "PlatformStrategies.h" |
50 | #include "ProgressTracker.h" |
51 | #include "ResourceError.h" |
52 | #include "ResourceRequest.h" |
53 | #include "ResourceTiming.h" |
54 | #include "RuntimeApplicationChecks.h" |
55 | #include "RuntimeEnabledFeatures.h" |
56 | #include "SchemeRegistry.h" |
57 | #include "SecurityOrigin.h" |
58 | #include "SharedBuffer.h" |
59 | #include "SubresourceIntegrity.h" |
60 | #include "SubresourceLoader.h" |
61 | #include "ThreadableLoaderClient.h" |
62 | #include <wtf/Assertions.h> |
63 | #include <wtf/Ref.h> |
64 | |
65 | #if PLATFORM(IOS_FAMILY) |
66 | #include <wtf/spi/darwin/dyldSPI.h> |
67 | #endif |
68 | |
69 | namespace WebCore { |
70 | |
71 | void DocumentThreadableLoader::loadResourceSynchronously(Document& document, ResourceRequest&& request, ThreadableLoaderClient& client, const ThreadableLoaderOptions& options, RefPtr<SecurityOrigin>&& origin, std::unique_ptr<ContentSecurityPolicy>&& contentSecurityPolicy) |
72 | { |
73 | // The loader will be deleted as soon as this function exits. |
74 | Ref<DocumentThreadableLoader> loader = adoptRef(*new DocumentThreadableLoader(document, client, LoadSynchronously, WTFMove(request), options, WTFMove(origin), WTFMove(contentSecurityPolicy), String(), ShouldLogError::Yes)); |
75 | ASSERT(loader->hasOneRef()); |
76 | } |
77 | |
78 | void DocumentThreadableLoader::loadResourceSynchronously(Document& document, ResourceRequest&& request, ThreadableLoaderClient& client, const ThreadableLoaderOptions& options) |
79 | { |
80 | loadResourceSynchronously(document, WTFMove(request), client, options, nullptr, nullptr); |
81 | } |
82 | |
83 | RefPtr<DocumentThreadableLoader> DocumentThreadableLoader::create(Document& document, ThreadableLoaderClient& client, |
84 | ResourceRequest&& request, const ThreadableLoaderOptions& options, RefPtr<SecurityOrigin>&& origin, |
85 | std::unique_ptr<ContentSecurityPolicy>&& contentSecurityPolicy, String&& referrer, ShouldLogError shouldLogError) |
86 | { |
87 | RefPtr<DocumentThreadableLoader> loader = adoptRef(new DocumentThreadableLoader(document, client, LoadAsynchronously, WTFMove(request), options, WTFMove(origin), WTFMove(contentSecurityPolicy), WTFMove(referrer), shouldLogError)); |
88 | if (!loader->isLoading()) |
89 | loader = nullptr; |
90 | return loader; |
91 | } |
92 | |
93 | RefPtr<DocumentThreadableLoader> DocumentThreadableLoader::create(Document& document, ThreadableLoaderClient& client, ResourceRequest&& request, const ThreadableLoaderOptions& options, String&& referrer) |
94 | { |
95 | return create(document, client, WTFMove(request), options, nullptr, nullptr, WTFMove(referrer), ShouldLogError::Yes); |
96 | } |
97 | |
98 | static inline bool shouldPerformSecurityChecks() |
99 | { |
100 | return platformStrategies()->loaderStrategy()->shouldPerformSecurityChecks(); |
101 | } |
102 | |
103 | bool DocumentThreadableLoader::() const |
104 | { |
105 | if (m_options.mode == FetchOptions::Mode::Cors && shouldPerformSecurityChecks()) |
106 | return true; |
107 | |
108 | #if ENABLE(SERVICE_WORKER) |
109 | if (m_options.serviceWorkersMode == ServiceWorkersMode::All && m_async) |
110 | return m_options.serviceWorkerRegistrationIdentifier || m_document.activeServiceWorker(); |
111 | #endif |
112 | |
113 | return false; |
114 | } |
115 | |
116 | DocumentThreadableLoader::DocumentThreadableLoader(Document& document, ThreadableLoaderClient& client, BlockingBehavior blockingBehavior, ResourceRequest&& request, const ThreadableLoaderOptions& options, RefPtr<SecurityOrigin>&& origin, std::unique_ptr<ContentSecurityPolicy>&& contentSecurityPolicy, String&& referrer, ShouldLogError shouldLogError) |
117 | : m_client(&client) |
118 | , m_document(document) |
119 | , m_options(options) |
120 | , m_origin(WTFMove(origin)) |
121 | , m_referrer(WTFMove(referrer)) |
122 | , m_sameOriginRequest(securityOrigin().canRequest(request.url())) |
123 | , m_simpleRequest(true) |
124 | , m_async(blockingBehavior == LoadAsynchronously) |
125 | , m_delayCallbacksForIntegrityCheck(!m_options.integrity.isEmpty()) |
126 | , m_contentSecurityPolicy(WTFMove(contentSecurityPolicy)) |
127 | , m_shouldLogError(shouldLogError) |
128 | { |
129 | relaxAdoptionRequirement(); |
130 | |
131 | // Setting a referrer header is only supported in the async code path. |
132 | ASSERT(m_async || m_referrer.isEmpty()); |
133 | |
134 | // Referrer and Origin headers should be set after the preflight if any. |
135 | ASSERT(!request.hasHTTPReferrer() && !request.hasHTTPOrigin()); |
136 | |
137 | ASSERT_WITH_SECURITY_IMPLICATION(isAllowedByContentSecurityPolicy(request.url(), ContentSecurityPolicy::RedirectResponseReceived::No)); |
138 | |
139 | m_options.storedCredentialsPolicy = (m_options.credentials == FetchOptions::Credentials::Include || (m_options.credentials == FetchOptions::Credentials::SameOrigin && m_sameOriginRequest)) ? StoredCredentialsPolicy::Use : StoredCredentialsPolicy::DoNotUse; |
140 | |
141 | ASSERT(!request.httpHeaderFields().contains(HTTPHeaderName::Origin)); |
142 | |
143 | // Copy headers if we need to replay the request after a redirection. |
144 | if (m_options.mode == FetchOptions::Mode::Cors) |
145 | m_originalHeaders = request.httpHeaderFields(); |
146 | |
147 | if (shouldSetHTTPHeadersToKeep()) |
148 | m_options.httpHeadersToKeep = httpHeadersToKeepFromCleaning(request.httpHeaderFields()); |
149 | |
150 | if (document.isRunningUserScripts() && SchemeRegistry::isUserExtensionScheme(request.url().protocol().toStringWithoutCopying())) { |
151 | m_options.mode = FetchOptions::Mode::NoCors; |
152 | m_options.filteringPolicy = ResponseFilteringPolicy::Disable; |
153 | } |
154 | |
155 | m_options.cspResponseHeaders = m_options.contentSecurityPolicyEnforcement != ContentSecurityPolicyEnforcement::DoNotEnforce ? this->contentSecurityPolicy().responseHeaders() : ContentSecurityPolicyResponseHeaders { }; |
156 | |
157 | // As per step 11 of https://fetch.spec.whatwg.org/#main-fetch, data scheme (if same-origin data-URL flag is set) and about scheme are considered same-origin. |
158 | if (request.url().protocolIsData()) |
159 | m_sameOriginRequest = options.sameOriginDataURLFlag == SameOriginDataURLFlag::Set; |
160 | |
161 | if (m_sameOriginRequest || m_options.mode == FetchOptions::Mode::NoCors || m_options.mode == FetchOptions::Mode::Navigate) { |
162 | loadRequest(WTFMove(request), SecurityCheckPolicy::DoSecurityCheck); |
163 | return; |
164 | } |
165 | |
166 | if (m_options.mode == FetchOptions::Mode::SameOrigin) { |
167 | logErrorAndFail(ResourceError(errorDomainWebKitInternal, 0, request.url(), "Cross origin requests are not allowed when using same-origin fetch mode." )); |
168 | return; |
169 | } |
170 | |
171 | makeCrossOriginAccessRequest(WTFMove(request)); |
172 | } |
173 | |
174 | bool DocumentThreadableLoader::checkURLSchemeAsCORSEnabled(const URL& url) |
175 | { |
176 | // Cross-origin requests are only allowed for HTTP and registered schemes. We would catch this when checking response headers later, but there is no reason to send a request that's guaranteed to be denied. |
177 | if (!SchemeRegistry::shouldTreatURLSchemeAsCORSEnabled(url.protocol().toStringWithoutCopying())) { |
178 | logErrorAndFail(ResourceError(errorDomainWebKitInternal, 0, url, "Cross origin requests are only supported for HTTP." , ResourceError::Type::AccessControl)); |
179 | return false; |
180 | } |
181 | return true; |
182 | } |
183 | |
184 | void DocumentThreadableLoader::makeCrossOriginAccessRequest(ResourceRequest&& request) |
185 | { |
186 | ASSERT(m_options.mode == FetchOptions::Mode::Cors); |
187 | |
188 | #if PLATFORM(IOS_FAMILY) |
189 | bool needsPreflightQuirk = IOSApplication::isMoviStarPlus() && applicationSDKVersion() < DYLD_IOS_VERSION_12_0 && (m_options.preflightPolicy == PreflightPolicy::Consider || m_options.preflightPolicy == PreflightPolicy::Force); |
190 | #else |
191 | bool needsPreflightQuirk = false; |
192 | #endif |
193 | |
194 | if ((m_options.preflightPolicy == PreflightPolicy::Consider && isSimpleCrossOriginAccessRequest(request.httpMethod(), request.httpHeaderFields())) || m_options.preflightPolicy == PreflightPolicy::Prevent || (shouldPerformSecurityChecks() && !needsPreflightQuirk)) { |
195 | if (checkURLSchemeAsCORSEnabled(request.url())) |
196 | makeSimpleCrossOriginAccessRequest(WTFMove(request)); |
197 | } else { |
198 | #if ENABLE(SERVICE_WORKER) |
199 | if (m_options.serviceWorkersMode == ServiceWorkersMode::All && m_async) { |
200 | if (m_options.serviceWorkerRegistrationIdentifier || document().activeServiceWorker()) { |
201 | ASSERT(!m_bypassingPreflightForServiceWorkerRequest); |
202 | m_bypassingPreflightForServiceWorkerRequest = WTFMove(request); |
203 | m_options.serviceWorkersMode = ServiceWorkersMode::Only; |
204 | loadRequest(ResourceRequest { m_bypassingPreflightForServiceWorkerRequest.value() }, SecurityCheckPolicy::SkipSecurityCheck); |
205 | return; |
206 | } |
207 | } |
208 | #endif |
209 | if (!needsPreflightQuirk && !checkURLSchemeAsCORSEnabled(request.url())) |
210 | return; |
211 | |
212 | m_simpleRequest = false; |
213 | if (CrossOriginPreflightResultCache::singleton().canSkipPreflight(securityOrigin().toString(), request.url(), m_options.storedCredentialsPolicy, request.httpMethod(), request.httpHeaderFields())) |
214 | preflightSuccess(WTFMove(request)); |
215 | else |
216 | makeCrossOriginAccessRequestWithPreflight(WTFMove(request)); |
217 | } |
218 | } |
219 | |
220 | void DocumentThreadableLoader::makeSimpleCrossOriginAccessRequest(ResourceRequest&& request) |
221 | { |
222 | ASSERT(m_options.preflightPolicy != PreflightPolicy::Force || shouldPerformSecurityChecks()); |
223 | ASSERT(m_options.preflightPolicy == PreflightPolicy::Prevent || isSimpleCrossOriginAccessRequest(request.httpMethod(), request.httpHeaderFields()) || shouldPerformSecurityChecks()); |
224 | |
225 | updateRequestForAccessControl(request, securityOrigin(), m_options.storedCredentialsPolicy); |
226 | loadRequest(WTFMove(request), SecurityCheckPolicy::DoSecurityCheck); |
227 | } |
228 | |
229 | void DocumentThreadableLoader::makeCrossOriginAccessRequestWithPreflight(ResourceRequest&& request) |
230 | { |
231 | if (m_async) { |
232 | m_preflightChecker.emplace(*this, WTFMove(request)); |
233 | m_preflightChecker->startPreflight(); |
234 | return; |
235 | } |
236 | CrossOriginPreflightChecker::doPreflight(*this, WTFMove(request)); |
237 | } |
238 | |
239 | DocumentThreadableLoader::~DocumentThreadableLoader() |
240 | { |
241 | if (m_resource) |
242 | m_resource->removeClient(*this); |
243 | } |
244 | |
245 | void DocumentThreadableLoader::cancel() |
246 | { |
247 | Ref<DocumentThreadableLoader> protectedThis(*this); |
248 | |
249 | // Cancel can re-enter and m_resource might be null here as a result. |
250 | if (m_client && m_resource) { |
251 | // FIXME: This error is sent to the client in didFail(), so it should not be an internal one. Use FrameLoaderClient::cancelledError() instead. |
252 | ResourceError error(errorDomainWebKitInternal, 0, m_resource->url(), "Load cancelled" , ResourceError::Type::Cancellation); |
253 | m_client->didFail(error); |
254 | } |
255 | clearResource(); |
256 | m_client = nullptr; |
257 | } |
258 | |
259 | void DocumentThreadableLoader::setDefersLoading(bool value) |
260 | { |
261 | if (m_resource) |
262 | m_resource->setDefersLoading(value); |
263 | if (m_preflightChecker) |
264 | m_preflightChecker->setDefersLoading(value); |
265 | } |
266 | |
267 | void DocumentThreadableLoader::clearResource() |
268 | { |
269 | // Script can cancel and restart a request reentrantly within removeClient(), |
270 | // which could lead to calling CachedResource::removeClient() multiple times for |
271 | // this DocumentThreadableLoader. Save off a copy of m_resource and clear it to |
272 | // prevent the reentrancy. |
273 | if (CachedResourceHandle<CachedRawResource> resource = m_resource) { |
274 | m_resource = nullptr; |
275 | resource->removeClient(*this); |
276 | } |
277 | if (m_preflightChecker) |
278 | m_preflightChecker = WTF::nullopt; |
279 | } |
280 | |
281 | void DocumentThreadableLoader::redirectReceived(CachedResource& resource, ResourceRequest&& request, const ResourceResponse& redirectResponse, CompletionHandler<void(ResourceRequest&&)>&& completionHandler) |
282 | { |
283 | ASSERT(m_client); |
284 | ASSERT_UNUSED(resource, &resource == m_resource); |
285 | |
286 | Ref<DocumentThreadableLoader> protectedThis(*this); |
287 | --m_options.maxRedirectCount; |
288 | |
289 | // FIXME: We restrict this check to Fetch API for the moment, as this might disrupt WorkerScriptLoader. |
290 | // Reassess this check based on https://github.com/whatwg/fetch/issues/393 discussions. |
291 | // We should also disable that check in navigation mode. |
292 | if (!request.url().protocolIsInHTTPFamily() && m_options.initiator == cachedResourceRequestInitiators().fetch) { |
293 | reportRedirectionWithBadScheme(request.url()); |
294 | clearResource(); |
295 | return completionHandler(WTFMove(request)); |
296 | } |
297 | |
298 | if (platformStrategies()->loaderStrategy()->havePerformedSecurityChecks(redirectResponse)) { |
299 | completionHandler(WTFMove(request)); |
300 | return; |
301 | } |
302 | |
303 | if (!isAllowedByContentSecurityPolicy(request.url(), redirectResponse.isNull() ? ContentSecurityPolicy::RedirectResponseReceived::No : ContentSecurityPolicy::RedirectResponseReceived::Yes)) { |
304 | reportContentSecurityPolicyError(redirectResponse.url()); |
305 | clearResource(); |
306 | return completionHandler(WTFMove(request)); |
307 | } |
308 | |
309 | // Allow same origin requests to continue after allowing clients to audit the redirect. |
310 | if (isAllowedRedirect(request.url())) |
311 | return completionHandler(WTFMove(request)); |
312 | |
313 | // Force any subsequent request to use these checks. |
314 | m_sameOriginRequest = false; |
315 | |
316 | ASSERT(m_resource); |
317 | ASSERT(m_originalHeaders); |
318 | |
319 | // Use a unique for subsequent loads if needed. |
320 | // https://fetch.spec.whatwg.org/#concept-http-redirect-fetch (Step 10). |
321 | ASSERT(m_options.mode == FetchOptions::Mode::Cors); |
322 | if (!securityOrigin().canRequest(redirectResponse.url()) && !protocolHostAndPortAreEqual(redirectResponse.url(), request.url())) |
323 | m_origin = SecurityOrigin::createUnique(); |
324 | |
325 | // Except in case where preflight is needed, loading should be able to continue on its own. |
326 | // But we also handle credentials here if it is restricted to SameOrigin. |
327 | if (m_options.credentials != FetchOptions::Credentials::SameOrigin && m_simpleRequest && isSimpleCrossOriginAccessRequest(request.httpMethod(), *m_originalHeaders)) |
328 | return completionHandler(WTFMove(request)); |
329 | |
330 | if (m_options.credentials == FetchOptions::Credentials::SameOrigin) |
331 | m_options.storedCredentialsPolicy = StoredCredentialsPolicy::DoNotUse; |
332 | |
333 | clearResource(); |
334 | |
335 | m_referrer = request.httpReferrer(); |
336 | if (m_referrer.isNull()) |
337 | m_options.referrerPolicy = ReferrerPolicy::NoReferrer; |
338 | |
339 | // Let's fetch the request with the original headers (equivalent to request cloning specified by fetch algorithm). |
340 | // Do not copy the Authorization header if removed by the network layer. |
341 | if (!request.httpHeaderFields().contains(HTTPHeaderName::Authorization)) |
342 | m_originalHeaders->remove(HTTPHeaderName::Authorization); |
343 | request.setHTTPHeaderFields(*m_originalHeaders); |
344 | |
345 | #if ENABLE(SERVICE_WORKER) |
346 | if (redirectResponse.source() != ResourceResponse::Source::ServiceWorker && redirectResponse.source() != ResourceResponse::Source::MemoryCache) |
347 | m_options.serviceWorkersMode = ServiceWorkersMode::None; |
348 | #endif |
349 | makeCrossOriginAccessRequest(ResourceRequest(request)); |
350 | completionHandler(WTFMove(request)); |
351 | } |
352 | |
353 | void DocumentThreadableLoader::dataSent(CachedResource& resource, unsigned long long bytesSent, unsigned long long totalBytesToBeSent) |
354 | { |
355 | ASSERT(m_client); |
356 | ASSERT_UNUSED(resource, &resource == m_resource); |
357 | m_client->didSendData(bytesSent, totalBytesToBeSent); |
358 | } |
359 | |
360 | void DocumentThreadableLoader::responseReceived(CachedResource& resource, const ResourceResponse& response, CompletionHandler<void()>&& completionHandler) |
361 | { |
362 | ASSERT_UNUSED(resource, &resource == m_resource); |
363 | didReceiveResponse(m_resource->identifier(), response); |
364 | |
365 | if (completionHandler) |
366 | completionHandler(); |
367 | } |
368 | |
369 | void DocumentThreadableLoader::didReceiveResponse(unsigned long identifier, const ResourceResponse& response) |
370 | { |
371 | ASSERT(m_client); |
372 | ASSERT(response.type() != ResourceResponse::Type::Error); |
373 | |
374 | InspectorInstrumentation::didReceiveThreadableLoaderResponse(*this, identifier); |
375 | |
376 | if (m_delayCallbacksForIntegrityCheck) |
377 | return; |
378 | |
379 | if (options().filteringPolicy == ResponseFilteringPolicy::Disable) { |
380 | m_client->didReceiveResponse(identifier, response); |
381 | return; |
382 | } |
383 | |
384 | if (response.type() == ResourceResponse::Type::Default) { |
385 | m_client->didReceiveResponse(identifier, ResourceResponseBase::filter(response)); |
386 | if (response.tainting() == ResourceResponse::Tainting::Opaque) { |
387 | clearResource(); |
388 | if (m_client) |
389 | m_client->didFinishLoading(identifier); |
390 | } |
391 | return; |
392 | } |
393 | ASSERT(response.type() == ResourceResponse::Type::Opaqueredirect || response.source() == ResourceResponse::Source::ServiceWorker || response.source() == ResourceResponse::Source::MemoryCache); |
394 | m_client->didReceiveResponse(identifier, response); |
395 | } |
396 | |
397 | void DocumentThreadableLoader::dataReceived(CachedResource& resource, const char* data, int dataLength) |
398 | { |
399 | ASSERT_UNUSED(resource, &resource == m_resource); |
400 | didReceiveData(m_resource->identifier(), data, dataLength); |
401 | } |
402 | |
403 | void DocumentThreadableLoader::didReceiveData(unsigned long, const char* data, int dataLength) |
404 | { |
405 | ASSERT(m_client); |
406 | |
407 | if (m_delayCallbacksForIntegrityCheck) |
408 | return; |
409 | |
410 | m_client->didReceiveData(data, dataLength); |
411 | } |
412 | |
413 | void DocumentThreadableLoader::finishedTimingForWorkerLoad(CachedResource& resource, const ResourceTiming& resourceTiming) |
414 | { |
415 | ASSERT(m_client); |
416 | ASSERT_UNUSED(resource, &resource == m_resource); |
417 | |
418 | finishedTimingForWorkerLoad(resourceTiming); |
419 | } |
420 | |
421 | void DocumentThreadableLoader::finishedTimingForWorkerLoad(const ResourceTiming& resourceTiming) |
422 | { |
423 | ASSERT(m_options.initiatorContext == InitiatorContext::Worker); |
424 | |
425 | m_client->didFinishTiming(resourceTiming); |
426 | } |
427 | |
428 | void DocumentThreadableLoader::notifyFinished(CachedResource& resource) |
429 | { |
430 | ASSERT(m_client); |
431 | ASSERT_UNUSED(resource, &resource == m_resource); |
432 | |
433 | if (m_resource->errorOccurred()) |
434 | didFail(m_resource->identifier(), m_resource->resourceError()); |
435 | else |
436 | didFinishLoading(m_resource->identifier()); |
437 | } |
438 | |
439 | void DocumentThreadableLoader::didFinishLoading(unsigned long identifier) |
440 | { |
441 | ASSERT(m_client); |
442 | |
443 | if (m_delayCallbacksForIntegrityCheck) { |
444 | if (!matchIntegrityMetadata(*m_resource, m_options.integrity)) { |
445 | reportIntegrityMetadataError(m_resource->url()); |
446 | return; |
447 | } |
448 | |
449 | auto response = m_resource->response(); |
450 | |
451 | if (options().filteringPolicy == ResponseFilteringPolicy::Disable) { |
452 | m_client->didReceiveResponse(identifier, response); |
453 | if (m_resource->resourceBuffer()) |
454 | m_client->didReceiveData(m_resource->resourceBuffer()->data(), m_resource->resourceBuffer()->size()); |
455 | } else { |
456 | ASSERT(response.type() == ResourceResponse::Type::Default); |
457 | |
458 | m_client->didReceiveResponse(identifier, ResourceResponseBase::filter(response)); |
459 | if (m_resource->resourceBuffer()) |
460 | m_client->didReceiveData(m_resource->resourceBuffer()->data(), m_resource->resourceBuffer()->size()); |
461 | } |
462 | } |
463 | |
464 | m_client->didFinishLoading(identifier); |
465 | } |
466 | |
467 | void DocumentThreadableLoader::didFail(unsigned long, const ResourceError& error) |
468 | { |
469 | ASSERT(m_client); |
470 | #if ENABLE(SERVICE_WORKER) |
471 | if (m_bypassingPreflightForServiceWorkerRequest && error.isCancellation()) { |
472 | clearResource(); |
473 | |
474 | m_options.serviceWorkersMode = ServiceWorkersMode::None; |
475 | makeCrossOriginAccessRequestWithPreflight(WTFMove(m_bypassingPreflightForServiceWorkerRequest.value())); |
476 | ASSERT(m_bypassingPreflightForServiceWorkerRequest->isNull()); |
477 | m_bypassingPreflightForServiceWorkerRequest = WTF::nullopt; |
478 | return; |
479 | } |
480 | #endif |
481 | |
482 | if (m_shouldLogError == ShouldLogError::Yes) |
483 | logError(m_document, error, m_options.initiator); |
484 | |
485 | m_client->didFail(error); |
486 | } |
487 | |
488 | void DocumentThreadableLoader::preflightSuccess(ResourceRequest&& request) |
489 | { |
490 | ResourceRequest actualRequest(WTFMove(request)); |
491 | updateRequestForAccessControl(actualRequest, securityOrigin(), m_options.storedCredentialsPolicy); |
492 | |
493 | m_preflightChecker = WTF::nullopt; |
494 | |
495 | // It should be ok to skip the security check since we already asked about the preflight request. |
496 | loadRequest(WTFMove(actualRequest), SecurityCheckPolicy::SkipSecurityCheck); |
497 | } |
498 | |
499 | void DocumentThreadableLoader::preflightFailure(unsigned long identifier, const ResourceError& error) |
500 | { |
501 | m_preflightChecker = WTF::nullopt; |
502 | |
503 | InspectorInstrumentation::didFailLoading(m_document.frame(), m_document.frame()->loader().documentLoader(), identifier, error); |
504 | |
505 | if (m_shouldLogError == ShouldLogError::Yes) |
506 | logError(m_document, error, m_options.initiator); |
507 | |
508 | m_client->didFail(error); |
509 | } |
510 | |
511 | void DocumentThreadableLoader::loadRequest(ResourceRequest&& request, SecurityCheckPolicy securityCheck) |
512 | { |
513 | Ref<DocumentThreadableLoader> protectedThis(*this); |
514 | |
515 | // Any credential should have been removed from the cross-site requests. |
516 | const URL& requestURL = request.url(); |
517 | m_options.securityCheck = securityCheck; |
518 | ASSERT(m_sameOriginRequest || requestURL.user().isEmpty()); |
519 | ASSERT(m_sameOriginRequest || requestURL.pass().isEmpty()); |
520 | |
521 | if (!m_referrer.isNull()) |
522 | request.setHTTPReferrer(m_referrer); |
523 | |
524 | if (m_async) { |
525 | ResourceLoaderOptions options = m_options; |
526 | options.clientCredentialPolicy = m_sameOriginRequest ? ClientCredentialPolicy::MayAskClientForCredentials : ClientCredentialPolicy::CannotAskClientForCredentials; |
527 | options.contentSecurityPolicyImposition = ContentSecurityPolicyImposition::SkipPolicyCheck; |
528 | |
529 | // If there is integrity metadata to validate, we must buffer. |
530 | if (!m_options.integrity.isEmpty()) |
531 | options.dataBufferingPolicy = DataBufferingPolicy::BufferData; |
532 | |
533 | request.setAllowCookies(m_options.storedCredentialsPolicy == StoredCredentialsPolicy::Use); |
534 | CachedResourceRequest newRequest(WTFMove(request), options); |
535 | if (RuntimeEnabledFeatures::sharedFeatures().resourceTimingEnabled()) |
536 | newRequest.setInitiator(m_options.initiator); |
537 | newRequest.setOrigin(securityOrigin()); |
538 | |
539 | ASSERT(!m_resource); |
540 | if (m_resource) { |
541 | CachedResourceHandle<CachedRawResource> resource = std::exchange(m_resource, nullptr); |
542 | resource->removeClient(*this); |
543 | } |
544 | |
545 | auto cachedResource = m_document.cachedResourceLoader().requestRawResource(WTFMove(newRequest)); |
546 | m_resource = cachedResource.value_or(nullptr); |
547 | if (m_resource) |
548 | m_resource->addClient(*this); |
549 | else |
550 | logErrorAndFail(cachedResource.error()); |
551 | return; |
552 | } |
553 | |
554 | // If credentials mode is 'Omit', we should disable cookie sending. |
555 | ASSERT(m_options.credentials != FetchOptions::Credentials::Omit); |
556 | |
557 | LoadTiming loadTiming; |
558 | loadTiming.markStartTimeAndFetchStart(); |
559 | |
560 | // FIXME: ThreadableLoaderOptions.sniffContent is not supported for synchronous requests. |
561 | RefPtr<SharedBuffer> data; |
562 | ResourceError error; |
563 | ResourceResponse response; |
564 | unsigned long identifier = std::numeric_limits<unsigned long>::max(); |
565 | if (m_document.frame()) { |
566 | auto& frameLoader = m_document.frame()->loader(); |
567 | if (!frameLoader.mixedContentChecker().canRunInsecureContent(m_document.securityOrigin(), requestURL)) |
568 | return; |
569 | identifier = frameLoader.loadResourceSynchronously(request, m_options.clientCredentialPolicy, m_options, *m_originalHeaders, error, response, data); |
570 | } |
571 | |
572 | loadTiming.setResponseEnd(MonotonicTime::now()); |
573 | |
574 | if (!error.isNull() && response.httpStatusCode() <= 0) { |
575 | if (requestURL.isLocalFile()) { |
576 | // We don't want XMLHttpRequest to raise an exception for file:// resources, see <rdar://problem/4962298>. |
577 | // FIXME: XMLHttpRequest quirks should be in XMLHttpRequest code, not in DocumentThreadableLoader.cpp. |
578 | didReceiveResponse(identifier, response); |
579 | didFinishLoading(identifier); |
580 | return; |
581 | } |
582 | logErrorAndFail(error); |
583 | return; |
584 | } |
585 | |
586 | if (!shouldPerformSecurityChecks()) { |
587 | // FIXME: FrameLoader::loadSynchronously() does not tell us whether a redirect happened or not, so we guess by comparing the |
588 | // request and response URLs. This isn't a perfect test though, since a server can serve a redirect to the same URL that was |
589 | // requested. Also comparing the request and response URLs as strings will fail if the requestURL still has its credentials. |
590 | bool didRedirect = requestURL != response.url(); |
591 | if (didRedirect) { |
592 | if (!isAllowedByContentSecurityPolicy(response.url(), ContentSecurityPolicy::RedirectResponseReceived::Yes)) { |
593 | reportContentSecurityPolicyError(requestURL); |
594 | return; |
595 | } |
596 | if (!isAllowedRedirect(response.url())) { |
597 | reportCrossOriginResourceSharingError(requestURL); |
598 | return; |
599 | } |
600 | } |
601 | |
602 | if (!m_sameOriginRequest) { |
603 | if (m_options.mode == FetchOptions::Mode::NoCors) |
604 | response.setTainting(ResourceResponse::Tainting::Opaque); |
605 | else { |
606 | ASSERT(m_options.mode == FetchOptions::Mode::Cors); |
607 | response.setTainting(ResourceResponse::Tainting::Cors); |
608 | String accessControlErrorDescription; |
609 | if (!passesAccessControlCheck(response, m_options.storedCredentialsPolicy, securityOrigin(), accessControlErrorDescription)) { |
610 | logErrorAndFail(ResourceError(errorDomainWebKitInternal, 0, response.url(), accessControlErrorDescription, ResourceError::Type::AccessControl)); |
611 | return; |
612 | } |
613 | } |
614 | } |
615 | } |
616 | didReceiveResponse(identifier, response); |
617 | |
618 | if (data) |
619 | didReceiveData(identifier, data->data(), data->size()); |
620 | |
621 | if (RuntimeEnabledFeatures::sharedFeatures().resourceTimingEnabled()) { |
622 | auto resourceTiming = ResourceTiming::fromSynchronousLoad(requestURL, m_options.initiator, loadTiming, response.deprecatedNetworkLoadMetrics(), response, securityOrigin()); |
623 | if (options().initiatorContext == InitiatorContext::Worker) |
624 | finishedTimingForWorkerLoad(resourceTiming); |
625 | else { |
626 | if (auto* window = document().domWindow()) |
627 | window->performance().addResourceTiming(WTFMove(resourceTiming)); |
628 | } |
629 | } |
630 | |
631 | didFinishLoading(identifier); |
632 | } |
633 | |
634 | bool DocumentThreadableLoader::isAllowedByContentSecurityPolicy(const URL& url, ContentSecurityPolicy::RedirectResponseReceived redirectResponseReceived) |
635 | { |
636 | switch (m_options.contentSecurityPolicyEnforcement) { |
637 | case ContentSecurityPolicyEnforcement::DoNotEnforce: |
638 | return true; |
639 | case ContentSecurityPolicyEnforcement::EnforceChildSrcDirective: |
640 | return contentSecurityPolicy().allowChildContextFromSource(url, redirectResponseReceived); |
641 | case ContentSecurityPolicyEnforcement::EnforceConnectSrcDirective: |
642 | return contentSecurityPolicy().allowConnectToSource(url, redirectResponseReceived); |
643 | case ContentSecurityPolicyEnforcement::EnforceScriptSrcDirective: |
644 | return contentSecurityPolicy().allowScriptFromSource(url, redirectResponseReceived); |
645 | } |
646 | ASSERT_NOT_REACHED(); |
647 | return false; |
648 | } |
649 | |
650 | bool DocumentThreadableLoader::isAllowedRedirect(const URL& url) |
651 | { |
652 | if (m_options.mode == FetchOptions::Mode::NoCors) |
653 | return true; |
654 | |
655 | return m_sameOriginRequest && securityOrigin().canRequest(url); |
656 | } |
657 | |
658 | bool DocumentThreadableLoader::isXMLHttpRequest() const |
659 | { |
660 | return m_options.initiator == cachedResourceRequestInitiators().xmlhttprequest; |
661 | } |
662 | |
663 | SecurityOrigin& DocumentThreadableLoader::securityOrigin() const |
664 | { |
665 | return m_origin ? *m_origin : m_document.securityOrigin(); |
666 | } |
667 | |
668 | const ContentSecurityPolicy& DocumentThreadableLoader::contentSecurityPolicy() const |
669 | { |
670 | if (m_contentSecurityPolicy) |
671 | return *m_contentSecurityPolicy.get(); |
672 | ASSERT(m_document.contentSecurityPolicy()); |
673 | return *m_document.contentSecurityPolicy(); |
674 | } |
675 | |
676 | void DocumentThreadableLoader::reportRedirectionWithBadScheme(const URL& url) |
677 | { |
678 | logErrorAndFail(ResourceError(errorDomainWebKitInternal, 0, url, "Redirection to URL with a scheme that is not HTTP(S)."_s , ResourceError::Type::AccessControl)); |
679 | } |
680 | |
681 | void DocumentThreadableLoader::reportContentSecurityPolicyError(const URL& url) |
682 | { |
683 | logErrorAndFail(ResourceError(errorDomainWebKitInternal, 0, url, "Blocked by Content Security Policy."_s , ResourceError::Type::AccessControl)); |
684 | } |
685 | |
686 | void DocumentThreadableLoader::reportCrossOriginResourceSharingError(const URL& url) |
687 | { |
688 | logErrorAndFail(ResourceError(errorDomainWebKitInternal, 0, url, "Cross-origin redirection denied by Cross-Origin Resource Sharing policy."_s , ResourceError::Type::AccessControl)); |
689 | } |
690 | |
691 | void DocumentThreadableLoader::reportIntegrityMetadataError(const URL& url) |
692 | { |
693 | logErrorAndFail(ResourceError(errorDomainWebKitInternal, 0, url, "Failed integrity metadata check."_s , ResourceError::Type::General)); |
694 | } |
695 | |
696 | void DocumentThreadableLoader::logErrorAndFail(const ResourceError& error) |
697 | { |
698 | if (m_shouldLogError == ShouldLogError::Yes) { |
699 | if (error.isAccessControl() && !error.localizedDescription().isEmpty()) |
700 | m_document.addConsoleMessage(MessageSource::Security, MessageLevel::Error, error.localizedDescription()); |
701 | logError(m_document, error, m_options.initiator); |
702 | } |
703 | ASSERT(m_client); |
704 | m_client->didFail(error); |
705 | } |
706 | |
707 | } // namespace WebCore |
708 | |