1/*
2 Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de)
3 Copyright (C) 2001 Dirk Mueller (mueller@kde.org)
4 Copyright (C) 2002 Waldo Bastian (bastian@kde.org)
5 Copyright (C) 2004-2017 Apple Inc. All rights reserved.
6 Copyright (C) 2009 Torch Mobile Inc. http://www.torchmobile.com/
7
8 This library is free software; you can redistribute it and/or
9 modify it under the terms of the GNU Library General Public
10 License as published by the Free Software Foundation; either
11 version 2 of the License, or (at your option) any later version.
12
13 This library is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Library General Public License for more details.
17
18 You should have received a copy of the GNU Library General Public License
19 along with this library; see the file COPYING.LIB. If not, write to
20 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 Boston, MA 02110-1301, USA.
22
23 This class provides all functionality needed for loading images, style sheets and html
24 pages from the web. It has a memory cache for these objects.
25*/
26
27#include "config.h"
28#include "CachedResourceLoader.h"
29
30#include "CachedCSSStyleSheet.h"
31#include "CachedFont.h"
32#include "CachedImage.h"
33#include "CachedRawResource.h"
34#include "CachedResourceRequest.h"
35#include "CachedSVGDocument.h"
36#include "CachedSVGFont.h"
37#include "CachedScript.h"
38#include "CachedXSLStyleSheet.h"
39#include "Chrome.h"
40#include "ChromeClient.h"
41#include "ContentExtensionError.h"
42#include "ContentExtensionRule.h"
43#include "ContentRuleListResults.h"
44#include "ContentSecurityPolicy.h"
45#include "CrossOriginAccessControl.h"
46#include "CustomHeaderFields.h"
47#include "DOMWindow.h"
48#include "DiagnosticLoggingClient.h"
49#include "DiagnosticLoggingKeys.h"
50#include "Document.h"
51#include "DocumentLoader.h"
52#include "Frame.h"
53#include "FrameLoader.h"
54#include "FrameLoaderClient.h"
55#include "HTMLElement.h"
56#include "HTMLFrameOwnerElement.h"
57#include "HTTPHeaderField.h"
58#include "LoaderStrategy.h"
59#include "LocalizedStrings.h"
60#include "Logging.h"
61#include "MemoryCache.h"
62#include "Page.h"
63#include "PingLoader.h"
64#include "PlatformStrategies.h"
65#include "RenderElement.h"
66#include "ResourceLoadInfo.h"
67#include "ResourceTiming.h"
68#include "RuntimeEnabledFeatures.h"
69#include "ScriptController.h"
70#include "SecurityOrigin.h"
71#include "SecurityPolicy.h"
72#include "ServiceWorker.h"
73#include "Settings.h"
74#include "StyleSheetContents.h"
75#include "SubresourceLoader.h"
76#include "UserContentController.h"
77#include "UserStyleSheet.h"
78#include <pal/SessionID.h>
79#include <wtf/text/CString.h>
80#include <wtf/text/WTFString.h>
81
82#if ENABLE(APPLICATION_MANIFEST)
83#include "CachedApplicationManifest.h"
84#endif
85
86#if ENABLE(VIDEO_TRACK)
87#include "CachedTextTrack.h"
88#endif
89
90#undef RELEASE_LOG_IF_ALLOWED
91#define RELEASE_LOG_IF_ALLOWED(fmt, ...) RELEASE_LOG_IF(isAlwaysOnLoggingAllowed(), Network, "%p - CachedResourceLoader::" fmt, this, ##__VA_ARGS__)
92
93namespace WebCore {
94
95// Timeout for link preloads to be used after window.onload
96static const Seconds unusedPreloadTimeout { 3_s };
97
98template <typename T, typename U>
99static inline ResourceErrorOr<CachedResourceHandle<T>> castCachedResourceTo(ResourceErrorOr<CachedResourceHandle<U>>&& cachedResource)
100{
101 if (cachedResource)
102 return CachedResourceHandle<T> { static_cast<T*>(cachedResource.value().get()) };
103 return makeUnexpected(cachedResource.error());
104}
105
106static CachedResource* createResource(CachedResource::Type type, CachedResourceRequest&& request, const PAL::SessionID& sessionID, const CookieJar* cookieJar)
107{
108 switch (type) {
109 case CachedResource::Type::ImageResource:
110 return new CachedImage(WTFMove(request), sessionID, cookieJar);
111 case CachedResource::Type::CSSStyleSheet:
112 return new CachedCSSStyleSheet(WTFMove(request), sessionID, cookieJar);
113 case CachedResource::Type::Script:
114 return new CachedScript(WTFMove(request), sessionID, cookieJar);
115 case CachedResource::Type::SVGDocumentResource:
116 return new CachedSVGDocument(WTFMove(request), sessionID, cookieJar);
117#if ENABLE(SVG_FONTS)
118 case CachedResource::Type::SVGFontResource:
119 return new CachedSVGFont(WTFMove(request), sessionID, cookieJar);
120#endif
121 case CachedResource::Type::FontResource:
122 return new CachedFont(WTFMove(request), sessionID, cookieJar);
123 case CachedResource::Type::Beacon:
124 case CachedResource::Type::Ping:
125 case CachedResource::Type::MediaResource:
126 case CachedResource::Type::RawResource:
127 case CachedResource::Type::Icon:
128 case CachedResource::Type::MainResource:
129 return new CachedRawResource(WTFMove(request), type, sessionID, cookieJar);
130#if ENABLE(XSLT)
131 case CachedResource::Type::XSLStyleSheet:
132 return new CachedXSLStyleSheet(WTFMove(request), sessionID, cookieJar);
133#endif
134 case CachedResource::Type::LinkPrefetch:
135 return new CachedResource(WTFMove(request), CachedResource::Type::LinkPrefetch, sessionID, cookieJar);
136#if ENABLE(VIDEO_TRACK)
137 case CachedResource::Type::TextTrackResource:
138 return new CachedTextTrack(WTFMove(request), sessionID, cookieJar);
139#endif
140#if ENABLE(APPLICATION_MANIFEST)
141 case CachedResource::Type::ApplicationManifest:
142 return new CachedApplicationManifest(WTFMove(request), sessionID, cookieJar);
143#endif
144 }
145 ASSERT_NOT_REACHED();
146 return nullptr;
147}
148
149CachedResourceLoader::CachedResourceLoader(DocumentLoader* documentLoader)
150 : m_document(nullptr)
151 , m_documentLoader(documentLoader)
152 , m_requestCount(0)
153 , m_unusedPreloadsTimer(*this, &CachedResourceLoader::warnUnusedPreloads)
154 , m_garbageCollectDocumentResourcesTimer(*this, &CachedResourceLoader::garbageCollectDocumentResources)
155 , m_autoLoadImages(true)
156 , m_imagesEnabled(true)
157 , m_allowStaleResources(false)
158{
159}
160
161CachedResourceLoader::~CachedResourceLoader()
162{
163 m_documentLoader = nullptr;
164 m_document = nullptr;
165
166 clearPreloads(ClearPreloadsMode::ClearAllPreloads);
167
168 // Make sure no requests still point to this CachedResourceLoader
169 ASSERT(m_requestCount == 0);
170 m_unusedPreloadsTimer.stop();
171}
172
173CachedResource* CachedResourceLoader::cachedResource(const String& resourceURL) const
174{
175 ASSERT(!resourceURL.isNull());
176 return cachedResource(MemoryCache::removeFragmentIdentifierIfNeeded(m_document->completeURL(resourceURL)));
177}
178
179CachedResource* CachedResourceLoader::cachedResource(const URL& url) const
180{
181 ASSERT(!MemoryCache::shouldRemoveFragmentIdentifier(url));
182 return m_documentResources.get(url).get();
183}
184
185Frame* CachedResourceLoader::frame() const
186{
187 return m_documentLoader ? m_documentLoader->frame() : nullptr;
188}
189
190PAL::SessionID CachedResourceLoader::sessionID() const
191{
192 auto sessionID = PAL::SessionID::defaultSessionID();
193 if (auto* frame = this->frame()) {
194 if (auto* page = frame->page())
195 sessionID = page->sessionID();
196 }
197 return sessionID;
198}
199
200ResourceErrorOr<CachedResourceHandle<CachedImage>> CachedResourceLoader::requestImage(CachedResourceRequest&& request)
201{
202 if (Frame* frame = this->frame()) {
203 if (frame->loader().pageDismissalEventBeingDispatched() != FrameLoader::PageDismissalType::None) {
204 if (Document* document = frame->document())
205 request.upgradeInsecureRequestIfNeeded(*document);
206 URL requestURL = request.resourceRequest().url();
207 if (requestURL.isValid() && canRequest(CachedResource::Type::ImageResource, requestURL, request, ForPreload::No))
208 PingLoader::loadImage(*frame, requestURL);
209 return CachedResourceHandle<CachedImage> { };
210 }
211 }
212
213 auto defer = clientDefersImage(request.resourceRequest().url()) ? DeferOption::DeferredByClient : DeferOption::NoDefer;
214 return castCachedResourceTo<CachedImage>(requestResource(CachedResource::Type::ImageResource, WTFMove(request), ForPreload::No, defer));
215}
216
217ResourceErrorOr<CachedResourceHandle<CachedFont>> CachedResourceLoader::requestFont(CachedResourceRequest&& request, bool isSVG)
218{
219#if ENABLE(SVG_FONTS)
220 if (isSVG)
221 return castCachedResourceTo<CachedFont>(requestResource(CachedResource::Type::SVGFontResource, WTFMove(request)));
222#else
223 UNUSED_PARAM(isSVG);
224#endif
225 return castCachedResourceTo<CachedFont>(requestResource(CachedResource::Type::FontResource, WTFMove(request)));
226}
227
228#if ENABLE(VIDEO_TRACK)
229ResourceErrorOr<CachedResourceHandle<CachedTextTrack>> CachedResourceLoader::requestTextTrack(CachedResourceRequest&& request)
230{
231 return castCachedResourceTo<CachedTextTrack>(requestResource(CachedResource::Type::TextTrackResource, WTFMove(request)));
232}
233#endif
234
235ResourceErrorOr<CachedResourceHandle<CachedCSSStyleSheet>> CachedResourceLoader::requestCSSStyleSheet(CachedResourceRequest&& request)
236{
237 return castCachedResourceTo<CachedCSSStyleSheet>(requestResource(CachedResource::Type::CSSStyleSheet, WTFMove(request)));
238}
239
240CachedResourceHandle<CachedCSSStyleSheet> CachedResourceLoader::requestUserCSSStyleSheet(Page& page, CachedResourceRequest&& request)
241{
242 request.setDestinationIfNotSet(FetchOptions::Destination::Style);
243
244 ASSERT(document());
245 request.setDomainForCachePartition(*document());
246
247 auto& memoryCache = MemoryCache::singleton();
248 if (request.allowsCaching()) {
249 if (CachedResource* existing = memoryCache.resourceForRequest(request.resourceRequest(), sessionID())) {
250 if (is<CachedCSSStyleSheet>(*existing))
251 return downcast<CachedCSSStyleSheet>(existing);
252 memoryCache.remove(*existing);
253 }
254 }
255
256 request.removeFragmentIdentifierIfNeeded();
257
258 CachedResourceHandle<CachedCSSStyleSheet> userSheet = new CachedCSSStyleSheet(WTFMove(request), page.sessionID(), &page.cookieJar());
259
260 if (userSheet->allowsCaching())
261 memoryCache.add(*userSheet);
262
263 userSheet->load(*this);
264 return userSheet;
265}
266
267ResourceErrorOr<CachedResourceHandle<CachedScript>> CachedResourceLoader::requestScript(CachedResourceRequest&& request)
268{
269 return castCachedResourceTo<CachedScript>(requestResource(CachedResource::Type::Script, WTFMove(request)));
270}
271
272#if ENABLE(XSLT)
273ResourceErrorOr<CachedResourceHandle<CachedXSLStyleSheet>> CachedResourceLoader::requestXSLStyleSheet(CachedResourceRequest&& request)
274{
275 return castCachedResourceTo<CachedXSLStyleSheet>(requestResource(CachedResource::Type::XSLStyleSheet, WTFMove(request)));
276}
277#endif
278
279ResourceErrorOr<CachedResourceHandle<CachedSVGDocument>> CachedResourceLoader::requestSVGDocument(CachedResourceRequest&& request)
280{
281 return castCachedResourceTo<CachedSVGDocument>(requestResource(CachedResource::Type::SVGDocumentResource, WTFMove(request)));
282}
283
284ResourceErrorOr<CachedResourceHandle<CachedResource>> CachedResourceLoader::requestLinkResource(CachedResource::Type type, CachedResourceRequest&& request)
285{
286 ASSERT(frame());
287 ASSERT(type == CachedResource::Type::LinkPrefetch);
288 return requestResource(type, WTFMove(request));
289}
290
291ResourceErrorOr<CachedResourceHandle<CachedRawResource>> CachedResourceLoader::requestMedia(CachedResourceRequest&& request)
292{
293 // FIXME: Assert request.options().destination is FetchOptions::Destination::{Audio, Video}.
294 return castCachedResourceTo<CachedRawResource>(requestResource(CachedResource::Type::MediaResource, WTFMove(request)));
295}
296
297ResourceErrorOr<CachedResourceHandle<CachedRawResource>> CachedResourceLoader::requestIcon(CachedResourceRequest&& request)
298{
299 return castCachedResourceTo<CachedRawResource>(requestResource(CachedResource::Type::Icon, WTFMove(request)));
300}
301
302ResourceErrorOr<CachedResourceHandle<CachedRawResource>> CachedResourceLoader::requestRawResource(CachedResourceRequest&& request)
303{
304 return castCachedResourceTo<CachedRawResource>(requestResource(CachedResource::Type::RawResource, WTFMove(request)));
305}
306
307ResourceErrorOr<CachedResourceHandle<CachedRawResource>> CachedResourceLoader::requestBeaconResource(CachedResourceRequest&& request)
308{
309 ASSERT(request.options().destination == FetchOptions::Destination::EmptyString);
310 return castCachedResourceTo<CachedRawResource>(requestResource(CachedResource::Type::Beacon, WTFMove(request)));
311}
312
313ResourceErrorOr<CachedResourceHandle<CachedRawResource>> CachedResourceLoader::requestPingResource(CachedResourceRequest&& request)
314{
315 ASSERT(request.options().destination == FetchOptions::Destination::EmptyString);
316 return castCachedResourceTo<CachedRawResource>(requestResource(CachedResource::Type::Ping, WTFMove(request)));
317}
318
319ResourceErrorOr<CachedResourceHandle<CachedRawResource>> CachedResourceLoader::requestMainResource(CachedResourceRequest&& request)
320{
321 return castCachedResourceTo<CachedRawResource>(requestResource(CachedResource::Type::MainResource, WTFMove(request)));
322}
323
324#if ENABLE(APPLICATION_MANIFEST)
325ResourceErrorOr<CachedResourceHandle<CachedApplicationManifest>> CachedResourceLoader::requestApplicationManifest(CachedResourceRequest&& request)
326{
327 return castCachedResourceTo<CachedApplicationManifest>(requestResource(CachedResource::Type::ApplicationManifest, WTFMove(request)));
328}
329#endif // ENABLE(APPLICATION_MANIFEST)
330
331static MixedContentChecker::ContentType contentTypeFromResourceType(CachedResource::Type type)
332{
333 switch (type) {
334 // https://w3c.github.io/webappsec-mixed-content/#category-optionally-blockable
335 // Editor's Draft, 11 February 2016
336 // 3.1. Optionally-blockable Content
337 case CachedResource::Type::ImageResource:
338 case CachedResource::Type::MediaResource:
339 return MixedContentChecker::ContentType::ActiveCanWarn;
340
341 case CachedResource::Type::CSSStyleSheet:
342 case CachedResource::Type::Script:
343 case CachedResource::Type::FontResource:
344 return MixedContentChecker::ContentType::Active;
345
346#if ENABLE(SVG_FONTS)
347 case CachedResource::Type::SVGFontResource:
348 return MixedContentChecker::ContentType::Active;
349#endif
350
351 case CachedResource::Type::Beacon:
352 case CachedResource::Type::Ping:
353 case CachedResource::Type::RawResource:
354 case CachedResource::Type::Icon:
355 case CachedResource::Type::SVGDocumentResource:
356 return MixedContentChecker::ContentType::Active;
357#if ENABLE(XSLT)
358 case CachedResource::Type::XSLStyleSheet:
359 return MixedContentChecker::ContentType::Active;
360#endif
361
362 case CachedResource::Type::LinkPrefetch:
363 return MixedContentChecker::ContentType::Active;
364
365#if ENABLE(VIDEO_TRACK)
366 case CachedResource::Type::TextTrackResource:
367 return MixedContentChecker::ContentType::Active;
368#endif
369#if ENABLE(APPLICATION_MANIFEST)
370 case CachedResource::Type::ApplicationManifest:
371 return MixedContentChecker::ContentType::Active;
372#endif
373 default:
374 ASSERT_NOT_REACHED();
375 return MixedContentChecker::ContentType::Active;
376 }
377}
378
379bool CachedResourceLoader::checkInsecureContent(CachedResource::Type type, const URL& url) const
380{
381 if (!canRequestInContentDispositionAttachmentSandbox(type, url))
382 return false;
383
384 switch (type) {
385 case CachedResource::Type::Script:
386#if ENABLE(XSLT)
387 case CachedResource::Type::XSLStyleSheet:
388#endif
389 case CachedResource::Type::SVGDocumentResource:
390 case CachedResource::Type::CSSStyleSheet:
391 // These resource can inject script into the current document (Script,
392 // XSL) or exfiltrate the content of the current document (CSS).
393 if (Frame* frame = this->frame()) {
394 if (!frame->loader().mixedContentChecker().canRunInsecureContent(m_document->securityOrigin(), url))
395 return false;
396 Frame& top = frame->tree().top();
397 if (&top != frame && !top.loader().mixedContentChecker().canRunInsecureContent(top.document()->securityOrigin(), url))
398 return false;
399 }
400 break;
401#if ENABLE(VIDEO_TRACK)
402 case CachedResource::Type::TextTrackResource:
403#endif
404 case CachedResource::Type::MediaResource:
405 case CachedResource::Type::RawResource:
406 case CachedResource::Type::Icon:
407 case CachedResource::Type::ImageResource:
408#if ENABLE(SVG_FONTS)
409 case CachedResource::Type::SVGFontResource:
410#endif
411 case CachedResource::Type::FontResource: {
412 // These resources can corrupt only the frame's pixels.
413 if (Frame* frame = this->frame()) {
414 if (!frame->loader().mixedContentChecker().canDisplayInsecureContent(m_document->securityOrigin(), contentTypeFromResourceType(type), url, MixedContentChecker::AlwaysDisplayInNonStrictMode::Yes))
415 return false;
416 Frame& topFrame = frame->tree().top();
417 if (!topFrame.loader().mixedContentChecker().canDisplayInsecureContent(topFrame.document()->securityOrigin(), contentTypeFromResourceType(type), url))
418 return false;
419 }
420 break;
421 }
422 case CachedResource::Type::MainResource:
423 case CachedResource::Type::Beacon:
424 case CachedResource::Type::Ping:
425 case CachedResource::Type::LinkPrefetch:
426 // Prefetch cannot affect the current document.
427#if ENABLE(APPLICATION_MANIFEST)
428 case CachedResource::Type::ApplicationManifest:
429#endif
430 break;
431 }
432 return true;
433}
434
435bool CachedResourceLoader::allowedByContentSecurityPolicy(CachedResource::Type type, const URL& url, const ResourceLoaderOptions& options, ContentSecurityPolicy::RedirectResponseReceived redirectResponseReceived) const
436{
437 if (options.contentSecurityPolicyImposition == ContentSecurityPolicyImposition::SkipPolicyCheck)
438 return true;
439
440 ASSERT(m_document);
441 ASSERT(m_document->contentSecurityPolicy());
442
443 switch (type) {
444#if ENABLE(XSLT)
445 case CachedResource::Type::XSLStyleSheet:
446#endif
447 case CachedResource::Type::Script:
448 if (!m_document->contentSecurityPolicy()->allowScriptFromSource(url, redirectResponseReceived))
449 return false;
450 break;
451 case CachedResource::Type::CSSStyleSheet:
452 if (!m_document->contentSecurityPolicy()->allowStyleFromSource(url, redirectResponseReceived))
453 return false;
454 break;
455 case CachedResource::Type::SVGDocumentResource:
456 case CachedResource::Type::Icon:
457 case CachedResource::Type::ImageResource:
458 if (!m_document->contentSecurityPolicy()->allowImageFromSource(url, redirectResponseReceived))
459 return false;
460 break;
461#if ENABLE(SVG_FONTS)
462 case CachedResource::Type::SVGFontResource:
463#endif
464 case CachedResource::Type::FontResource:
465 if (!m_document->contentSecurityPolicy()->allowFontFromSource(url, redirectResponseReceived))
466 return false;
467 break;
468 case CachedResource::Type::MediaResource:
469#if ENABLE(VIDEO_TRACK)
470 case CachedResource::Type::TextTrackResource:
471#endif
472 if (!m_document->contentSecurityPolicy()->allowMediaFromSource(url, redirectResponseReceived))
473 return false;
474 break;
475 case CachedResource::Type::Beacon:
476 case CachedResource::Type::Ping:
477 case CachedResource::Type::RawResource:
478 return true;
479#if ENABLE(APPLICATION_MANIFEST)
480 case CachedResource::Type::ApplicationManifest:
481 if (!m_document->contentSecurityPolicy()->allowManifestFromSource(url, redirectResponseReceived))
482 return false;
483 break;
484#endif
485 default:
486 ASSERT_NOT_REACHED();
487 }
488
489 return true;
490}
491
492static inline bool isSameOriginDataURL(const URL& url, const ResourceLoaderOptions& options)
493{
494 // FIXME: Remove same-origin data URL flag since it was removed from fetch spec (https://github.com/whatwg/fetch/issues/381).
495 return url.protocolIsData() && options.sameOriginDataURLFlag == SameOriginDataURLFlag::Set;
496}
497
498// Security checks defined in https://fetch.spec.whatwg.org/#main-fetch step 2 and 5.
499bool CachedResourceLoader::canRequest(CachedResource::Type type, const URL& url, const CachedResourceRequest& request, ForPreload forPreload)
500{
501 auto& options = request.options();
502
503 if (document() && !document()->securityOrigin().canDisplay(url)) {
504 if (forPreload == ForPreload::No)
505 FrameLoader::reportLocalLoadFailed(frame(), url.stringCenterEllipsizedToLength());
506 LOG(ResourceLoading, "CachedResourceLoader::requestResource URL was not allowed by SecurityOrigin::canDisplay");
507 return false;
508 }
509
510 if (options.mode == FetchOptions::Mode::SameOrigin && !m_document->securityOrigin().canRequest(url) && !isSameOriginDataURL(url, options)) {
511 printAccessDeniedMessage(url);
512 return false;
513 }
514
515 if (options.mode == FetchOptions::Mode::NoCors && options.redirect != FetchOptions::Redirect::Follow && type != CachedResource::Type::Ping) {
516 ASSERT(type != CachedResource::Type::MainResource);
517 frame()->document()->addConsoleMessage(MessageSource::Security, MessageLevel::Error, "No-Cors mode requires follow redirect mode"_s);
518 return false;
519 }
520
521 if (!allowedByContentSecurityPolicy(type, url, options, ContentSecurityPolicy::RedirectResponseReceived::No))
522 return false;
523
524 // SVG Images have unique security rules that prevent all subresource requests except for data urls.
525 if (type != CachedResource::Type::MainResource && frame() && frame()->page()) {
526 if (frame()->page()->chrome().client().isSVGImageChromeClient() && !url.protocolIsData())
527 return false;
528 }
529
530 // Last of all, check for insecure content. We do this last so that when folks block insecure content with a CSP policy, they don't get a warning.
531 // They'll still get a warning in the console about CSP blocking the load.
532
533 // FIXME: Should we consider whether the request is for preload here?
534 if (!checkInsecureContent(type, url))
535 return false;
536
537 return true;
538}
539
540// FIXME: Should we find a way to know whether the redirection is for a preload request like we do for CachedResourceLoader::canRequest?
541bool CachedResourceLoader::canRequestAfterRedirection(CachedResource::Type type, const URL& url, const ResourceLoaderOptions& options) const
542{
543 if (document() && !document()->securityOrigin().canDisplay(url)) {
544 FrameLoader::reportLocalLoadFailed(frame(), url.stringCenterEllipsizedToLength());
545 LOG(ResourceLoading, "CachedResourceLoader::requestResource URL was not allowed by SecurityOrigin::canDisplay");
546 return false;
547 }
548
549 // FIXME: According to https://fetch.spec.whatwg.org/#http-redirect-fetch, we should check that the URL is HTTP(s) except if in navigation mode.
550 // But we currently allow at least data URLs to be loaded.
551
552 if (options.mode == FetchOptions::Mode::SameOrigin && !m_document->securityOrigin().canRequest(url)) {
553 printAccessDeniedMessage(url);
554 return false;
555 }
556
557 if (!allowedByContentSecurityPolicy(type, url, options, ContentSecurityPolicy::RedirectResponseReceived::Yes))
558 return false;
559
560 // Last of all, check for insecure content. We do this last so that when folks block insecure content with a CSP policy, they don't get a warning.
561 // They'll still get a warning in the console about CSP blocking the load.
562 if (!checkInsecureContent(type, url))
563 return false;
564
565 return true;
566}
567
568bool CachedResourceLoader::updateRequestAfterRedirection(CachedResource::Type type, ResourceRequest& request, const ResourceLoaderOptions& options)
569{
570 ASSERT(m_documentLoader);
571 if (auto* document = m_documentLoader->cachedResourceLoader().document())
572 upgradeInsecureResourceRequestIfNeeded(request, *document);
573
574 // FIXME: We might want to align the checks done here with the ones done in CachedResourceLoader::requestResource, content extensions blocking in particular.
575
576 return canRequestAfterRedirection(type, request.url(), options);
577}
578
579bool CachedResourceLoader::canRequestInContentDispositionAttachmentSandbox(CachedResource::Type type, const URL& url) const
580{
581 Document* document;
582
583 // FIXME: Do we want to expand this to all resource types that the mixed content checker would consider active content?
584 switch (type) {
585 case CachedResource::Type::MainResource:
586 if (auto ownerElement = frame() ? frame()->ownerElement() : nullptr) {
587 document = &ownerElement->document();
588 break;
589 }
590 return true;
591 case CachedResource::Type::CSSStyleSheet:
592 document = m_document.get();
593 break;
594 default:
595 return true;
596 }
597
598 if (!document->shouldEnforceContentDispositionAttachmentSandbox() || document->securityOrigin().canRequest(url))
599 return true;
600
601 String message = "Unsafe attempt to load URL " + url.stringCenterEllipsizedToLength() + " from document with Content-Disposition: attachment at URL " + document->url().stringCenterEllipsizedToLength() + ".";
602 document->addConsoleMessage(MessageSource::Security, MessageLevel::Error, message);
603 return false;
604}
605
606bool CachedResourceLoader::shouldContinueAfterNotifyingLoadedFromMemoryCache(const CachedResourceRequest& request, CachedResource& resource, ResourceError& error)
607{
608 if (!frame() || resource.status() != CachedResource::Cached)
609 return true;
610
611 ResourceRequest newRequest = ResourceRequest(resource.url());
612 newRequest.setInitiatorIdentifier(request.resourceRequest().initiatorIdentifier());
613 if (auto inspectorInitiatorNodeIdentifier = request.resourceRequest().inspectorInitiatorNodeIdentifier())
614 newRequest.setInspectorInitiatorNodeIdentifier(*inspectorInitiatorNodeIdentifier);
615 if (request.resourceRequest().hiddenFromInspector())
616 newRequest.setHiddenFromInspector(true);
617 frame()->loader().loadedResourceFromMemoryCache(resource, newRequest, error);
618
619 // FIXME <http://webkit.org/b/113251>: If the delegate modifies the request's
620 // URL, it is no longer appropriate to use this CachedResource.
621 return !newRequest.isNull();
622}
623
624bool CachedResourceLoader::shouldUpdateCachedResourceWithCurrentRequest(const CachedResource& resource, const CachedResourceRequest& request)
625{
626 // WebKit is not supporting CORS for fonts (https://bugs.webkit.org/show_bug.cgi?id=86817), no need to update the resource before reusing it.
627 if (resource.type() == CachedResource::Type::FontResource)
628 return false;
629
630#if ENABLE(SVG_FONTS)
631 if (resource.type() == CachedResource::Type::SVGFontResource)
632 return false;
633#endif
634
635#if ENABLE(XSLT)
636 // Load is same-origin, we do not check for CORS.
637 if (resource.type() == CachedResource::Type::XSLStyleSheet)
638 return false;
639#endif
640
641 // FIXME: We should enable resource reuse for these resource types
642 switch (resource.type()) {
643 case CachedResource::Type::SVGDocumentResource:
644 return false;
645 case CachedResource::Type::MainResource:
646 return false;
647 case CachedResource::Type::LinkPrefetch:
648 return false;
649 default:
650 break;
651 }
652
653 if (resource.options().mode != request.options().mode || !serializedOriginsMatch(request.origin(), resource.origin()))
654 return true;
655
656 if (resource.options().redirect != request.options().redirect && resource.hasRedirections())
657 return true;
658
659 return false;
660}
661
662static inline bool isResourceSuitableForDirectReuse(const CachedResource& resource, const CachedResourceRequest& request)
663{
664 // FIXME: For being loaded requests, the response tainting may not be correctly computed if the fetch mode is not the same.
665 // Even if the fetch mode is the same, we are not sure that the resource can be reused (Vary: Origin header for instance).
666 // We should find a way to improve this.
667 if (resource.status() != CachedResource::Cached)
668 return false;
669
670 // If the cached resource has not followed redirections, it is incomplete and we should not use it.
671 // Let's make sure the memory cache has no such resource.
672 ASSERT(resource.response().type() != ResourceResponse::Type::Opaqueredirect);
673
674 // We could support redirect modes other than Follow in case of a redirected resource.
675 // This case is rare and is not worth optimizing currently.
676 if (request.options().redirect != FetchOptions::Redirect::Follow && resource.hasRedirections())
677 return false;
678
679 // FIXME: Implement reuse of cached raw resources.
680 if (resource.type() == CachedResource::Type::RawResource || resource.type() == CachedResource::Type::MediaResource)
681 return false;
682
683 if (resource.type() == CachedResource::Type::Beacon || resource.type() == CachedResource::Type::Ping)
684 return false;
685
686 return true;
687}
688
689CachedResourceHandle<CachedResource> CachedResourceLoader::updateCachedResourceWithCurrentRequest(const CachedResource& resource, CachedResourceRequest&& request, const PAL::SessionID& sessionID, const CookieJar* cookieJar)
690{
691 if (!isResourceSuitableForDirectReuse(resource, request)) {
692 request.setCachingPolicy(CachingPolicy::DisallowCaching);
693 return loadResource(resource.type(), WTFMove(request), cookieJar);
694 }
695
696 auto resourceHandle = createResource(resource.type(), WTFMove(request), sessionID, cookieJar);
697 resourceHandle->loadFrom(resource);
698 return resourceHandle;
699}
700
701static inline void logMemoryCacheResourceRequest(Frame* frame, const String& key, const String& description)
702{
703 if (!frame || !frame->page())
704 return;
705 frame->page()->diagnosticLoggingClient().logDiagnosticMessage(key, description, ShouldSample::Yes);
706}
707
708void CachedResourceLoader::prepareFetch(CachedResource::Type type, CachedResourceRequest& request)
709{
710 // Implementing step 1 to 7 of https://fetch.spec.whatwg.org/#fetching
711 auto* document = this->document();
712
713 if (document) {
714 if (!request.origin())
715 request.setOrigin(document->securityOrigin());
716#if ENABLE(SERVICE_WORKER)
717 request.setClientIdentifierIfNeeded(document->identifier());
718 if (auto* activeServiceWorker = document->activeServiceWorker())
719 request.setSelectedServiceWorkerRegistrationIdentifierIfNeeded(activeServiceWorker->registrationIdentifier());
720#endif
721 }
722
723 request.setAcceptHeaderIfNone(type);
724
725 // Accept-Language value is handled in underlying port-specific code.
726 // FIXME: Decide whether to support client hints
727}
728
729void CachedResourceLoader::updateHTTPRequestHeaders(CachedResource::Type type, CachedResourceRequest& request)
730{
731 // Implementing steps 7 to 12 of https://fetch.spec.whatwg.org/#http-network-or-cache-fetch
732
733 // FIXME: We should reconcile handling of MainResource with other resources.
734 if (type != CachedResource::Type::MainResource) {
735 // In some cases we may try to load resources in frameless documents. Such loads always fail.
736 // FIXME: We shouldn't need to do the check on frame.
737 if (auto* frame = this->frame())
738 request.updateReferrerOriginAndUserAgentHeaders(frame->loader());
739 }
740
741 request.updateAccordingCacheMode();
742 request.updateAcceptEncodingHeader();
743}
744
745static FetchOptions::Destination destinationForType(CachedResource::Type type)
746{
747 switch (type) {
748 case CachedResource::Type::MainResource:
749 case CachedResource::Type::SVGDocumentResource:
750 return FetchOptions::Destination::Document;
751 case CachedResource::Type::ImageResource:
752 case CachedResource::Type::Icon:
753 return FetchOptions::Destination::Image;
754 case CachedResource::Type::CSSStyleSheet:
755 return FetchOptions::Destination::Style;
756 case CachedResource::Type::Script:
757 return FetchOptions::Destination::Script;
758 case CachedResource::Type::FontResource:
759#if ENABLE(SVG_FONTS)
760 case CachedResource::Type::SVGFontResource:
761#endif
762 return FetchOptions::Destination::Font;
763#if ENABLE(XSLT)
764 case CachedResource::Type::XSLStyleSheet:
765 return FetchOptions::Destination::Xslt;
766#endif
767#if ENABLE(VIDEO_TRACK)
768 case CachedResource::Type::TextTrackResource:
769 return FetchOptions::Destination::Track;
770#endif
771#if ENABLE(APPLICATION_MANIFEST)
772 case CachedResource::Type::ApplicationManifest:
773 return FetchOptions::Destination::Manifest;
774#endif
775 case CachedResource::Type::Beacon:
776 case CachedResource::Type::Ping:
777 case CachedResource::Type::LinkPrefetch:
778 case CachedResource::Type::RawResource:
779 case CachedResource::Type::MediaResource:
780 // The caller is responsible for setting the appropriate destination.
781 return FetchOptions::Destination::EmptyString;
782 }
783 ASSERT_NOT_REACHED();
784 return FetchOptions::Destination::EmptyString;
785}
786
787ResourceErrorOr<CachedResourceHandle<CachedResource>> CachedResourceLoader::requestResource(CachedResource::Type type, CachedResourceRequest&& request, ForPreload forPreload, DeferOption defer)
788{
789 request.setDestinationIfNotSet(destinationForType(type));
790
791 // Entry point to https://fetch.spec.whatwg.org/#main-fetch.
792 std::unique_ptr<ResourceRequest> originalRequest;
793 if (CachedResource::shouldUsePingLoad(type) || request.options().destination == FetchOptions::Destination::EmptyString) {
794 originalRequest = std::make_unique<ResourceRequest>(request.resourceRequest());
795 originalRequest->clearHTTPReferrer();
796 originalRequest->clearHTTPOrigin();
797 }
798
799 if (Document* document = this->document())
800 request.upgradeInsecureRequestIfNeeded(*document);
801
802 request.updateReferrerPolicy(document() ? document()->referrerPolicy() : ReferrerPolicy::NoReferrerWhenDowngrade);
803 URL url = request.resourceRequest().url();
804
805 LOG(ResourceLoading, "CachedResourceLoader::requestResource '%.255s', charset '%s', priority=%d, forPreload=%u", url.stringCenterEllipsizedToLength().latin1().data(), request.charset().latin1().data(), request.priority() ? static_cast<int>(request.priority().value()) : -1, forPreload == ForPreload::Yes);
806
807 if (!url.isValid()) {
808 RELEASE_LOG_IF_ALLOWED("requestResource: URL is invalid (frame = %p)", frame());
809 return makeUnexpected(ResourceError { errorDomainWebKitInternal, 0, url, "URL is invalid"_s });
810 }
811
812 prepareFetch(type, request);
813
814 // We are passing url as well as request, as request url may contain a fragment identifier.
815 if (!canRequest(type, url, request, forPreload)) {
816 RELEASE_LOG_IF_ALLOWED("requestResource: Not allowed to request resource (frame = %p)", frame());
817 return makeUnexpected(ResourceError { errorDomainWebKitInternal, 0, url, "Not allowed to request resource"_s, ResourceError::Type::AccessControl });
818 }
819
820#if ENABLE(CONTENT_EXTENSIONS)
821 if (frame() && frame()->page() && m_documentLoader) {
822 const auto& resourceRequest = request.resourceRequest();
823 auto* page = frame()->page();
824 auto results = page->userContentProvider().processContentRuleListsForLoad(resourceRequest.url(), ContentExtensions::toResourceType(type), *m_documentLoader);
825 bool blockedLoad = results.summary.blockedLoad;
826 bool madeHTTPS = results.summary.madeHTTPS;
827 request.applyResults(WTFMove(results), page);
828 if (blockedLoad) {
829 RELEASE_LOG_IF_ALLOWED("requestResource: Resource blocked by content blocker (frame = %p)", frame());
830 if (type == CachedResource::Type::MainResource) {
831 CachedResourceHandle<CachedResource> resource = createResource(type, WTFMove(request), page->sessionID(), &page->cookieJar());
832 ASSERT(resource);
833 resource->error(CachedResource::Status::LoadError);
834 resource->setResourceError(ResourceError(ContentExtensions::WebKitContentBlockerDomain, 0, resourceRequest.url(), WEB_UI_STRING("The URL was blocked by a content blocker", "WebKitErrorBlockedByContentBlocker description")));
835 return resource;
836 }
837 return makeUnexpected(ResourceError { errorDomainWebKitInternal, 0, url, "Resource blocked by content blocker"_s, ResourceError::Type::AccessControl });
838 }
839 if (madeHTTPS
840 && type == CachedResource::Type::MainResource
841 && m_documentLoader->isLoadingMainResource()) {
842 // This is to make sure the correct 'new' URL shows in the location bar.
843 m_documentLoader->frameLoader()->client().dispatchDidChangeProvisionalURL();
844 }
845 url = request.resourceRequest().url(); // The content extension could have changed it from http to https.
846 url = MemoryCache::removeFragmentIdentifierIfNeeded(url); // Might need to remove fragment identifier again.
847 }
848#endif
849
850 if (frame() && m_documentLoader && !m_documentLoader->customHeaderFields().isEmpty()) {
851 bool sameOriginRequest = false;
852 auto requestedOrigin = SecurityOrigin::create(url);
853 if (type == CachedResource::Type::MainResource) {
854 if (frame()->isMainFrame())
855 sameOriginRequest = true;
856 else if (auto* topDocument = frame()->mainFrame().document())
857 sameOriginRequest = topDocument->securityOrigin().isSameSchemeHostPort(requestedOrigin.get());
858 } else if (document()) {
859 sameOriginRequest = document()->topDocument().securityOrigin().isSameSchemeHostPort(requestedOrigin.get())
860 && document()->securityOrigin().isSameSchemeHostPort(requestedOrigin.get());
861 }
862 for (auto& fields : m_documentLoader->customHeaderFields()) {
863 if (sameOriginRequest || fields.thirdPartyDomainsMatch(url)) {
864 for (auto& field : fields.fields)
865 request.resourceRequest().setHTTPHeaderField(field.name(), field.value());
866 }
867 }
868 }
869
870 LoadTiming loadTiming;
871 loadTiming.markStartTimeAndFetchStart();
872 InitiatorContext initiatorContext = request.options().initiatorContext;
873
874 if (request.resourceRequest().url().protocolIsInHTTPFamily())
875 updateHTTPRequestHeaders(type, request);
876
877 auto& memoryCache = MemoryCache::singleton();
878 if (request.allowsCaching() && memoryCache.disabled())
879 m_documentResources.remove(url.string());
880
881 // See if we can use an existing resource from the cache.
882 CachedResourceHandle<CachedResource> resource;
883 if (document())
884 request.setDomainForCachePartition(*document());
885
886 if (request.allowsCaching())
887 resource = memoryCache.resourceForRequest(request.resourceRequest(), sessionID());
888
889 if (resource && request.isLinkPreload() && !resource->isLinkPreload())
890 resource->setLinkPreload();
891
892 logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::memoryCacheUsageKey(), resource ? DiagnosticLoggingKeys::inMemoryCacheKey() : DiagnosticLoggingKeys::notInMemoryCacheKey());
893
894 auto* cookieJar = document() && document()->page() ? &document()->page()->cookieJar() : nullptr;
895
896 RevalidationPolicy policy = determineRevalidationPolicy(type, request, resource.get(), forPreload, defer);
897 switch (policy) {
898 case Reload:
899 memoryCache.remove(*resource);
900 FALLTHROUGH;
901 case Load:
902 if (resource)
903 logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::memoryCacheEntryDecisionKey(), DiagnosticLoggingKeys::unusedKey());
904 resource = loadResource(type, WTFMove(request), cookieJar);
905 break;
906 case Revalidate:
907 if (resource)
908 logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::memoryCacheEntryDecisionKey(), DiagnosticLoggingKeys::revalidatingKey());
909 resource = revalidateResource(WTFMove(request), *resource);
910 break;
911 case Use:
912 ASSERT(resource);
913 if (request.options().mode == FetchOptions::Mode::NoCors) {
914 if (auto error = validateCrossOriginResourcePolicy(*request.origin(), request.resourceRequest().url(), resource->response()))
915 return makeUnexpected(WTFMove(*error));
916 }
917 if (shouldUpdateCachedResourceWithCurrentRequest(*resource, request)) {
918 resource = updateCachedResourceWithCurrentRequest(*resource, WTFMove(request), document()->page()->sessionID(), cookieJar);
919 if (resource->status() != CachedResource::Status::Cached)
920 policy = Load;
921 } else {
922 ResourceError error;
923 if (!shouldContinueAfterNotifyingLoadedFromMemoryCache(request, *resource, error))
924 return makeUnexpected(WTFMove(error));
925 logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::memoryCacheEntryDecisionKey(), DiagnosticLoggingKeys::usedKey());
926 loadTiming.setResponseEnd(MonotonicTime::now());
927
928 memoryCache.resourceAccessed(*resource);
929
930 if (RuntimeEnabledFeatures::sharedFeatures().resourceTimingEnabled() && document() && !resource->isLoading()) {
931 auto resourceTiming = ResourceTiming::fromCache(url, request.initiatorName(), loadTiming, resource->response(), *request.origin());
932 if (initiatorContext == InitiatorContext::Worker) {
933 ASSERT(is<CachedRawResource>(resource.get()));
934 downcast<CachedRawResource>(resource.get())->finishedTimingForWorkerLoad(WTFMove(resourceTiming));
935 } else {
936 ASSERT(initiatorContext == InitiatorContext::Document);
937 m_resourceTimingInfo.storeResourceTimingInitiatorInformation(resource, request.initiatorName(), frame());
938 m_resourceTimingInfo.addResourceTiming(*resource.get(), *document(), WTFMove(resourceTiming));
939 }
940 }
941
942 if (forPreload == ForPreload::No)
943 resource->setLoadPriority(request.priority());
944 }
945 break;
946 }
947 ASSERT(resource);
948 resource->setOriginalRequest(WTFMove(originalRequest));
949
950 if (forPreload == ForPreload::No && resource->loader() && resource->ignoreForRequestCount()) {
951 resource->setIgnoreForRequestCount(false);
952 incrementRequestCount(*resource);
953 }
954
955 if ((policy != Use || resource->stillNeedsLoad()) && defer == DeferOption::NoDefer) {
956 resource->load(*this);
957
958 // We don't support immediate loads, but we do support immediate failure.
959 if (resource->errorOccurred()) {
960 if (resource->allowsCaching() && resource->inCache())
961 memoryCache.remove(*resource);
962
963 auto resourceError = resource->resourceError();
964 // Synchronous cancellations are likely due to access control.
965 if (resourceError.isNull() || resourceError.isCancellation())
966 return makeUnexpected(ResourceError { String(), 0, url, String(), ResourceError::Type::AccessControl });
967 return makeUnexpected(resourceError);
968 }
969 }
970
971 if (document() && !document()->loadEventFinished() && !resource->resourceRequest().url().protocolIsData())
972 m_validatedURLs.add(resource->resourceRequest().url());
973
974 ASSERT(resource->url() == url.string());
975 m_documentResources.set(resource->url(), resource);
976 return resource;
977}
978
979void CachedResourceLoader::documentDidFinishLoadEvent()
980{
981 m_validatedURLs.clear();
982
983 // If m_preloads is not empty here, it's full of link preloads,
984 // as speculative preloads were cleared at DCL.
985 if (m_preloads && m_preloads->size() && !m_unusedPreloadsTimer.isActive())
986 m_unusedPreloadsTimer.startOneShot(unusedPreloadTimeout);
987}
988
989void CachedResourceLoader::stopUnusedPreloadsTimer()
990{
991 m_unusedPreloadsTimer.stop();
992}
993
994CachedResourceHandle<CachedResource> CachedResourceLoader::revalidateResource(CachedResourceRequest&& request, CachedResource& resource)
995{
996 ASSERT(resource.inCache());
997 auto& memoryCache = MemoryCache::singleton();
998 ASSERT(!memoryCache.disabled());
999 ASSERT(resource.canUseCacheValidator());
1000 ASSERT(!resource.resourceToRevalidate());
1001 ASSERT(resource.sessionID() == sessionID());
1002 ASSERT(resource.allowsCaching());
1003
1004 CachedResourceHandle<CachedResource> newResource = createResource(resource.type(), WTFMove(request), resource.sessionID(), resource.cookieJar());
1005
1006 LOG(ResourceLoading, "Resource %p created to revalidate %p", newResource.get(), &resource);
1007 newResource->setResourceToRevalidate(&resource);
1008
1009 memoryCache.remove(resource);
1010 memoryCache.add(*newResource);
1011
1012 if (RuntimeEnabledFeatures::sharedFeatures().resourceTimingEnabled())
1013 m_resourceTimingInfo.storeResourceTimingInitiatorInformation(newResource, newResource->initiatorName(), frame());
1014
1015 return newResource;
1016}
1017
1018CachedResourceHandle<CachedResource> CachedResourceLoader::loadResource(CachedResource::Type type, CachedResourceRequest&& request, const CookieJar* cookieJar)
1019{
1020 auto& memoryCache = MemoryCache::singleton();
1021 ASSERT(!request.allowsCaching() || !memoryCache.resourceForRequest(request.resourceRequest(), sessionID())
1022 || request.resourceRequest().cachePolicy() == ResourceRequestCachePolicy::DoNotUseAnyCache || request.resourceRequest().cachePolicy() == ResourceRequestCachePolicy::ReloadIgnoringCacheData || request.resourceRequest().cachePolicy() == ResourceRequestCachePolicy::RefreshAnyCacheData);
1023
1024 LOG(ResourceLoading, "Loading CachedResource for '%s'.", request.resourceRequest().url().stringCenterEllipsizedToLength().latin1().data());
1025
1026 CachedResourceHandle<CachedResource> resource = createResource(type, WTFMove(request), sessionID(), cookieJar);
1027
1028 if (resource->allowsCaching())
1029 memoryCache.add(*resource);
1030
1031 if (RuntimeEnabledFeatures::sharedFeatures().resourceTimingEnabled())
1032 m_resourceTimingInfo.storeResourceTimingInitiatorInformation(resource, resource->initiatorName(), frame());
1033
1034 return resource;
1035}
1036
1037static void logRevalidation(const String& reason, DiagnosticLoggingClient& logClient)
1038{
1039 logClient.logDiagnosticMessage(DiagnosticLoggingKeys::cachedResourceRevalidationReasonKey(), reason, ShouldSample::Yes);
1040}
1041
1042static void logResourceRevalidationDecision(CachedResource::RevalidationDecision reason, const Frame* frame)
1043{
1044 if (!frame || !frame->page())
1045 return;
1046 auto& logClient = frame->page()->diagnosticLoggingClient();
1047 switch (reason) {
1048 case CachedResource::RevalidationDecision::No:
1049 break;
1050 case CachedResource::RevalidationDecision::YesDueToExpired:
1051 logRevalidation(DiagnosticLoggingKeys::isExpiredKey(), logClient);
1052 break;
1053 case CachedResource::RevalidationDecision::YesDueToNoStore:
1054 logRevalidation(DiagnosticLoggingKeys::noStoreKey(), logClient);
1055 break;
1056 case CachedResource::RevalidationDecision::YesDueToNoCache:
1057 logRevalidation(DiagnosticLoggingKeys::noCacheKey(), logClient);
1058 break;
1059 case CachedResource::RevalidationDecision::YesDueToCachePolicy:
1060 logRevalidation(DiagnosticLoggingKeys::reloadKey(), logClient);
1061 break;
1062 }
1063}
1064
1065CachedResourceLoader::RevalidationPolicy CachedResourceLoader::determineRevalidationPolicy(CachedResource::Type type, CachedResourceRequest& cachedResourceRequest, CachedResource* existingResource, ForPreload forPreload, DeferOption defer) const
1066{
1067 auto& request = cachedResourceRequest.resourceRequest();
1068
1069 if (!existingResource)
1070 return Load;
1071
1072 if (request.cachePolicy() == ResourceRequestCachePolicy::DoNotUseAnyCache || request.cachePolicy() == ResourceRequestCachePolicy::ReloadIgnoringCacheData)
1073 return Load;
1074
1075 if (request.cachePolicy() == ResourceRequestCachePolicy::RefreshAnyCacheData)
1076 return Reload;
1077
1078#if ENABLE(SERVICE_WORKER)
1079 // FIXME: We should validate/specify this behavior.
1080 if (cachedResourceRequest.options().serviceWorkerRegistrationIdentifier != existingResource->options().serviceWorkerRegistrationIdentifier) {
1081 LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading because selected service worker differs");
1082 return Reload;
1083 }
1084#endif
1085
1086 // We already have a preload going for this URL.
1087 if (forPreload == ForPreload::Yes && existingResource->isPreloaded())
1088 return Use;
1089
1090 // If the same URL has been loaded as a different type, we need to reload.
1091 if (existingResource->type() != type) {
1092 LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to type mismatch.");
1093 logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::inMemoryCacheKey(), DiagnosticLoggingKeys::unusedReasonTypeMismatchKey());
1094 return Reload;
1095 }
1096
1097 if (!existingResource->varyHeaderValuesMatch(request))
1098 return Reload;
1099
1100 auto* textDecoder = existingResource->textResourceDecoder();
1101 if (textDecoder && !textDecoder->hasEqualEncodingForCharset(cachedResourceRequest.charset())) {
1102 if (!existingResource->hasUnknownEncoding())
1103 return Reload;
1104 existingResource->setHasUnknownEncoding(false);
1105 existingResource->setEncoding(cachedResourceRequest.charset());
1106 }
1107
1108 // FIXME: We should use the same cache policy for all resource types. The raw resource policy is overly strict
1109 // while the normal subresource policy is too loose.
1110 if (existingResource->isMainOrMediaOrIconOrRawResource() && frame()) {
1111 bool strictPolicyDisabled = frame()->loader().isStrictRawResourceValidationPolicyDisabledForTesting();
1112 bool canReuseRawResource = strictPolicyDisabled || downcast<CachedRawResource>(*existingResource).canReuse(request);
1113 if (!canReuseRawResource)
1114 return Reload;
1115 }
1116
1117 // Conditional requests should have failed canReuse check.
1118 ASSERT(!request.isConditional());
1119
1120 // Do not load from cache if images are not enabled. The load for this image will be blocked in CachedImage::load.
1121 if (defer == DeferOption::DeferredByClient)
1122 return Reload;
1123
1124 // Don't reload resources while pasting or if cache mode allows stale resources.
1125 if (m_allowStaleResources || cachedResourceRequest.options().cache == FetchOptions::Cache::ForceCache || cachedResourceRequest.options().cache == FetchOptions::Cache::OnlyIfCached)
1126 return Use;
1127
1128 ASSERT(cachedResourceRequest.options().cache == FetchOptions::Cache::Default || cachedResourceRequest.options().cache == FetchOptions::Cache::NoCache);
1129
1130 // Always use preloads.
1131 if (existingResource->isPreloaded())
1132 return Use;
1133
1134 // We can find resources that are being validated from cache only when validation is just successfully completing.
1135 if (existingResource->validationCompleting())
1136 return Use;
1137 ASSERT(!existingResource->validationInProgress());
1138
1139 auto cachePolicy = this->cachePolicy(type, request.url());
1140
1141 // Validate the redirect chain.
1142 bool cachePolicyIsHistoryBuffer = cachePolicy == CachePolicyHistoryBuffer;
1143 if (!existingResource->redirectChainAllowsReuse(cachePolicyIsHistoryBuffer ? ReuseExpiredRedirection : DoNotReuseExpiredRedirection)) {
1144 LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to not cached or expired redirections.");
1145 logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::inMemoryCacheKey(), DiagnosticLoggingKeys::unusedReasonRedirectChainKey());
1146 return Reload;
1147 }
1148
1149 // CachePolicyHistoryBuffer uses the cache except if this is a main resource with "cache-control: no-store".
1150 if (cachePolicyIsHistoryBuffer) {
1151 // FIXME: Ignoring "cache-control: no-cache" for sub-resources on history navigation but not the main
1152 // resource is inconsistent. We should probably harmonize this.
1153 if (!existingResource->response().cacheControlContainsNoStore() || type != CachedResource::Type::MainResource)
1154 return Use;
1155 }
1156
1157 // Don't reuse resources with Cache-control: no-store.
1158 if (existingResource->response().cacheControlContainsNoStore()) {
1159 LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to Cache-control: no-store.");
1160 logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::inMemoryCacheKey(), DiagnosticLoggingKeys::unusedReasonNoStoreKey());
1161 return Reload;
1162 }
1163
1164 // If credentials were sent with the previous request and won't be
1165 // with this one, or vice versa, re-fetch the resource.
1166 //
1167 // This helps with the case where the server sends back
1168 // "Access-Control-Allow-Origin: *" all the time, but some of the
1169 // client's requests are made without CORS and some with.
1170 if (existingResource->resourceRequest().allowCookies() != request.allowCookies() || existingResource->options().credentials != cachedResourceRequest.options().credentials) {
1171 LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to difference in credentials settings.");
1172 logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::inMemoryCacheKey(), DiagnosticLoggingKeys::unusedReasonCredentialSettingsKey());
1173 return Reload;
1174 }
1175
1176 // During the initial load, avoid loading the same resource multiple times for a single document, even if the cache policies would tell us to.
1177 if (document() && !document()->loadEventFinished() && m_validatedURLs.contains(existingResource->url()))
1178 return Use;
1179
1180 // CachePolicyReload always reloads
1181 if (cachePolicy == CachePolicyReload) {
1182 LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to CachePolicyReload.");
1183 logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::inMemoryCacheKey(), DiagnosticLoggingKeys::unusedReasonReloadKey());
1184 return Reload;
1185 }
1186
1187 // We'll try to reload the resource if it failed last time.
1188 if (existingResource->errorOccurred()) {
1189 LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicye reloading due to resource being in the error state");
1190 logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::inMemoryCacheKey(), DiagnosticLoggingKeys::unusedReasonErrorKey());
1191 return Reload;
1192 }
1193
1194 if (existingResource->isLoading()) {
1195 // Do not use cached main resources that are still loading because sharing
1196 // loading CachedResources in this case causes issues with regards to cancellation.
1197 // If one of the DocumentLoader clients decides to cancel the load, then the load
1198 // would be cancelled for all other DocumentLoaders as well.
1199 if (type == CachedResource::Type::MainResource)
1200 return Reload;
1201 // For cached subresources that are still loading we ignore the cache policy.
1202 return Use;
1203 }
1204
1205 auto revalidationDecision = existingResource->makeRevalidationDecision(cachePolicy);
1206 logResourceRevalidationDecision(revalidationDecision, frame());
1207
1208 // Check if the cache headers requires us to revalidate (cache expiration for example).
1209 if (revalidationDecision != CachedResource::RevalidationDecision::No) {
1210 // See if the resource has usable ETag or Last-modified headers.
1211 if (existingResource->canUseCacheValidator())
1212 return Revalidate;
1213
1214 // No, must reload.
1215 LOG(ResourceLoading, "CachedResourceLoader::determineRevalidationPolicy reloading due to missing cache validators.");
1216 logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::inMemoryCacheKey(), DiagnosticLoggingKeys::unusedReasonMustRevalidateNoValidatorKey());
1217 return Reload;
1218 }
1219
1220 return Use;
1221}
1222
1223void CachedResourceLoader::printAccessDeniedMessage(const URL& url) const
1224{
1225 if (url.isNull())
1226 return;
1227
1228 if (!frame())
1229 return;
1230
1231 String message;
1232 if (!m_document || m_document->url().isNull())
1233 message = makeString("Unsafe attempt to load URL ", url.stringCenterEllipsizedToLength(), '.');
1234 else
1235 message = makeString("Unsafe attempt to load URL ", url.stringCenterEllipsizedToLength(), " from origin ", m_document->origin(), ". Domains, protocols and ports must match.\n");
1236
1237 frame()->document()->addConsoleMessage(MessageSource::Security, MessageLevel::Error, message);
1238}
1239
1240void CachedResourceLoader::setAutoLoadImages(bool enable)
1241{
1242 if (enable == m_autoLoadImages)
1243 return;
1244
1245 m_autoLoadImages = enable;
1246
1247 if (!m_autoLoadImages)
1248 return;
1249
1250 reloadImagesIfNotDeferred();
1251}
1252
1253void CachedResourceLoader::setImagesEnabled(bool enable)
1254{
1255 if (enable == m_imagesEnabled)
1256 return;
1257
1258 m_imagesEnabled = enable;
1259
1260 if (!m_imagesEnabled)
1261 return;
1262
1263 reloadImagesIfNotDeferred();
1264}
1265
1266bool CachedResourceLoader::clientDefersImage(const URL&) const
1267{
1268 return !m_imagesEnabled;
1269}
1270
1271bool CachedResourceLoader::shouldPerformImageLoad(const URL& url) const
1272{
1273 return m_autoLoadImages || url.protocolIsData();
1274}
1275
1276bool CachedResourceLoader::shouldDeferImageLoad(const URL& url) const
1277{
1278 return clientDefersImage(url) || !shouldPerformImageLoad(url);
1279}
1280
1281void CachedResourceLoader::reloadImagesIfNotDeferred()
1282{
1283 for (auto& resource : m_documentResources.values()) {
1284 if (is<CachedImage>(*resource) && resource->stillNeedsLoad() && !clientDefersImage(resource->url()))
1285 downcast<CachedImage>(*resource).load(*this);
1286 }
1287}
1288
1289CachePolicy CachedResourceLoader::cachePolicy(CachedResource::Type type, const URL& url) const
1290{
1291 Frame* frame = this->frame();
1292 if (!frame)
1293 return CachePolicyVerify;
1294
1295 if (type != CachedResource::Type::MainResource)
1296 return frame->loader().subresourceCachePolicy(url);
1297
1298 if (Page* page = frame->page()) {
1299 if (page->isResourceCachingDisabled())
1300 return CachePolicyReload;
1301 }
1302
1303 switch (frame->loader().loadType()) {
1304 case FrameLoadType::ReloadFromOrigin:
1305 case FrameLoadType::Reload:
1306 return CachePolicyReload;
1307 case FrameLoadType::Back:
1308 case FrameLoadType::Forward:
1309 case FrameLoadType::IndexedBackForward:
1310 // Do not revalidate cached main resource on back/forward navigation.
1311 return CachePolicyHistoryBuffer;
1312 default:
1313 return CachePolicyVerify;
1314 }
1315}
1316
1317void CachedResourceLoader::loadDone(LoadCompletionType type, bool shouldPerformPostLoadActions)
1318{
1319 RefPtr<DocumentLoader> protectDocumentLoader(m_documentLoader);
1320 RefPtr<Document> protectDocument(m_document.get());
1321
1322 ASSERT(shouldPerformPostLoadActions || type == LoadCompletionType::Cancel);
1323
1324 if (frame())
1325 frame()->loader().loadDone(type);
1326
1327 if (shouldPerformPostLoadActions)
1328 performPostLoadActions();
1329
1330 if (!m_garbageCollectDocumentResourcesTimer.isActive())
1331 m_garbageCollectDocumentResourcesTimer.startOneShot(0_s);
1332}
1333
1334// Garbage collecting m_documentResources is a workaround for the
1335// CachedResourceHandles on the RHS being strong references. Ideally this
1336// would be a weak map, however CachedResourceHandles perform additional
1337// bookkeeping on CachedResources, so instead pseudo-GC them -- when the
1338// reference count reaches 1, m_documentResources is the only reference, so
1339// remove it from the map.
1340void CachedResourceLoader::garbageCollectDocumentResources()
1341{
1342 LOG(ResourceLoading, "CachedResourceLoader %p garbageCollectDocumentResources", this);
1343
1344 typedef Vector<String, 10> StringVector;
1345 StringVector resourcesToDelete;
1346
1347 for (auto& resource : m_documentResources) {
1348 LOG(ResourceLoading, " cached resource %p - hasOneHandle %d", resource.value.get(), resource.value->hasOneHandle());
1349
1350 if (resource.value->hasOneHandle())
1351 resourcesToDelete.append(resource.key);
1352 }
1353
1354 for (auto& resource : resourcesToDelete)
1355 m_documentResources.remove(resource);
1356}
1357
1358void CachedResourceLoader::performPostLoadActions()
1359{
1360 platformStrategies()->loaderStrategy()->servePendingRequests();
1361}
1362
1363void CachedResourceLoader::incrementRequestCount(const CachedResource& resource)
1364{
1365 if (resource.ignoreForRequestCount())
1366 return;
1367
1368 ++m_requestCount;
1369}
1370
1371void CachedResourceLoader::decrementRequestCount(const CachedResource& resource)
1372{
1373 if (resource.ignoreForRequestCount())
1374 return;
1375
1376 --m_requestCount;
1377 ASSERT(m_requestCount > -1);
1378}
1379
1380ResourceErrorOr<CachedResourceHandle<CachedResource>> CachedResourceLoader::preload(CachedResource::Type type, CachedResourceRequest&& request)
1381{
1382 if (request.charset().isEmpty() && (type == CachedResource::Type::Script || type == CachedResource::Type::CSSStyleSheet))
1383 request.setCharset(m_document->charset());
1384
1385 auto resource = requestResource(type, WTFMove(request), ForPreload::Yes);
1386 if (resource && (!m_preloads || !m_preloads->contains(resource.value().get()))) {
1387 auto resourceValue = resource.value();
1388 // Fonts need special treatment since just creating the resource doesn't trigger a load.
1389 if (type == CachedResource::Type::FontResource)
1390 downcast<CachedFont>(resourceValue.get())->beginLoadIfNeeded(*this);
1391 resourceValue->increasePreloadCount();
1392
1393 if (!m_preloads)
1394 m_preloads = std::make_unique<ListHashSet<CachedResource*>>();
1395 m_preloads->add(resourceValue.get());
1396 }
1397 return resource;
1398}
1399
1400void CachedResourceLoader::warnUnusedPreloads()
1401{
1402 if (!m_preloads)
1403 return;
1404 for (const auto& resource : *m_preloads) {
1405 if (resource && resource->isLinkPreload() && resource->preloadResult() == CachedResource::PreloadResult::PreloadNotReferenced && document()) {
1406 document()->addConsoleMessage(MessageSource::Other, MessageLevel::Warning,
1407 "The resource " + resource->url().string() +
1408 " was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it wasn't preloaded for nothing.");
1409 }
1410 }
1411}
1412
1413bool CachedResourceLoader::isPreloaded(const String& urlString) const
1414{
1415 const URL& url = m_document->completeURL(urlString);
1416
1417 if (m_preloads) {
1418 for (auto& resource : *m_preloads) {
1419 if (resource->url() == url)
1420 return true;
1421 }
1422 }
1423 return false;
1424}
1425
1426void CachedResourceLoader::clearPreloads(ClearPreloadsMode mode)
1427{
1428 if (!m_preloads)
1429 return;
1430
1431 std::unique_ptr<ListHashSet<CachedResource*>> remainingLinkPreloads;
1432 for (auto* resource : *m_preloads) {
1433 ASSERT(resource);
1434 if (mode == ClearPreloadsMode::ClearSpeculativePreloads && resource->isLinkPreload()) {
1435 if (!remainingLinkPreloads)
1436 remainingLinkPreloads = std::make_unique<ListHashSet<CachedResource*>>();
1437 remainingLinkPreloads->add(resource);
1438 continue;
1439 }
1440 resource->decreasePreloadCount();
1441 bool deleted = resource->deleteIfPossible();
1442 if (!deleted && resource->preloadResult() == CachedResource::PreloadResult::PreloadNotReferenced)
1443 MemoryCache::singleton().remove(*resource);
1444 }
1445 m_preloads = WTFMove(remainingLinkPreloads);
1446}
1447
1448const ResourceLoaderOptions& CachedResourceLoader::defaultCachedResourceOptions()
1449{
1450 static NeverDestroyed<ResourceLoaderOptions> options(
1451 SendCallbackPolicy::SendCallbacks,
1452 ContentSniffingPolicy::SniffContent,
1453 DataBufferingPolicy::BufferData,
1454 StoredCredentialsPolicy::Use,
1455 ClientCredentialPolicy::MayAskClientForCredentials,
1456 FetchOptions::Credentials::Include,
1457 SecurityCheckPolicy::DoSecurityCheck,
1458 FetchOptions::Mode::NoCors,
1459 CertificateInfoPolicy::DoNotIncludeCertificateInfo,
1460 ContentSecurityPolicyImposition::DoPolicyCheck,
1461 DefersLoadingPolicy::AllowDefersLoading,
1462 CachingPolicy::AllowCaching);
1463 return options;
1464}
1465
1466bool CachedResourceLoader::isAlwaysOnLoggingAllowed() const
1467{
1468 return m_documentLoader ? m_documentLoader->isAlwaysOnLoggingAllowed() : true;
1469}
1470
1471}
1472