1/*
2 * Copyright (C) 2011 Google Inc. All rights reserved.
3 * Copyright (C) 2016-2017 Apple Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 * * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * * Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
14 * distribution.
15 * * Neither the name of Google Inc. nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 *
31 */
32
33#include "config.h"
34#include "LinkLoader.h"
35
36#include "CSSStyleSheet.h"
37#include "CachedCSSStyleSheet.h"
38#include "CachedResourceLoader.h"
39#include "CachedResourceRequest.h"
40#include "ContainerNode.h"
41#include "CrossOriginAccessControl.h"
42#include "Document.h"
43#include "Frame.h"
44#include "FrameLoader.h"
45#include "FrameLoaderClient.h"
46#include "FrameView.h"
47#include "HTMLSrcsetParser.h"
48#include "LinkHeader.h"
49#include "LinkPreloadResourceClients.h"
50#include "LinkRelAttribute.h"
51#include "LoaderStrategy.h"
52#include "MIMETypeRegistry.h"
53#include "MediaList.h"
54#include "MediaQueryEvaluator.h"
55#include "PlatformStrategies.h"
56#include "ResourceError.h"
57#include "RuntimeEnabledFeatures.h"
58#include "Settings.h"
59#include "SizesAttributeParser.h"
60#include "StyleResolver.h"
61
62namespace WebCore {
63
64LinkLoader::LinkLoader(LinkLoaderClient& client)
65 : m_client(client)
66{
67}
68
69LinkLoader::~LinkLoader()
70{
71 if (m_cachedLinkResource)
72 m_cachedLinkResource->removeClient(*this);
73 if (m_preloadResourceClient)
74 m_preloadResourceClient->clear();
75}
76
77void LinkLoader::triggerEvents(const CachedResource& resource)
78{
79 if (resource.errorOccurred())
80 m_client.linkLoadingErrored();
81 else
82 m_client.linkLoaded();
83}
84
85void LinkLoader::notifyFinished(CachedResource& resource)
86{
87 ASSERT_UNUSED(resource, m_cachedLinkResource.get() == &resource);
88
89 triggerEvents(*m_cachedLinkResource);
90
91 m_cachedLinkResource->removeClient(*this);
92 m_cachedLinkResource = nullptr;
93}
94
95void LinkLoader::loadLinksFromHeader(const String& headerValue, const URL& baseURL, Document& document, MediaAttributeCheck mediaAttributeCheck)
96{
97 if (headerValue.isEmpty())
98 return;
99 LinkHeaderSet headerSet(headerValue);
100 for (auto& header : headerSet) {
101 if (!header.valid() || header.url().isEmpty() || header.rel().isEmpty())
102 continue;
103 if ((mediaAttributeCheck == MediaAttributeCheck::MediaAttributeNotEmpty && !header.isViewportDependent())
104 || (mediaAttributeCheck == MediaAttributeCheck::MediaAttributeEmpty && header.isViewportDependent())) {
105 continue;
106 }
107
108 LinkRelAttribute relAttribute(document, header.rel());
109 URL url(baseURL, header.url());
110 // Sanity check to avoid re-entrancy here.
111 if (equalIgnoringFragmentIdentifier(url, baseURL))
112 continue;
113 preconnectIfNeeded(relAttribute, url, document, header.crossOrigin());
114 preloadIfNeeded(relAttribute, url, document, header.as(), header.media(), header.mimeType(), header.crossOrigin(), header.imageSrcSet(), header.imageSizes(), nullptr);
115 }
116}
117
118Optional<CachedResource::Type> LinkLoader::resourceTypeFromAsAttribute(const String& as)
119{
120 if (equalLettersIgnoringASCIICase(as, "fetch"))
121 return CachedResource::Type::RawResource;
122 if (equalLettersIgnoringASCIICase(as, "image"))
123 return CachedResource::Type::ImageResource;
124 if (equalLettersIgnoringASCIICase(as, "script"))
125 return CachedResource::Type::Script;
126 if (equalLettersIgnoringASCIICase(as, "style"))
127 return CachedResource::Type::CSSStyleSheet;
128 if (RuntimeEnabledFeatures::sharedFeatures().mediaPreloadingEnabled() && (equalLettersIgnoringASCIICase(as, "video") || equalLettersIgnoringASCIICase(as, "audio")))
129 return CachedResource::Type::MediaResource;
130 if (equalLettersIgnoringASCIICase(as, "font"))
131 return CachedResource::Type::FontResource;
132#if ENABLE(VIDEO_TRACK)
133 if (equalLettersIgnoringASCIICase(as, "track"))
134 return CachedResource::Type::TextTrackResource;
135#endif
136 return WTF::nullopt;
137}
138
139static std::unique_ptr<LinkPreloadResourceClient> createLinkPreloadResourceClient(CachedResource& resource, LinkLoader& loader)
140{
141 switch (resource.type()) {
142 case CachedResource::Type::ImageResource:
143 return std::make_unique<LinkPreloadImageResourceClient>(loader, downcast<CachedImage>(resource));
144 case CachedResource::Type::Script:
145 return std::make_unique<LinkPreloadDefaultResourceClient>(loader, downcast<CachedScript>(resource));
146 case CachedResource::Type::CSSStyleSheet:
147 return std::make_unique<LinkPreloadStyleResourceClient>(loader, downcast<CachedCSSStyleSheet>(resource));
148 case CachedResource::Type::FontResource:
149 return std::make_unique<LinkPreloadFontResourceClient>(loader, downcast<CachedFont>(resource));
150#if ENABLE(VIDEO_TRACK)
151 case CachedResource::Type::TextTrackResource:
152 return std::make_unique<LinkPreloadDefaultResourceClient>(loader, downcast<CachedTextTrack>(resource));
153#endif
154 case CachedResource::Type::MediaResource:
155 ASSERT(RuntimeEnabledFeatures::sharedFeatures().mediaPreloadingEnabled());
156 FALLTHROUGH;
157 case CachedResource::Type::RawResource:
158 return std::make_unique<LinkPreloadRawResourceClient>(loader, downcast<CachedRawResource>(resource));
159 case CachedResource::Type::MainResource:
160 case CachedResource::Type::Icon:
161#if ENABLE(SVG_FONTS)
162 case CachedResource::Type::SVGFontResource:
163#endif
164 case CachedResource::Type::SVGDocumentResource:
165#if ENABLE(XSLT)
166 case CachedResource::Type::XSLStyleSheet:
167#endif
168 case CachedResource::Type::Beacon:
169 case CachedResource::Type::Ping:
170 case CachedResource::Type::LinkPrefetch:
171#if ENABLE(APPLICATION_MANIFEST)
172 case CachedResource::Type::ApplicationManifest:
173#endif
174 // None of these values is currently supported as an `as` value.
175 ASSERT_NOT_REACHED();
176 }
177 return nullptr;
178}
179
180bool LinkLoader::isSupportedType(CachedResource::Type resourceType, const String& mimeType)
181{
182 if (mimeType.isEmpty())
183 return true;
184 switch (resourceType) {
185 case CachedResource::Type::ImageResource:
186 return MIMETypeRegistry::isSupportedImageVideoOrSVGMIMEType(mimeType);
187 case CachedResource::Type::Script:
188 return MIMETypeRegistry::isSupportedJavaScriptMIMEType(mimeType);
189 case CachedResource::Type::CSSStyleSheet:
190 return MIMETypeRegistry::isSupportedStyleSheetMIMEType(mimeType);
191 case CachedResource::Type::FontResource:
192 return MIMETypeRegistry::isSupportedFontMIMEType(mimeType);
193 case CachedResource::Type::MediaResource:
194 if (!RuntimeEnabledFeatures::sharedFeatures().mediaPreloadingEnabled())
195 ASSERT_NOT_REACHED();
196 return MIMETypeRegistry::isSupportedMediaMIMEType(mimeType);
197
198#if ENABLE(VIDEO_TRACK)
199 case CachedResource::Type::TextTrackResource:
200 return MIMETypeRegistry::isSupportedTextTrackMIMEType(mimeType);
201#endif
202 case CachedResource::Type::RawResource:
203#if ENABLE(APPLICATION_MANIFEST)
204 case CachedResource::Type::ApplicationManifest:
205#endif
206 return true;
207 default:
208 ASSERT_NOT_REACHED();
209 }
210 return false;
211}
212
213void LinkLoader::preconnectIfNeeded(const LinkRelAttribute& relAttribute, const URL& href, Document& document, const String& crossOrigin)
214{
215 if (!relAttribute.isLinkPreconnect || !href.isValid() || !href.protocolIsInHTTPFamily() || !document.frame())
216 return;
217 ASSERT(document.settings().linkPreconnectEnabled());
218 StoredCredentialsPolicy storageCredentialsPolicy = StoredCredentialsPolicy::Use;
219 if (equalIgnoringASCIICase(crossOrigin, "anonymous") && document.securityOrigin().canAccess(SecurityOrigin::create(href)))
220 storageCredentialsPolicy = StoredCredentialsPolicy::DoNotUse;
221 ASSERT(document.frame()->loader().networkingContext());
222 platformStrategies()->loaderStrategy()->preconnectTo(document.frame()->loader(), href, storageCredentialsPolicy, [weakDocument = makeWeakPtr(document), href](ResourceError error) {
223 if (!weakDocument)
224 return;
225
226 if (!error.isNull())
227 weakDocument->addConsoleMessage(MessageSource::Network, MessageLevel::Error, makeString("Failed to preconnect to "_s, href.string(), ". Error: "_s, error.localizedDescription()));
228 else
229 weakDocument->addConsoleMessage(MessageSource::Network, MessageLevel::Info, makeString("Successfuly preconnected to "_s, href.string()));
230 });
231}
232
233std::unique_ptr<LinkPreloadResourceClient> LinkLoader::preloadIfNeeded(const LinkRelAttribute& relAttribute, const URL& href, Document& document, const String& as, const String& media, const String& mimeType, const String& crossOriginMode, const String& imageSrcSet, const String& imageSizes, LinkLoader* loader)
234{
235 if (!document.loader() || !relAttribute.isLinkPreload)
236 return nullptr;
237
238 ASSERT(RuntimeEnabledFeatures::sharedFeatures().linkPreloadEnabled());
239 auto type = LinkLoader::resourceTypeFromAsAttribute(as);
240 if (!type) {
241 document.addConsoleMessage(MessageSource::Other, MessageLevel::Error, "<link rel=preload> must have a valid `as` value"_s);
242 return nullptr;
243 }
244 URL url;
245 if (type == CachedResource::Type::ImageResource && !imageSrcSet.isEmpty()) {
246 auto sourceSize = SizesAttributeParser(imageSizes, document).length();
247 auto candidate = bestFitSourceForImageAttributes(document.deviceScaleFactor(), href.string(), imageSrcSet, sourceSize);
248 url = document.completeURL(URL({ }, candidate.string.toString()));
249 } else
250 url = document.completeURL(href);
251
252 if (!url.isValid()) {
253 if (imageSrcSet.isEmpty())
254 document.addConsoleMessage(MessageSource::Other, MessageLevel::Error, "<link rel=preload> has an invalid `href` value"_s);
255 else
256 document.addConsoleMessage(MessageSource::Other, MessageLevel::Error, "<link rel=preload> has an invalid `imagesrcset` value"_s);
257 return nullptr;
258 }
259 if (!MediaQueryEvaluator::mediaAttributeMatches(document, media))
260 return nullptr;
261 if (!isSupportedType(type.value(), mimeType))
262 return nullptr;
263
264 auto options = CachedResourceLoader::defaultCachedResourceOptions();
265 auto linkRequest = createPotentialAccessControlRequest(url, document, crossOriginMode, WTFMove(options));
266 linkRequest.setPriority(CachedResource::defaultPriorityForResourceType(type.value()));
267 linkRequest.setInitiator("link");
268 linkRequest.setIgnoreForRequestCount(true);
269 linkRequest.setIsLinkPreload();
270
271 auto cachedLinkResource = document.cachedResourceLoader().preload(type.value(), WTFMove(linkRequest)).value_or(nullptr);
272
273 if (cachedLinkResource && cachedLinkResource->type() != *type)
274 return nullptr;
275
276 if (cachedLinkResource && loader)
277 return createLinkPreloadResourceClient(*cachedLinkResource, *loader);
278 return nullptr;
279}
280
281void LinkLoader::prefetchIfNeeded(const LinkRelAttribute& relAttribute, const URL& href, Document& document)
282{
283 if (!relAttribute.isLinkPrefetch || !href.isValid() || !document.frame() || !m_client.shouldLoadLink())
284 return;
285
286 ASSERT(RuntimeEnabledFeatures::sharedFeatures().linkPrefetchEnabled());
287 Optional<ResourceLoadPriority> priority;
288 CachedResource::Type type = CachedResource::Type::LinkPrefetch;
289
290 if (m_cachedLinkResource) {
291 m_cachedLinkResource->removeClient(*this);
292 m_cachedLinkResource = nullptr;
293 }
294 // FIXME: Add further prefetch restrictions/limitations:
295 // - third-party iframes cannot trigger prefetches
296 // - Number of prefetches of a given page is limited (to 1 maybe?)
297 ResourceLoaderOptions options = CachedResourceLoader::defaultCachedResourceOptions();
298 options.contentSecurityPolicyImposition = ContentSecurityPolicyImposition::SkipPolicyCheck;
299 options.certificateInfoPolicy = CertificateInfoPolicy::IncludeCertificateInfo;
300 options.credentials = FetchOptions::Credentials::SameOrigin;
301 options.redirect = FetchOptions::Redirect::Manual;
302 options.mode = FetchOptions::Mode::Navigate;
303 options.serviceWorkersMode = ServiceWorkersMode::None;
304 options.cachingPolicy = CachingPolicy::DisallowCaching;
305 m_cachedLinkResource = document.cachedResourceLoader().requestLinkResource(type, CachedResourceRequest(ResourceRequest(document.completeURL(href)), options, priority)).value_or(nullptr);
306 if (m_cachedLinkResource)
307 m_cachedLinkResource->addClient(*this);
308}
309
310void LinkLoader::cancelLoad()
311{
312 if (m_preloadResourceClient)
313 m_preloadResourceClient->clear();
314}
315
316bool LinkLoader::loadLink(const LinkRelAttribute& relAttribute, const URL& href, const String& as, const String& media, const String& mimeType, const String& crossOrigin, const String& imageSrcSet, const String& imageSizes, Document& document)
317{
318 if (relAttribute.isDNSPrefetch) {
319 // FIXME: The href attribute of the link element can be in "//hostname" form, and we shouldn't attempt
320 // to complete that as URL <https://bugs.webkit.org/show_bug.cgi?id=48857>.
321 if (document.settings().dnsPrefetchingEnabled() && href.isValid() && !href.isEmpty() && document.frame())
322 document.frame()->loader().client().prefetchDNS(href.host().toString());
323 }
324
325 preconnectIfNeeded(relAttribute, href, document, crossOrigin);
326
327 if (m_client.shouldLoadLink()) {
328 auto resourceClient = preloadIfNeeded(relAttribute, href, document, as, media, mimeType, crossOrigin, imageSrcSet, imageSizes, this);
329 if (m_preloadResourceClient)
330 m_preloadResourceClient->clear();
331 if (resourceClient)
332 m_preloadResourceClient = WTFMove(resourceClient);
333 }
334
335 prefetchIfNeeded(relAttribute, href, document);
336
337 return true;
338}
339
340}
341