1/*
2 * Copyright (C) 2006-2019 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 *
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 * 3. Neither the name of Apple Inc. ("Apple") nor the names of
14 * its contributors may be used to endorse or promote products derived
15 * from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include "config.h"
30#include "SubresourceLoader.h"
31
32#include "CachedRawResource.h"
33#include "CachedResourceLoader.h"
34#include "CrossOriginAccessControl.h"
35#include "DiagnosticLoggingClient.h"
36#include "DiagnosticLoggingKeys.h"
37#include "Document.h"
38#include "DocumentLoader.h"
39#include "Frame.h"
40#include "FrameLoader.h"
41#include "HTTPParsers.h"
42#include "LinkLoader.h"
43#include "Logging.h"
44#include "MemoryCache.h"
45#include "Page.h"
46#include "ResourceLoadObserver.h"
47#include "ResourceTiming.h"
48#include "RuntimeEnabledFeatures.h"
49#include "Settings.h"
50#include <wtf/CompletionHandler.h>
51#include <wtf/Ref.h>
52#include <wtf/RefCountedLeakCounter.h>
53#include <wtf/StdLibExtras.h>
54#include <wtf/SystemTracing.h>
55#include <wtf/text/CString.h>
56
57#if PLATFORM(IOS_FAMILY)
58#include <RuntimeApplicationChecks.h>
59#endif
60
61#if ENABLE(CONTENT_EXTENSIONS)
62#include "ResourceLoadInfo.h"
63#endif
64
65#if USE(QUICK_LOOK)
66#include "PreviewConverter.h"
67#include "PreviewLoader.h"
68#endif
69
70#undef RELEASE_LOG_IF_ALLOWED
71#undef RELEASE_LOG_ERROR_IF_ALLOWED
72#define RELEASE_LOG_IF_ALLOWED(fmt, ...) RELEASE_LOG_IF(isAlwaysOnLoggingAllowed(), ResourceLoading, "%p - SubresourceLoader::" fmt, this, ##__VA_ARGS__)
73#define RELEASE_LOG_ERROR_IF_ALLOWED(fmt, ...) RELEASE_LOG_ERROR_IF(isAlwaysOnLoggingAllowed(), ResourceLoading, "%p - SubresourceLoader::" fmt, this, ##__VA_ARGS__)
74
75namespace WebCore {
76
77DEFINE_DEBUG_ONLY_GLOBAL(WTF::RefCountedLeakCounter, subresourceLoaderCounter, ("SubresourceLoader"));
78
79SubresourceLoader::RequestCountTracker::RequestCountTracker(CachedResourceLoader& cachedResourceLoader, const CachedResource& resource)
80 : m_cachedResourceLoader(cachedResourceLoader)
81 , m_resource(resource)
82{
83 m_cachedResourceLoader.incrementRequestCount(m_resource);
84}
85
86SubresourceLoader::RequestCountTracker::~RequestCountTracker()
87{
88 m_cachedResourceLoader.decrementRequestCount(m_resource);
89}
90
91SubresourceLoader::SubresourceLoader(Frame& frame, CachedResource& resource, const ResourceLoaderOptions& options)
92 : ResourceLoader(frame, options)
93 , m_resource(&resource)
94 , m_state(Uninitialized)
95 , m_requestCountTracker(std::in_place, frame.document()->cachedResourceLoader(), resource)
96{
97#ifndef NDEBUG
98 subresourceLoaderCounter.increment();
99#endif
100#if ENABLE(CONTENT_EXTENSIONS)
101 m_resourceType = ContentExtensions::toResourceType(resource.type());
102#endif
103 m_canCrossOriginRequestsAskUserForCredentials = resource.type() == CachedResource::Type::MainResource || frame.settings().allowCrossOriginSubresourcesToAskForCredentials();
104}
105
106SubresourceLoader::~SubresourceLoader()
107{
108 ASSERT(m_state != Initialized);
109 ASSERT(reachedTerminalState());
110#ifndef NDEBUG
111 subresourceLoaderCounter.decrement();
112#endif
113}
114
115void SubresourceLoader::create(Frame& frame, CachedResource& resource, ResourceRequest&& request, const ResourceLoaderOptions& options, CompletionHandler<void(RefPtr<SubresourceLoader>&&)>&& completionHandler)
116{
117 auto subloader(adoptRef(*new SubresourceLoader(frame, resource, options)));
118#if PLATFORM(IOS_FAMILY)
119 if (!IOSApplication::isWebProcess()) {
120 // On iOS, do not invoke synchronous resource load delegates while resource load scheduling
121 // is disabled to avoid re-entering style selection from a different thread (see <rdar://problem/9121719>).
122 // FIXME: This should be fixed for all ports in <https://bugs.webkit.org/show_bug.cgi?id=56647>.
123 subloader->m_iOSOriginalRequest = request;
124 return completionHandler(WTFMove(subloader));
125 }
126#endif
127 subloader->init(WTFMove(request), [subloader = subloader.copyRef(), completionHandler = WTFMove(completionHandler)] (bool initialized) mutable {
128 if (!initialized)
129 return completionHandler(nullptr);
130 completionHandler(WTFMove(subloader));
131 });
132}
133
134#if PLATFORM(IOS_FAMILY)
135void SubresourceLoader::startLoading()
136{
137 // FIXME: this should probably be removed.
138 ASSERT(!IOSApplication::isWebProcess());
139 init(ResourceRequest(m_iOSOriginalRequest), [this, protectedThis = makeRef(*this)] (bool success) {
140 if (!success)
141 return;
142 m_iOSOriginalRequest = ResourceRequest();
143 start();
144 });
145}
146#endif
147
148CachedResource* SubresourceLoader::cachedResource()
149{
150 return m_resource;
151}
152
153void SubresourceLoader::cancelIfNotFinishing()
154{
155 if (m_state != Initialized)
156 return;
157
158 ResourceLoader::cancel();
159}
160
161void SubresourceLoader::init(ResourceRequest&& request, CompletionHandler<void(bool)>&& completionHandler)
162{
163 ResourceLoader::init(WTFMove(request), [this, protectedThis = makeRef(*this), completionHandler = WTFMove(completionHandler)] (bool initialized) mutable {
164 if (!initialized)
165 return completionHandler(false);
166 ASSERT(!reachedTerminalState());
167 m_state = Initialized;
168 m_documentLoader->addSubresourceLoader(this);
169 m_origin = m_resource->origin();
170 completionHandler(true);
171 });
172}
173
174bool SubresourceLoader::isSubresourceLoader() const
175{
176 return true;
177}
178
179void SubresourceLoader::willSendRequestInternal(ResourceRequest&& newRequest, const ResourceResponse& redirectResponse, CompletionHandler<void(ResourceRequest&&)>&& completionHandler)
180{
181 // Store the previous URL because the call to ResourceLoader::willSendRequest will modify it.
182 URL previousURL = request().url();
183 Ref<SubresourceLoader> protectedThis(*this);
184
185 if (!newRequest.url().isValid()) {
186 cancel(cannotShowURLError());
187 return completionHandler(WTFMove(newRequest));
188 }
189
190 if (newRequest.requester() != ResourceRequestBase::Requester::Main) {
191 tracePoint(SubresourceLoadWillStart);
192 ResourceLoadObserver::shared().logSubresourceLoading(m_frame.get(), newRequest, redirectResponse);
193 }
194
195 auto continueWillSendRequest = [this, protectedThis = makeRef(*this), redirectResponse] (CompletionHandler<void(ResourceRequest&&)>&& completionHandler, ResourceRequest&& newRequest) mutable {
196 if (newRequest.isNull() || reachedTerminalState())
197 return completionHandler(WTFMove(newRequest));
198
199 ResourceLoader::willSendRequestInternal(WTFMove(newRequest), redirectResponse, [this, protectedThis = WTFMove(protectedThis), completionHandler = WTFMove(completionHandler), redirectResponse] (ResourceRequest&& request) mutable {
200 if (reachedTerminalState())
201 return completionHandler(WTFMove(request));
202
203 if (request.isNull()) {
204 cancel();
205 return completionHandler(WTFMove(request));
206 }
207
208 if (m_resource->type() == CachedResource::Type::MainResource && !redirectResponse.isNull())
209 m_documentLoader->willContinueMainResourceLoadAfterRedirect(request);
210 completionHandler(WTFMove(request));
211 });
212 };
213
214 ASSERT(!newRequest.isNull());
215 if (!redirectResponse.isNull()) {
216 if (options().redirect != FetchOptions::Redirect::Follow) {
217 if (options().redirect == FetchOptions::Redirect::Error) {
218 ResourceError error { errorDomainWebKitInternal, 0, request().url(), makeString("Not allowed to follow a redirection while loading ", request().url().string()), ResourceError::Type::AccessControl };
219
220 if (m_frame && m_frame->document())
221 m_frame->document()->addConsoleMessage(MessageSource::Security, MessageLevel::Error, error.localizedDescription());
222
223 RELEASE_LOG_IF_ALLOWED("willSendRequestinternal: resource load canceled because not allowed to follow a redirect (frame = %p, frameLoader = %p, resourceID = %lu)", frame(), frameLoader(), identifier());
224
225 cancel(error);
226 return completionHandler(WTFMove(newRequest));
227 }
228
229 ResourceResponse opaqueRedirectedResponse = redirectResponse;
230 opaqueRedirectedResponse.setType(ResourceResponse::Type::Opaqueredirect);
231 opaqueRedirectedResponse.setTainting(ResourceResponse::Tainting::Opaqueredirect);
232 m_resource->responseReceived(opaqueRedirectedResponse);
233 if (reachedTerminalState()) {
234 RELEASE_LOG_IF_ALLOWED("willSendRequestinternal: reached terminal state (frame = %p, frameLoader = %p, resourceID = %lu)", frame(), frameLoader(), identifier());
235 return;
236 }
237
238 RELEASE_LOG_IF_ALLOWED("willSendRequestinternal: resource load completed (frame = %p, frameLoader = %p, resourceID = %lu)", frame(), frameLoader(), identifier());
239
240 NetworkLoadMetrics emptyMetrics;
241 didFinishLoading(emptyMetrics);
242 return completionHandler(WTFMove(newRequest));
243 } else if (m_redirectCount++ >= options().maxRedirectCount) {
244 RELEASE_LOG_IF_ALLOWED("willSendRequestinternal: resource load canceled because too many redirects (frame = %p, frameLoader = %p, resourceID = %lu)", frame(), frameLoader(), identifier());
245 cancel(ResourceError(String(), 0, request().url(), "Too many redirections"_s, ResourceError::Type::General));
246 return completionHandler(WTFMove(newRequest));
247 }
248
249 // CachedResources are keyed off their original request URL.
250 // Requesting the same original URL a second time can redirect to a unique second resource.
251 // Therefore, if a redirect to a different destination URL occurs, we should no longer consider this a revalidation of the first resource.
252 // Doing so would have us reusing the resource from the first request if the second request's revalidation succeeds.
253 if (newRequest.isConditional() && m_resource->resourceToRevalidate() && newRequest.url() != m_resource->resourceToRevalidate()->response().url()) {
254 newRequest.makeUnconditional();
255 MemoryCache::singleton().revalidationFailed(*m_resource);
256 if (m_frame && m_frame->page())
257 m_frame->page()->diagnosticLoggingClient().logDiagnosticMessageWithResult(DiagnosticLoggingKeys::cachedResourceRevalidationKey(), emptyString(), DiagnosticLoggingResultFail, ShouldSample::Yes);
258 }
259
260 if (!m_documentLoader->cachedResourceLoader().updateRequestAfterRedirection(m_resource->type(), newRequest, options())) {
261 RELEASE_LOG_IF_ALLOWED("willSendRequestinternal: resource load canceled because something about updateRequestAfterRedirection (frame = %p, frameLoader = %p, resourceID = %lu)", frame(), frameLoader(), identifier());
262 cancel();
263 return completionHandler(WTFMove(newRequest));
264 }
265
266 String errorDescription;
267 if (!checkRedirectionCrossOriginAccessControl(request(), redirectResponse, newRequest, errorDescription)) {
268 String errorMessage = "Cross-origin redirection to " + newRequest.url().string() + " denied by Cross-Origin Resource Sharing policy: " + errorDescription;
269 if (m_frame && m_frame->document())
270 m_frame->document()->addConsoleMessage(MessageSource::Security, MessageLevel::Error, errorMessage);
271 RELEASE_LOG_IF_ALLOWED("willSendRequestinternal: resource load canceled because crosss-origin redirection denied by CORS policy (frame = %p, frameLoader = %p, resourceID = %lu)", frame(), frameLoader(), identifier());
272 cancel(ResourceError(String(), 0, request().url(), errorMessage, ResourceError::Type::AccessControl));
273 return completionHandler(WTFMove(newRequest));
274 }
275
276 if (m_resource->isImage() && m_documentLoader->cachedResourceLoader().shouldDeferImageLoad(newRequest.url())) {
277 RELEASE_LOG_IF_ALLOWED("willSendRequestinternal: resource load canceled because it's an image that should be defered (frame = %p, frameLoader = %p, resourceID = %lu)", frame(), frameLoader(), identifier());
278 cancel();
279 return completionHandler(WTFMove(newRequest));
280 }
281 m_loadTiming.addRedirect(redirectResponse.url(), newRequest.url());
282 m_resource->redirectReceived(WTFMove(newRequest), redirectResponse, [completionHandler = WTFMove(completionHandler), continueWillSendRequest = WTFMove(continueWillSendRequest)] (ResourceRequest&& request) mutable {
283 continueWillSendRequest(WTFMove(completionHandler), WTFMove(request));
284 });
285 return;
286 }
287
288 continueWillSendRequest(WTFMove(completionHandler), WTFMove(newRequest));
289}
290
291void SubresourceLoader::didSendData(unsigned long long bytesSent, unsigned long long totalBytesToBeSent)
292{
293 ASSERT(m_state == Initialized);
294 Ref<SubresourceLoader> protectedThis(*this);
295 m_resource->didSendData(bytesSent, totalBytesToBeSent);
296}
297
298#if USE(QUICK_LOOK)
299
300bool SubresourceLoader::shouldCreatePreviewLoaderForResponse(const ResourceResponse& response) const
301{
302 if (m_resource->type() != CachedResource::Type::MainResource)
303 return false;
304
305 if (m_previewLoader)
306 return false;
307
308 return PreviewConverter::supportsMIMEType(response.mimeType());
309}
310
311#endif
312
313void SubresourceLoader::didReceiveResponse(const ResourceResponse& response, CompletionHandler<void()>&& policyCompletionHandler)
314{
315 ASSERT(!response.isNull());
316 ASSERT(m_state == Initialized);
317
318 CompletionHandlerCallingScope completionHandlerCaller(WTFMove(policyCompletionHandler));
319
320#if USE(QUICK_LOOK)
321 if (shouldCreatePreviewLoaderForResponse(response)) {
322 m_previewLoader = PreviewLoader::create(*this, response);
323 if (m_previewLoader->didReceiveResponse(response))
324 return;
325 }
326#endif
327#if ENABLE(SERVICE_WORKER)
328 // Implementing step 10 of https://fetch.spec.whatwg.org/#main-fetch for service worker responses.
329 if (response.source() == ResourceResponse::Source::ServiceWorker && response.url() != request().url()) {
330 auto& loader = m_documentLoader->cachedResourceLoader();
331 if (!loader.allowedByContentSecurityPolicy(m_resource->type(), response.url(), options(), ContentSecurityPolicy::RedirectResponseReceived::Yes)) {
332 RELEASE_LOG_IF_ALLOWED("didReceiveResponse: canceling load because not allowed by content policy (frame = %p, frameLoader = %p, resourceID = %lu)", frame(), frameLoader(), identifier());
333 cancel(ResourceError({ }, 0, response.url(), { }, ResourceError::Type::General));
334 return;
335 }
336 }
337#endif
338
339 // We want redirect responses to be processed through willSendRequestInternal. Exceptions are
340 // redirection with no Location headers and fetch in manual redirect mode. Or in rare circumstances,
341 // cases of too many redirects from CFNetwork (<rdar://problem/30610988>).
342#if !PLATFORM(COCOA)
343 ASSERT(response.httpStatusCode() < 300 || response.httpStatusCode() >= 400 || response.httpStatusCode() == 304 || !response.httpHeaderField(HTTPHeaderName::Location) || response.type() == ResourceResponse::Type::Opaqueredirect);
344#endif
345
346 // Reference the object in this method since the additional processing can do
347 // anything including removing the last reference to this object; one example of this is 3266216.
348 Ref<SubresourceLoader> protectedThis(*this);
349
350 if (shouldIncludeCertificateInfo())
351 response.includeCertificateInfo();
352
353 if (m_resource->resourceToRevalidate()) {
354 if (response.httpStatusCode() == 304) {
355 // 304 Not modified / Use local copy
356 // Existing resource is ok, just use it updating the expiration time.
357 ResourceResponse revalidationResponse = response;
358 revalidationResponse.setSource(ResourceResponse::Source::MemoryCacheAfterValidation);
359 m_resource->setResponse(revalidationResponse);
360 MemoryCache::singleton().revalidationSucceeded(*m_resource, revalidationResponse);
361 if (m_frame && m_frame->page())
362 m_frame->page()->diagnosticLoggingClient().logDiagnosticMessageWithResult(DiagnosticLoggingKeys::cachedResourceRevalidationKey(), emptyString(), DiagnosticLoggingResultPass, ShouldSample::Yes);
363 if (!reachedTerminalState())
364 ResourceLoader::didReceiveResponse(revalidationResponse, [completionHandlerCaller = WTFMove(completionHandlerCaller)] { });
365 return;
366 }
367 // Did not get 304 response, continue as a regular resource load.
368 MemoryCache::singleton().revalidationFailed(*m_resource);
369 if (m_frame && m_frame->page())
370 m_frame->page()->diagnosticLoggingClient().logDiagnosticMessageWithResult(DiagnosticLoggingKeys::cachedResourceRevalidationKey(), emptyString(), DiagnosticLoggingResultFail, ShouldSample::Yes);
371 }
372
373 String errorDescription;
374 if (!checkResponseCrossOriginAccessControl(response, errorDescription)) {
375 if (m_frame && m_frame->document())
376 m_frame->document()->addConsoleMessage(MessageSource::Security, MessageLevel::Error, errorDescription);
377 RELEASE_LOG_IF_ALLOWED("didReceiveResponse: canceling load because of cross origin access control (frame = %p, frameLoader = %p, resourceID = %lu)", frame(), frameLoader(), identifier());
378 cancel(ResourceError(String(), 0, request().url(), errorDescription, ResourceError::Type::AccessControl));
379 return;
380 }
381
382 m_resource->responseReceived(response);
383 if (reachedTerminalState())
384 return;
385
386 bool isResponseMultipart = response.isMultipart();
387 if (options().mode != FetchOptions::Mode::Navigate)
388 LinkLoader::loadLinksFromHeader(response.httpHeaderField(HTTPHeaderName::Link), m_documentLoader->url(), *m_frame->document(), LinkLoader::MediaAttributeCheck::SkipMediaAttributeCheck);
389 ResourceLoader::didReceiveResponse(response, [this, protectedThis = WTFMove(protectedThis), isResponseMultipart, completionHandlerCaller = WTFMove(completionHandlerCaller)]() mutable {
390 if (reachedTerminalState())
391 return;
392
393 // FIXME: Main resources have a different set of rules for multipart than images do.
394 // Hopefully we can merge those 2 paths.
395 if (isResponseMultipart && m_resource->type() != CachedResource::Type::MainResource) {
396 m_loadingMultipartContent = true;
397
398 // We don't count multiParts in a CachedResourceLoader's request count
399 m_requestCountTracker = WTF::nullopt;
400 if (!m_resource->isImage()) {
401 RELEASE_LOG_IF_ALLOWED("didReceiveResponse: canceling load because something about a multi-part non-image (frame = %p, frameLoader = %p, resourceID = %lu)", frame(), frameLoader(), identifier());
402 cancel();
403 return;
404 }
405 }
406
407 auto* buffer = resourceData();
408 if (m_loadingMultipartContent && buffer && buffer->size()) {
409 // The resource data will change as the next part is loaded, so we need to make a copy.
410 m_resource->finishLoading(buffer->copy().ptr());
411 clearResourceData();
412 // Since a subresource loader does not load multipart sections progressively, data was delivered to the loader all at once.
413 // After the first multipart section is complete, signal to delegates that this load is "finished"
414 NetworkLoadMetrics emptyMetrics;
415 m_documentLoader->subresourceLoaderFinishedLoadingOnePart(this);
416 didFinishLoadingOnePart(emptyMetrics);
417 }
418
419 checkForHTTPStatusCodeError();
420
421 if (m_inAsyncResponsePolicyCheck)
422 m_policyForResponseCompletionHandler = completionHandlerCaller.release();
423 });
424}
425
426void SubresourceLoader::didReceiveResponsePolicy()
427{
428 ASSERT(m_inAsyncResponsePolicyCheck);
429 m_inAsyncResponsePolicyCheck = false;
430 if (auto completionHandler = WTFMove(m_policyForResponseCompletionHandler))
431 completionHandler();
432}
433
434void SubresourceLoader::didReceiveData(const char* data, unsigned length, long long encodedDataLength, DataPayloadType dataPayloadType)
435{
436#if USE(QUICK_LOOK)
437 if (auto previewLoader = m_previewLoader.get()) {
438 if (previewLoader->didReceiveData(data, length))
439 return;
440 }
441#endif
442
443 didReceiveDataOrBuffer(data, length, nullptr, encodedDataLength, dataPayloadType);
444}
445
446void SubresourceLoader::didReceiveBuffer(Ref<SharedBuffer>&& buffer, long long encodedDataLength, DataPayloadType dataPayloadType)
447{
448#if USE(QUICK_LOOK)
449 if (auto previewLoader = m_previewLoader.get()) {
450 if (previewLoader->didReceiveBuffer(buffer.get()))
451 return;
452 }
453#endif
454
455 didReceiveDataOrBuffer(nullptr, 0, WTFMove(buffer), encodedDataLength, dataPayloadType);
456}
457
458void SubresourceLoader::didReceiveDataOrBuffer(const char* data, int length, RefPtr<SharedBuffer>&& buffer, long long encodedDataLength, DataPayloadType dataPayloadType)
459{
460 ASSERT(m_resource);
461
462 if (m_resource->response().httpStatusCode() >= 400 && !m_resource->shouldIgnoreHTTPStatusCodeErrors())
463 return;
464 ASSERT(!m_resource->resourceToRevalidate());
465 ASSERT(!m_resource->errorOccurred());
466 ASSERT(m_state == Initialized);
467 // Reference the object in this method since the additional processing can do
468 // anything including removing the last reference to this object; one example of this is 3266216.
469 Ref<SubresourceLoader> protectedThis(*this);
470
471 ResourceLoader::didReceiveDataOrBuffer(data, length, buffer.copyRef(), encodedDataLength, dataPayloadType);
472
473 if (!m_loadingMultipartContent) {
474 if (auto* resourceData = this->resourceData())
475 m_resource->updateBuffer(*resourceData);
476 else
477 m_resource->updateData(buffer ? buffer->data() : data, buffer ? buffer->size() : length);
478 }
479}
480
481bool SubresourceLoader::checkForHTTPStatusCodeError()
482{
483 if (m_resource->response().httpStatusCode() < 400 || m_resource->shouldIgnoreHTTPStatusCodeErrors())
484 return false;
485
486 m_state = Finishing;
487 m_resource->error(CachedResource::LoadError);
488 cancel();
489 return true;
490}
491
492static void logResourceLoaded(Frame* frame, CachedResource::Type type)
493{
494 if (!frame || !frame->page())
495 return;
496
497 String resourceType;
498 switch (type) {
499 case CachedResource::Type::MainResource:
500 resourceType = DiagnosticLoggingKeys::mainResourceKey();
501 break;
502 case CachedResource::Type::ImageResource:
503 resourceType = DiagnosticLoggingKeys::imageKey();
504 break;
505#if ENABLE(XSLT)
506 case CachedResource::Type::XSLStyleSheet:
507#endif
508 case CachedResource::Type::CSSStyleSheet:
509 resourceType = DiagnosticLoggingKeys::styleSheetKey();
510 break;
511 case CachedResource::Type::Script:
512 resourceType = DiagnosticLoggingKeys::scriptKey();
513 break;
514 case CachedResource::Type::FontResource:
515#if ENABLE(SVG_FONTS)
516 case CachedResource::Type::SVGFontResource:
517#endif
518 resourceType = DiagnosticLoggingKeys::fontKey();
519 break;
520 case CachedResource::Type::Beacon:
521 case CachedResource::Type::Ping:
522 case CachedResource::Type::MediaResource:
523 case CachedResource::Type::Icon:
524 case CachedResource::Type::RawResource:
525 resourceType = DiagnosticLoggingKeys::rawKey();
526 break;
527 case CachedResource::Type::SVGDocumentResource:
528 resourceType = DiagnosticLoggingKeys::svgDocumentKey();
529 break;
530#if ENABLE(APPLICATION_MANIFEST)
531 case CachedResource::Type::ApplicationManifest:
532 resourceType = DiagnosticLoggingKeys::applicationManifestKey();
533 break;
534#endif
535 case CachedResource::Type::LinkPrefetch:
536#if ENABLE(VIDEO_TRACK)
537 case CachedResource::Type::TextTrackResource:
538#endif
539 resourceType = DiagnosticLoggingKeys::otherKey();
540 break;
541 }
542
543 frame->page()->diagnosticLoggingClient().logDiagnosticMessage(DiagnosticLoggingKeys::resourceLoadedKey(), resourceType, ShouldSample::Yes);
544}
545
546bool SubresourceLoader::checkResponseCrossOriginAccessControl(const ResourceResponse& response, String& errorDescription)
547{
548 if (!m_resource->isCrossOrigin() || options().mode != FetchOptions::Mode::Cors)
549 return true;
550
551#if ENABLE(SERVICE_WORKER)
552 if (response.source() == ResourceResponse::Source::ServiceWorker)
553 return response.tainting() != ResourceResponse::Tainting::Opaque;
554#endif
555
556 ASSERT(m_origin);
557
558 return passesAccessControlCheck(response, options().credentials == FetchOptions::Credentials::Include ? StoredCredentialsPolicy::Use : StoredCredentialsPolicy::DoNotUse, *m_origin, errorDescription);
559}
560
561bool SubresourceLoader::checkRedirectionCrossOriginAccessControl(const ResourceRequest& previousRequest, const ResourceResponse& redirectResponse, ResourceRequest& newRequest, String& errorMessage)
562{
563 bool crossOriginFlag = m_resource->isCrossOrigin();
564 bool isNextRequestCrossOrigin = m_origin && !m_origin->canRequest(newRequest.url());
565
566 if (isNextRequestCrossOrigin)
567 m_resource->setCrossOrigin();
568
569 ASSERT(options().mode != FetchOptions::Mode::SameOrigin || !m_resource->isCrossOrigin());
570
571 // Implementing https://fetch.spec.whatwg.org/#concept-http-redirect-fetch step 7 & 8.
572 if (options().mode == FetchOptions::Mode::Cors) {
573 if (m_resource->isCrossOrigin() && !isValidCrossOriginRedirectionURL(newRequest.url())) {
574 errorMessage = "URL is either a non-HTTP URL or contains credentials."_s;
575 return false;
576 }
577
578 ASSERT(m_origin);
579 if (crossOriginFlag && !passesAccessControlCheck(redirectResponse, options().storedCredentialsPolicy, *m_origin, errorMessage))
580 return false;
581 }
582
583 bool redirectingToNewOrigin = false;
584 if (m_resource->isCrossOrigin()) {
585 if (!crossOriginFlag && isNextRequestCrossOrigin)
586 redirectingToNewOrigin = true;
587 else
588 redirectingToNewOrigin = !protocolHostAndPortAreEqual(previousRequest.url(), newRequest.url());
589 }
590
591 // Implementing https://fetch.spec.whatwg.org/#concept-http-redirect-fetch step 10.
592 if (crossOriginFlag && redirectingToNewOrigin)
593 m_origin = SecurityOrigin::createUnique();
594
595 // Implementing https://fetch.spec.whatwg.org/#concept-http-redirect-fetch step 14.
596 updateReferrerPolicy(redirectResponse.httpHeaderField(HTTPHeaderName::ReferrerPolicy));
597
598 if (options().mode == FetchOptions::Mode::Cors && redirectingToNewOrigin) {
599 cleanHTTPRequestHeadersForAccessControl(newRequest, options().httpHeadersToKeep);
600 updateRequestForAccessControl(newRequest, *m_origin, options().storedCredentialsPolicy);
601 }
602
603 updateRequestReferrer(newRequest, referrerPolicy(), previousRequest.httpReferrer());
604
605 return true;
606}
607
608void SubresourceLoader::updateReferrerPolicy(const String& referrerPolicyValue)
609{
610 if (auto referrerPolicy = parseReferrerPolicy(referrerPolicyValue, ReferrerPolicySource::HTTPHeader)) {
611 ASSERT(*referrerPolicy != ReferrerPolicy::EmptyString);
612 setReferrerPolicy(*referrerPolicy);
613 }
614}
615
616void SubresourceLoader::didFinishLoading(const NetworkLoadMetrics& networkLoadMetrics)
617{
618 RELEASE_LOG_IF_ALLOWED("didFinishLoading: (frame = %p, frameLoader = %p, resourceID = %lu)", frame(), frameLoader(), identifier());
619
620#if USE(QUICK_LOOK)
621 if (auto previewLoader = m_previewLoader.get()) {
622 if (previewLoader->didFinishLoading())
623 return;
624 }
625#endif
626
627 if (m_state != Initialized)
628 return;
629 ASSERT(!reachedTerminalState());
630 ASSERT(!m_resource->resourceToRevalidate());
631 // FIXME (129394): We should cancel the load when a decode error occurs instead of continuing the load to completion.
632 ASSERT(!m_resource->errorOccurred() || m_resource->status() == CachedResource::DecodeError || !m_resource->isLoading());
633 LOG(ResourceLoading, "Received '%s'.", m_resource->url().string().latin1().data());
634 logResourceLoaded(m_frame.get(), m_resource->type());
635
636 Ref<SubresourceLoader> protectedThis(*this);
637 CachedResourceHandle<CachedResource> protectResource(m_resource);
638
639 // FIXME: Remove this with deprecatedNetworkLoadMetrics.
640 m_loadTiming.setResponseEnd(MonotonicTime::now());
641
642 if (networkLoadMetrics.isComplete())
643 reportResourceTiming(networkLoadMetrics);
644 else {
645 // This is the legacy path for platforms (and ResourceHandle paths) that do not provide
646 // complete load metrics in didFinishLoad. In those cases, fall back to the possibility
647 // that they populated partial load timing information on the ResourceResponse.
648 reportResourceTiming(m_resource->response().deprecatedNetworkLoadMetrics());
649 }
650
651 if (m_resource->type() != CachedResource::Type::MainResource)
652 tracePoint(SubresourceLoadDidEnd);
653
654 m_state = Finishing;
655 m_resource->finishLoading(resourceData());
656
657 if (wasCancelled()) {
658 RELEASE_LOG_IF_ALLOWED("didFinishLoading: was canceled (frame = %p, frameLoader = %p, resourceID = %lu)", frame(), frameLoader(), identifier());
659 return;
660 }
661
662 m_resource->finish();
663 ASSERT(!reachedTerminalState());
664 didFinishLoadingOnePart(networkLoadMetrics);
665 notifyDone(LoadCompletionType::Finish);
666
667 if (reachedTerminalState()) {
668 RELEASE_LOG_IF_ALLOWED("didFinishLoading: reached terminal state (frame = %p, frameLoader = %p, resourceID = %lu)", frame(), frameLoader(), identifier());
669 return;
670 }
671 releaseResources();
672}
673
674void SubresourceLoader::didFail(const ResourceError& error)
675{
676 RELEASE_LOG_IF_ALLOWED("didFail: (frame = %p, frameLoader = %p, resourceID = %lu, type = %d, code = %d)", frame(), frameLoader(), identifier(), static_cast<int>(error.type()), error.errorCode());
677
678#if USE(QUICK_LOOK)
679 if (auto previewLoader = m_previewLoader.get())
680 previewLoader->didFail();
681#endif
682
683 if (m_state != Initialized)
684 return;
685
686 ASSERT(!reachedTerminalState());
687 LOG(ResourceLoading, "Failed to load '%s'.\n", m_resource->url().string().latin1().data());
688
689 if (m_frame->document() && error.isAccessControl() && m_resource->type() != CachedResource::Type::Ping)
690 m_frame->document()->addConsoleMessage(MessageSource::Security, MessageLevel::Error, error.localizedDescription());
691
692 Ref<SubresourceLoader> protectedThis(*this);
693 CachedResourceHandle<CachedResource> protectResource(m_resource);
694 m_state = Finishing;
695
696 if (m_resource->type() != CachedResource::Type::MainResource)
697 tracePoint(SubresourceLoadDidEnd);
698
699 if (m_resource->resourceToRevalidate())
700 MemoryCache::singleton().revalidationFailed(*m_resource);
701 m_resource->setResourceError(error);
702 if (!m_resource->isPreloaded())
703 MemoryCache::singleton().remove(*m_resource);
704 m_resource->error(CachedResource::LoadError);
705 cleanupForError(error);
706 notifyDone(LoadCompletionType::Cancel);
707 if (reachedTerminalState())
708 return;
709 releaseResources();
710}
711
712void SubresourceLoader::willCancel(const ResourceError& error)
713{
714 RELEASE_LOG_IF_ALLOWED("willCancel: (frame = %p, frameLoader = %p, resourceID = %lu, type = %d, code = %d)", frame(), frameLoader(), identifier(), static_cast<int>(error.type()), error.errorCode());
715
716#if PLATFORM(IOS_FAMILY)
717 // Since we defer initialization to scheduling time on iOS but
718 // CachedResourceLoader stores resources in the memory cache immediately,
719 // m_resource might be cached despite its loader not being initialized.
720 if (m_state != Initialized && m_state != Uninitialized)
721#else
722 if (m_state != Initialized)
723#endif
724 return;
725
726 ASSERT(!reachedTerminalState());
727 LOG(ResourceLoading, "Cancelled load of '%s'.\n", m_resource->url().string().latin1().data());
728
729 Ref<SubresourceLoader> protectedThis(*this);
730#if PLATFORM(IOS_FAMILY)
731 m_state = m_state == Uninitialized ? CancelledWhileInitializing : Finishing;
732#else
733 m_state = Finishing;
734#endif
735 auto& memoryCache = MemoryCache::singleton();
736 if (m_resource->resourceToRevalidate())
737 memoryCache.revalidationFailed(*m_resource);
738 m_resource->setResourceError(error);
739 memoryCache.remove(*m_resource);
740}
741
742void SubresourceLoader::didCancel(const ResourceError&)
743{
744 if (m_state == Uninitialized)
745 return;
746
747 if (m_resource->type() != CachedResource::Type::MainResource)
748 tracePoint(SubresourceLoadDidEnd);
749
750 m_resource->cancelLoad();
751 notifyDone(LoadCompletionType::Cancel);
752}
753
754void SubresourceLoader::notifyDone(LoadCompletionType type)
755{
756 if (reachedTerminalState())
757 return;
758
759 m_requestCountTracker = WTF::nullopt;
760 bool shouldPerformPostLoadActions = true;
761#if PLATFORM(IOS_FAMILY)
762 if (m_state == CancelledWhileInitializing)
763 shouldPerformPostLoadActions = false;
764#endif
765 m_documentLoader->cachedResourceLoader().loadDone(type, shouldPerformPostLoadActions);
766 if (reachedTerminalState())
767 return;
768 m_documentLoader->removeSubresourceLoader(type, this);
769}
770
771void SubresourceLoader::releaseResources()
772{
773 ASSERT(!reachedTerminalState());
774#if PLATFORM(IOS_FAMILY)
775 if (m_state != Uninitialized && m_state != CancelledWhileInitializing)
776#else
777 if (m_state != Uninitialized)
778#endif
779 m_resource->clearLoader();
780 m_resource = nullptr;
781 ResourceLoader::releaseResources();
782}
783
784void SubresourceLoader::reportResourceTiming(const NetworkLoadMetrics& networkLoadMetrics)
785{
786 if (!RuntimeEnabledFeatures::sharedFeatures().resourceTimingEnabled())
787 return;
788
789 if (!ResourceTimingInformation::shouldAddResourceTiming(*m_resource))
790 return;
791
792 Document* document = m_documentLoader->cachedResourceLoader().document();
793 if (!document)
794 return;
795
796 SecurityOrigin& origin = m_origin ? *m_origin : document->securityOrigin();
797 auto resourceTiming = ResourceTiming::fromLoad(*m_resource, m_resource->initiatorName(), m_loadTiming, networkLoadMetrics, origin);
798
799 // Worker resources loaded here are all CachedRawResources loaded through WorkerThreadableLoader.
800 // Pass the ResourceTiming information on so that WorkerThreadableLoader may add them to the
801 // Worker's Performance object.
802 if (options().initiatorContext == InitiatorContext::Worker) {
803 ASSERT(m_origin);
804 ASSERT(is<CachedRawResource>(m_resource));
805 downcast<CachedRawResource>(*m_resource).finishedTimingForWorkerLoad(WTFMove(resourceTiming));
806 return;
807 }
808
809 ASSERT(options().initiatorContext == InitiatorContext::Document);
810 m_documentLoader->cachedResourceLoader().resourceTimingInformation().addResourceTiming(*m_resource, *document, WTFMove(resourceTiming));
811}
812
813const HTTPHeaderMap* SubresourceLoader::originalHeaders() const
814{
815 return (m_resource && m_resource->originalRequest()) ? &m_resource->originalRequest()->httpHeaderFields() : nullptr;
816}
817
818}
819
820#undef RELEASE_LOG_IF_ALLOWED
821#undef RELEASE_LOG_ERROR_IF_ALLOWED
822