/* Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de) Copyright (C) 2001 Dirk Mueller (mueller@kde.org) Copyright (C) 2002 Waldo Bastian (bastian@kde.org) Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Apple Inc. All rights reserved. Copyright (C) 2009 Torch Mobile Inc. http://www.torchmobile.com/ This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. This class provides all functionality needed for loading images, style sheets and html pages from the web. It has a memory cache for these objects. */ #include "config.h" #include "CachedResourceLoader.h" #include "CachedCSSStyleSheet.h" #include "CachedSVGDocument.h" #include "CachedFont.h" #include "CachedImage.h" #include "CachedRawResource.h" #include "CachedResourceRequest.h" #include "CachedSVGFont.h" #include "CachedScript.h" #include "CachedXSLStyleSheet.h" #include "Chrome.h" #include "ChromeClient.h" #include "ContentExtensionError.h" #include "ContentExtensionRule.h" #include "ContentSecurityPolicy.h" #include "DOMWindow.h" #include "DiagnosticLoggingClient.h" #include "DiagnosticLoggingKeys.h" #include "Document.h" #include "DocumentLoader.h" #include "Frame.h" #include "FrameLoader.h" #include "FrameLoaderClient.h" #include "HTMLElement.h" #include "HTMLFrameOwnerElement.h" #include "LoaderStrategy.h" #include "LocalizedStrings.h" #include "Logging.h" #include "MainFrame.h" #include "MemoryCache.h" #include "Page.h" #include "PingLoader.h" #include "PlatformStrategies.h" #include "RenderElement.h" #include "ResourceLoadInfo.h" #include "ScriptController.h" #include "SecurityOrigin.h" #include "SessionID.h" #include "Settings.h" #include "StyleSheetContents.h" #include "UserContentController.h" #include "UserStyleSheet.h" #include #include #if ENABLE(VIDEO_TRACK) #include "CachedTextTrack.h" #endif #if ENABLE(RESOURCE_TIMING) #include "Performance.h" #endif #define PRELOAD_DEBUG 0 namespace WebCore { static CachedResource* createResource(CachedResource::Type type, ResourceRequest& request, const String& charset, SessionID sessionID) { switch (type) { case CachedResource::ImageResource: return new CachedImage(request, sessionID); case CachedResource::CSSStyleSheet: return new CachedCSSStyleSheet(request, charset, sessionID); case CachedResource::Script: return new CachedScript(request, charset, sessionID); case CachedResource::SVGDocumentResource: return new CachedSVGDocument(request, sessionID); #if ENABLE(SVG_FONTS) case CachedResource::SVGFontResource: return new CachedSVGFont(request, sessionID); #endif case CachedResource::FontResource: return new CachedFont(request, sessionID); case CachedResource::RawResource: case CachedResource::MainResource: return new CachedRawResource(request, type, sessionID); #if ENABLE(XSLT) case CachedResource::XSLStyleSheet: return new CachedXSLStyleSheet(request, sessionID); #endif #if ENABLE(LINK_PREFETCH) case CachedResource::LinkPrefetch: return new CachedResource(request, CachedResource::LinkPrefetch, sessionID); case CachedResource::LinkSubresource: return new CachedResource(request, CachedResource::LinkSubresource, sessionID); #endif #if ENABLE(VIDEO_TRACK) case CachedResource::TextTrackResource: return new CachedTextTrack(request, sessionID); #endif } ASSERT_NOT_REACHED(); return nullptr; } CachedResourceLoader::CachedResourceLoader(DocumentLoader* documentLoader) : m_document(nullptr) , m_documentLoader(documentLoader) , m_requestCount(0) , m_garbageCollectDocumentResourcesTimer(*this, &CachedResourceLoader::garbageCollectDocumentResources) , m_autoLoadImages(true) , m_imagesEnabled(true) , m_allowStaleResources(false) { } CachedResourceLoader::~CachedResourceLoader() { m_documentLoader = nullptr; m_document = nullptr; clearPreloads(); for (auto& resource : m_documentResources.values()) resource->setOwningCachedResourceLoader(nullptr); // Make sure no requests still point to this CachedResourceLoader ASSERT(m_requestCount == 0); } CachedResource* CachedResourceLoader::cachedResource(const String& resourceURL) const { ASSERT(!resourceURL.isNull()); URL url = m_document->completeURL(resourceURL); return cachedResource(url); } CachedResource* CachedResourceLoader::cachedResource(const URL& resourceURL) const { URL url = MemoryCache::removeFragmentIdentifierIfNeeded(resourceURL); return m_documentResources.get(url).get(); } Frame* CachedResourceLoader::frame() const { return m_documentLoader ? m_documentLoader->frame() : nullptr; } SessionID CachedResourceLoader::sessionID() const { SessionID sessionID = SessionID::defaultSessionID(); if (Frame* f = frame()) sessionID = f->page()->sessionID(); return sessionID; } CachedResourceHandle CachedResourceLoader::requestImage(CachedResourceRequest& request) { if (Frame* frame = this->frame()) { if (frame->loader().pageDismissalEventBeingDispatched() != FrameLoader::PageDismissalType::None) { URL requestURL = request.resourceRequest().url(); if (requestURL.isValid() && canRequest(CachedResource::ImageResource, requestURL, request.options(), request.forPreload())) PingLoader::loadImage(*frame, requestURL); return nullptr; } } request.setDefer(clientDefersImage(request.resourceRequest().url()) ? CachedResourceRequest::DeferredByClient : CachedResourceRequest::NoDefer); return downcast(requestResource(CachedResource::ImageResource, request).get()); } CachedResourceHandle CachedResourceLoader::requestFont(CachedResourceRequest& request, bool isSVG) { #if ENABLE(SVG_FONTS) if (isSVG) return downcast(requestResource(CachedResource::SVGFontResource, request).get()); #else UNUSED_PARAM(isSVG); #endif return downcast(requestResource(CachedResource::FontResource, request).get()); } #if ENABLE(VIDEO_TRACK) CachedResourceHandle CachedResourceLoader::requestTextTrack(CachedResourceRequest& request) { return downcast(requestResource(CachedResource::TextTrackResource, request).get()); } #endif CachedResourceHandle CachedResourceLoader::requestCSSStyleSheet(CachedResourceRequest& request) { return downcast(requestResource(CachedResource::CSSStyleSheet, request).get()); } CachedResourceHandle CachedResourceLoader::requestUserCSSStyleSheet(CachedResourceRequest& request) { URL url = MemoryCache::removeFragmentIdentifierIfNeeded(request.resourceRequest().url()); #if ENABLE(CACHE_PARTITIONING) request.mutableResourceRequest().setDomainForCachePartition(document()->topOrigin()->domainForCachePartition()); #endif auto& memoryCache = MemoryCache::singleton(); if (request.allowsCaching()) { if (CachedResource* existing = memoryCache.resourceForRequest(request.resourceRequest(), sessionID())) { if (is(*existing)) return downcast(existing); memoryCache.remove(*existing); } } if (url.string() != request.resourceRequest().url()) request.mutableResourceRequest().setURL(url); CachedResourceHandle userSheet = new CachedCSSStyleSheet(request.resourceRequest(), request.charset(), sessionID()); if (request.allowsCaching()) memoryCache.add(*userSheet); // FIXME: loadResource calls setOwningCachedResourceLoader() if the resource couldn't be added to cache. Does this function need to call it, too? userSheet->load(*this, ResourceLoaderOptions(DoNotSendCallbacks, SniffContent, BufferData, AllowStoredCredentials, AskClientForAllCredentials, ClientRequestedCredentials, SkipSecurityCheck, UseDefaultOriginRestrictionsForType, DoNotIncludeCertificateInfo, ContentSecurityPolicyImposition::SkipPolicyCheck, DefersLoadingPolicy::AllowDefersLoading, CachingPolicy::AllowCaching)); return userSheet; } CachedResourceHandle CachedResourceLoader::requestScript(CachedResourceRequest& request) { return downcast(requestResource(CachedResource::Script, request).get()); } #if ENABLE(XSLT) CachedResourceHandle CachedResourceLoader::requestXSLStyleSheet(CachedResourceRequest& request) { return downcast(requestResource(CachedResource::XSLStyleSheet, request).get()); } #endif CachedResourceHandle CachedResourceLoader::requestSVGDocument(CachedResourceRequest& request) { return downcast(requestResource(CachedResource::SVGDocumentResource, request).get()); } #if ENABLE(LINK_PREFETCH) CachedResourceHandle CachedResourceLoader::requestLinkResource(CachedResource::Type type, CachedResourceRequest& request) { ASSERT(frame()); ASSERT(type == CachedResource::LinkPrefetch || type == CachedResource::LinkSubresource); return requestResource(type, request); } #endif CachedResourceHandle CachedResourceLoader::requestRawResource(CachedResourceRequest& request) { return downcast(requestResource(CachedResource::RawResource, request).get()); } CachedResourceHandle CachedResourceLoader::requestMainResource(CachedResourceRequest& request) { return downcast(requestResource(CachedResource::MainResource, request).get()); } static MixedContentChecker::ContentType contentTypeFromResourceType(CachedResource::Type type) { switch (type) { case CachedResource::ImageResource: return MixedContentChecker::ContentType::ActiveCanWarn; case CachedResource::CSSStyleSheet: case CachedResource::Script: case CachedResource::FontResource: return MixedContentChecker::ContentType::Active; #if ENABLE(SVG_FONTS) case CachedResource::SVGFontResource: return MixedContentChecker::ContentType::Active; #endif case CachedResource::RawResource: case CachedResource::SVGDocumentResource: return MixedContentChecker::ContentType::Active; #if ENABLE(XSLT) case CachedResource::XSLStyleSheet: return MixedContentChecker::ContentType::Active; #endif #if ENABLE(LINK_PREFETCH) case CachedResource::LinkPrefetch: case CachedResource::LinkSubresource: return MixedContentChecker::ContentType::Active; #endif #if ENABLE(VIDEO_TRACK) case CachedResource::TextTrackResource: return MixedContentChecker::ContentType::Active; #endif default: ASSERT_NOT_REACHED(); return MixedContentChecker::ContentType::Active; } } bool CachedResourceLoader::checkInsecureContent(CachedResource::Type type, const URL& url) const { switch (type) { case CachedResource::Script: #if ENABLE(XSLT) case CachedResource::XSLStyleSheet: #endif case CachedResource::SVGDocumentResource: case CachedResource::CSSStyleSheet: // These resource can inject script into the current document (Script, // XSL) or exfiltrate the content of the current document (CSS). if (Frame* f = frame()) if (!f->loader().mixedContentChecker().canRunInsecureContent(m_document->securityOrigin(), url)) return false; break; #if ENABLE(VIDEO_TRACK) case CachedResource::TextTrackResource: #endif case CachedResource::RawResource: case CachedResource::ImageResource: #if ENABLE(SVG_FONTS) case CachedResource::SVGFontResource: #endif case CachedResource::FontResource: { // These resources can corrupt only the frame's pixels. if (Frame* f = frame()) { Frame& topFrame = f->tree().top(); if (!topFrame.loader().mixedContentChecker().canDisplayInsecureContent(topFrame.document()->securityOrigin(), contentTypeFromResourceType(type), url)) return false; } break; } case CachedResource::MainResource: #if ENABLE(LINK_PREFETCH) case CachedResource::LinkPrefetch: case CachedResource::LinkSubresource: // Prefetch cannot affect the current document. #endif break; } return true; } bool CachedResourceLoader::canRequest(CachedResource::Type type, const URL& url, const ResourceLoaderOptions& options, bool forPreload) { if (document() && !document()->securityOrigin()->canDisplay(url)) { if (!forPreload) FrameLoader::reportLocalLoadFailed(frame(), url.stringCenterEllipsizedToLength()); LOG(ResourceLoading, "CachedResourceLoader::requestResource URL was not allowed by SecurityOrigin::canDisplay"); return false; } bool skipContentSecurityPolicyCheck = options.contentSecurityPolicyImposition() == ContentSecurityPolicyImposition::SkipPolicyCheck; // Some types of resources can be loaded only from the same origin. Other // types of resources, like Images, Scripts, and CSS, can be loaded from // any URL. switch (type) { case CachedResource::MainResource: case CachedResource::ImageResource: case CachedResource::CSSStyleSheet: case CachedResource::Script: #if ENABLE(SVG_FONTS) case CachedResource::SVGFontResource: #endif case CachedResource::FontResource: case CachedResource::RawResource: #if ENABLE(LINK_PREFETCH) case CachedResource::LinkPrefetch: case CachedResource::LinkSubresource: #endif #if ENABLE(VIDEO_TRACK) case CachedResource::TextTrackResource: #endif if (options.requestOriginPolicy() == RestrictToSameOrigin && !m_document->securityOrigin()->canRequest(url)) { printAccessDeniedMessage(url); return false; } break; case CachedResource::SVGDocumentResource: #if ENABLE(XSLT) case CachedResource::XSLStyleSheet: if (!m_document->securityOrigin()->canRequest(url)) { printAccessDeniedMessage(url); return false; } #endif break; } switch (type) { #if ENABLE(XSLT) case CachedResource::XSLStyleSheet: if (!m_document->contentSecurityPolicy()->allowScriptFromSource(url, skipContentSecurityPolicyCheck)) return false; break; #endif case CachedResource::Script: if (!m_document->contentSecurityPolicy()->allowScriptFromSource(url, skipContentSecurityPolicyCheck)) return false; if (frame() && !frame()->settings().isScriptEnabled()) return false; break; case CachedResource::CSSStyleSheet: if (!m_document->contentSecurityPolicy()->allowStyleFromSource(url, skipContentSecurityPolicyCheck)) return false; break; case CachedResource::SVGDocumentResource: case CachedResource::ImageResource: if (!m_document->contentSecurityPolicy()->allowImageFromSource(url, skipContentSecurityPolicyCheck)) return false; break; #if ENABLE(SVG_FONTS) case CachedResource::SVGFontResource: #endif case CachedResource::FontResource: { if (!m_document->contentSecurityPolicy()->allowFontFromSource(url, skipContentSecurityPolicyCheck)) return false; break; } case CachedResource::MainResource: case CachedResource::RawResource: #if ENABLE(LINK_PREFETCH) case CachedResource::LinkPrefetch: case CachedResource::LinkSubresource: #endif break; #if ENABLE(VIDEO_TRACK) case CachedResource::TextTrackResource: if (!m_document->contentSecurityPolicy()->allowMediaFromSource(url, skipContentSecurityPolicyCheck)) return false; break; #endif } // SVG Images have unique security rules that prevent all subresource requests except for data urls. if (type != CachedResource::MainResource && frame() && frame()->page()) { if (frame()->page()->chrome().client().isSVGImageChromeClient() && !url.protocolIsData()) return false; } if (!canRequestInContentDispositionAttachmentSandbox(type, url)) return false; // 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. // They'll still get a warning in the console about CSP blocking the load. // FIXME: Should we consider forPreload here? if (!checkInsecureContent(type, url)) return false; return true; } bool CachedResourceLoader::canRequestInContentDispositionAttachmentSandbox(CachedResource::Type type, const URL& url) const { Document* document; // FIXME: Do we want to expand this to all resource types that the mixed content checker would consider active content? switch (type) { case CachedResource::MainResource: if (auto ownerElement = frame() ? frame()->ownerElement() : nullptr) { document = &ownerElement->document(); break; } return true; case CachedResource::CSSStyleSheet: document = m_document; break; default: return true; } if (!document->shouldEnforceContentDispositionAttachmentSandbox() || document->securityOrigin()->canRequest(url)) return true; String message = "Unsafe attempt to load URL " + url.stringCenterEllipsizedToLength() + " from document with Content-Disposition: attachment at URL " + document->url().stringCenterEllipsizedToLength() + "."; document->addConsoleMessage(MessageSource::Security, MessageLevel::Error, message); return false; } bool CachedResourceLoader::shouldContinueAfterNotifyingLoadedFromMemoryCache(const CachedResourceRequest& request, CachedResource* resource) { if (!resource || !frame() || resource->status() != CachedResource::Cached) return true; ResourceRequest newRequest = ResourceRequest(resource->url()); if (request.resourceRequest().hiddenFromInspector()) newRequest.setHiddenFromInspector(true); frame()->loader().loadedResourceFromMemoryCache(resource, newRequest); // FIXME : If the delegate modifies the request's // URL, it is no longer appropriate to use this CachedResource. return !newRequest.isNull(); } static inline void logMemoryCacheResourceRequest(Frame* frame, const String& description, const String& value = String()) { if (!frame) return; if (value.isNull()) frame->mainFrame().diagnosticLoggingClient().logDiagnosticMessage(DiagnosticLoggingKeys::resourceRequestKey(), description, ShouldSample::Yes); else frame->mainFrame().diagnosticLoggingClient().logDiagnosticMessageWithValue(DiagnosticLoggingKeys::resourceRequestKey(), description, value, ShouldSample::Yes); } CachedResourceHandle CachedResourceLoader::requestResource(CachedResource::Type type, CachedResourceRequest& request) { URL url = request.resourceRequest().url(); LOG(ResourceLoading, "CachedResourceLoader::requestResource '%s', charset '%s', priority=%d, forPreload=%u", url.stringCenterEllipsizedToLength().latin1().data(), request.charset().latin1().data(), request.priority() ? static_cast(request.priority().value()) : -1, request.forPreload()); // If only the fragment identifiers differ, it is the same resource. url = MemoryCache::removeFragmentIdentifierIfNeeded(url); if (!url.isValid()) return nullptr; if (!canRequest(type, url, request.options(), request.forPreload())) return nullptr; #if ENABLE(CONTENT_EXTENSIONS) if (frame() && frame()->mainFrame().page() && frame()->mainFrame().page()->userContentController() && m_documentLoader) { if (frame()->mainFrame().page()->userContentController()->processContentExtensionRulesForLoad(request.mutableResourceRequest(), toResourceType(type), *m_documentLoader) == ContentExtensions::BlockedStatus::Blocked) { if (type == CachedResource::Type::MainResource) { auto resource = createResource(type, request.mutableResourceRequest(), request.charset(), sessionID()); ASSERT(resource); resource->error(CachedResource::Status::LoadError); resource->setResourceError(ResourceError(ContentExtensions::WebKitContentBlockerDomain, 0, request.resourceRequest().url(), WEB_UI_STRING("The URL was blocked by a content blocker", "WebKitErrorBlockedByContentBlocker description"))); return resource; } return nullptr; } url = request.resourceRequest().url(); // The content extension could have changed it from http to https. url = MemoryCache::removeFragmentIdentifierIfNeeded(url); // Might need to remove fragment identifier again. } #endif auto& memoryCache = MemoryCache::singleton(); if (request.allowsCaching() && memoryCache.disabled()) { DocumentResourceMap::iterator it = m_documentResources.find(url.string()); if (it != m_documentResources.end()) { it->value->setOwningCachedResourceLoader(nullptr); m_documentResources.remove(it); } } // See if we can use an existing resource from the cache. CachedResourceHandle resource; #if ENABLE(CACHE_PARTITIONING) if (document()) request.mutableResourceRequest().setDomainForCachePartition(document()->topOrigin()->domainForCachePartition()); #endif if (request.allowsCaching()) resource = memoryCache.resourceForRequest(request.resourceRequest(), sessionID()); logMemoryCacheResourceRequest(frame(), resource ? DiagnosticLoggingKeys::inMemoryCacheKey() : DiagnosticLoggingKeys::notInMemoryCacheKey()); const RevalidationPolicy policy = determineRevalidationPolicy(type, request, resource.get()); switch (policy) { case Reload: memoryCache.remove(*resource); FALLTHROUGH; case Load: if (resource) logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::inMemoryCacheKey(), DiagnosticLoggingKeys::unusedKey()); resource = loadResource(type, request); break; case Revalidate: if (resource) logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::inMemoryCacheKey(), DiagnosticLoggingKeys::revalidatingKey()); resource = revalidateResource(request, resource.get()); break; case Use: if (!shouldContinueAfterNotifyingLoadedFromMemoryCache(request, resource.get())) return nullptr; logMemoryCacheResourceRequest(frame(), DiagnosticLoggingKeys::inMemoryCacheKey(), DiagnosticLoggingKeys::usedKey()); memoryCache.resourceAccessed(*resource); break; } if (!resource) return nullptr; if (!request.forPreload() || policy != Use) resource->setLoadPriority(request.priority()); if ((policy != Use || resource->stillNeedsLoad()) && CachedResourceRequest::NoDefer == request.defer()) { resource->load(*this, request.options()); // We don't support immediate loads, but we do support immediate failure. if (resource->errorOccurred()) { if (resource->allowsCaching() && resource->inCache()) memoryCache.remove(*resource); return nullptr; } } if (document() && !document()->loadEventFinished() && !request.resourceRequest().url().protocolIsData()) m_validatedURLs.add(request.resourceRequest().url()); ASSERT(resource->url() == url.string()); m_documentResources.set(resource->url(), resource); return resource; } void CachedResourceLoader::documentDidFinishLoadEvent() { m_validatedURLs.clear(); } CachedResourceHandle CachedResourceLoader::revalidateResource(const CachedResourceRequest& request, CachedResource* resource) { ASSERT(resource); ASSERT(resource->inCache()); auto& memoryCache = MemoryCache::singleton(); ASSERT(!memoryCache.disabled()); ASSERT(resource->canUseCacheValidator()); ASSERT(!resource->resourceToRevalidate()); ASSERT(resource->sessionID() == sessionID()); ASSERT(resource->allowsCaching()); CachedResourceHandle newResource = createResource(resource->type(), resource->resourceRequest(), resource->encoding(), resource->sessionID()); LOG(ResourceLoading, "Resource %p created to revalidate %p", newResource.get(), resource); newResource->setResourceToRevalidate(resource); memoryCache.remove(*resource); memoryCache.add(*newResource); #if ENABLE(RESOURCE_TIMING) storeResourceTimingInitiatorInformation(resource, request); #else UNUSED_PARAM(request); #endif return newResource; } CachedResourceHandle CachedResourceLoader::loadResource(CachedResource::Type type, CachedResourceRequest& request) { auto& memoryCache = MemoryCache::singleton(); ASSERT(!memoryCache.resourceForRequest(request.resourceRequest(), sessionID())); LOG(ResourceLoading, "Loading CachedResource for '%s'.", request.resourceRequest().url().stringCenterEllipsizedToLength().latin1().data()); CachedResourceHandle resource = createResource(type, request.mutableResourceRequest(), request.charset(), sessionID()); if (request.allowsCaching() && !memoryCache.add(*resource)) resource->setOwningCachedResourceLoader(this); #if ENABLE(RESOURCE_TIMING) storeResourceTimingInitiatorInformation(resource, request); #endif return resource; } #if ENABLE(RESOURCE_TIMING) void CachedResourceLoader::storeResourceTimingInitiatorInformation(const CachedResourceHandle& resource, const CachedResourceRequest& request) { if (resource->type() == CachedResource::MainResource) { //