9f55ae8a325b7e3ff315a4616a5415e91b716477
[WebKit-https.git] / Source / WebCore / loader / LinkLoader.cpp
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 "LinkHeader.h"
48 #include "LinkPreloadResourceClients.h"
49 #include "LinkRelAttribute.h"
50 #include "LoaderStrategy.h"
51 #include "MIMETypeRegistry.h"
52 #include "MediaQueryEvaluator.h"
53 #include "PlatformStrategies.h"
54 #include "ResourceError.h"
55 #include "RuntimeEnabledFeatures.h"
56 #include "Settings.h"
57 #include "StyleResolver.h"
58
59 namespace WebCore {
60
61 LinkLoader::LinkLoader(LinkLoaderClient& client)
62     : m_client(client)
63 {
64 }
65
66 LinkLoader::~LinkLoader()
67 {
68     if (m_cachedLinkResource)
69         m_cachedLinkResource->removeClient(*this);
70     if (m_preloadResourceClient)
71         m_preloadResourceClient->clear();
72 }
73
74 void LinkLoader::triggerEvents(const CachedResource& resource)
75 {
76     if (resource.errorOccurred())
77         m_client.linkLoadingErrored();
78     else
79         m_client.linkLoaded();
80 }
81
82 void LinkLoader::notifyFinished(CachedResource& resource)
83 {
84     ASSERT_UNUSED(resource, m_cachedLinkResource.get() == &resource);
85
86     triggerEvents(*m_cachedLinkResource);
87
88     m_cachedLinkResource->removeClient(*this);
89     m_cachedLinkResource = nullptr;
90 }
91
92 void LinkLoader::loadLinksFromHeader(const String& headerValue, const URL& baseURL, Document& document, MediaAttributeCheck mediaAttributeCheck)
93 {
94     if (headerValue.isEmpty())
95         return;
96     LinkHeaderSet headerSet(headerValue);
97     for (auto& header : headerSet) {
98         if (!header.valid() || header.url().isEmpty() || header.rel().isEmpty())
99             continue;
100         if ((mediaAttributeCheck == MediaAttributeCheck::MediaAttributeNotEmpty && header.media().isEmpty())
101             || (mediaAttributeCheck == MediaAttributeCheck::MediaAttributeEmpty && !header.media().isEmpty())) {
102                 continue;
103         }
104
105         LinkRelAttribute relAttribute(document, header.rel());
106         URL url(baseURL, header.url());
107         // Sanity check to avoid re-entrancy here.
108         if (equalIgnoringFragmentIdentifier(url, baseURL))
109             continue;
110         preconnectIfNeeded(relAttribute, url, document, header.crossOrigin());
111         preloadIfNeeded(relAttribute, url, document, header.as(), header.media(), header.mimeType(), header.crossOrigin(), nullptr);
112     }
113 }
114
115 Optional<CachedResource::Type> LinkLoader::resourceTypeFromAsAttribute(const String& as)
116 {
117     if (equalLettersIgnoringASCIICase(as, "fetch"))
118         return CachedResource::Type::RawResource;
119     if (equalLettersIgnoringASCIICase(as, "image"))
120         return CachedResource::Type::ImageResource;
121     if (equalLettersIgnoringASCIICase(as, "script"))
122         return CachedResource::Type::Script;
123     if (equalLettersIgnoringASCIICase(as, "style"))
124         return CachedResource::Type::CSSStyleSheet;
125     if (RuntimeEnabledFeatures::sharedFeatures().mediaPreloadingEnabled() && (equalLettersIgnoringASCIICase(as, "video") || equalLettersIgnoringASCIICase(as, "audio")))
126         return CachedResource::Type::MediaResource;
127     if (equalLettersIgnoringASCIICase(as, "font"))
128         return CachedResource::Type::FontResource;
129 #if ENABLE(VIDEO_TRACK)
130     if (equalLettersIgnoringASCIICase(as, "track"))
131         return CachedResource::Type::TextTrackResource;
132 #endif
133     return WTF::nullopt;
134 }
135
136 static std::unique_ptr<LinkPreloadResourceClient> createLinkPreloadResourceClient(CachedResource& resource, LinkLoader& loader)
137 {
138     switch (resource.type()) {
139     case CachedResource::Type::ImageResource:
140         return std::make_unique<LinkPreloadImageResourceClient>(loader, downcast<CachedImage>(resource));
141     case CachedResource::Type::Script:
142         return std::make_unique<LinkPreloadDefaultResourceClient>(loader, downcast<CachedScript>(resource));
143     case CachedResource::Type::CSSStyleSheet:
144         return std::make_unique<LinkPreloadStyleResourceClient>(loader, downcast<CachedCSSStyleSheet>(resource));
145     case CachedResource::Type::FontResource:
146         return std::make_unique<LinkPreloadFontResourceClient>(loader, downcast<CachedFont>(resource));
147 #if ENABLE(VIDEO_TRACK)
148     case CachedResource::Type::TextTrackResource:
149         return std::make_unique<LinkPreloadDefaultResourceClient>(loader, downcast<CachedTextTrack>(resource));
150 #endif
151     case CachedResource::Type::MediaResource:
152         ASSERT(RuntimeEnabledFeatures::sharedFeatures().mediaPreloadingEnabled());
153         FALLTHROUGH;
154     case CachedResource::Type::RawResource:
155         return std::make_unique<LinkPreloadRawResourceClient>(loader, downcast<CachedRawResource>(resource));
156     case CachedResource::Type::MainResource:
157     case CachedResource::Type::Icon:
158 #if ENABLE(SVG_FONTS)
159     case CachedResource::Type::SVGFontResource:
160 #endif
161     case CachedResource::Type::SVGDocumentResource:
162 #if ENABLE(XSLT)
163     case CachedResource::Type::XSLStyleSheet:
164 #endif
165     case CachedResource::Type::Beacon:
166     case CachedResource::Type::LinkPrefetch:
167 #if ENABLE(APPLICATION_MANIFEST)
168     case CachedResource::Type::ApplicationManifest:
169 #endif
170         // None of these values is currently supported as an `as` value.
171         ASSERT_NOT_REACHED();
172     }
173     return nullptr;
174 }
175
176 bool LinkLoader::isSupportedType(CachedResource::Type resourceType, const String& mimeType)
177 {
178     if (mimeType.isEmpty())
179         return true;
180     switch (resourceType) {
181     case CachedResource::Type::ImageResource:
182         return MIMETypeRegistry::isSupportedImageVideoOrSVGMIMEType(mimeType);
183     case CachedResource::Type::Script:
184         return MIMETypeRegistry::isSupportedJavaScriptMIMEType(mimeType);
185     case CachedResource::Type::CSSStyleSheet:
186         return MIMETypeRegistry::isSupportedStyleSheetMIMEType(mimeType);
187     case CachedResource::Type::FontResource:
188         return MIMETypeRegistry::isSupportedFontMIMEType(mimeType);
189     case CachedResource::Type::MediaResource:
190         if (!RuntimeEnabledFeatures::sharedFeatures().mediaPreloadingEnabled())
191             ASSERT_NOT_REACHED();
192         return MIMETypeRegistry::isSupportedMediaMIMEType(mimeType);
193
194 #if ENABLE(VIDEO_TRACK)
195     case CachedResource::Type::TextTrackResource:
196         return MIMETypeRegistry::isSupportedTextTrackMIMEType(mimeType);
197 #endif
198     case CachedResource::Type::RawResource:
199 #if ENABLE(APPLICATION_MANIFEST)
200     case CachedResource::Type::ApplicationManifest:
201 #endif
202         return true;
203     default:
204         ASSERT_NOT_REACHED();
205     }
206     return false;
207 }
208
209 void LinkLoader::preconnectIfNeeded(const LinkRelAttribute& relAttribute, const URL& href, Document& document, const String& crossOrigin)
210 {
211     if (!relAttribute.isLinkPreconnect || !href.isValid() || !href.protocolIsInHTTPFamily() || !document.frame())
212         return;
213     ASSERT(document.settings().linkPreconnectEnabled());
214     StoredCredentialsPolicy storageCredentialsPolicy = StoredCredentialsPolicy::Use;
215     if (equalIgnoringASCIICase(crossOrigin, "anonymous") && document.securityOrigin().canAccess(SecurityOrigin::create(href)))
216         storageCredentialsPolicy = StoredCredentialsPolicy::DoNotUse;
217     ASSERT(document.frame()->loader().networkingContext());
218     platformStrategies()->loaderStrategy()->preconnectTo(document.frame()->loader(), href, storageCredentialsPolicy, [weakDocument = makeWeakPtr(document), href](ResourceError error) {
219         if (!weakDocument)
220             return;
221
222         if (!error.isNull())
223             weakDocument->addConsoleMessage(MessageSource::Network, MessageLevel::Error, makeString("Failed to preconnect to "_s, href.string(), ". Error: "_s, error.localizedDescription()));
224         else
225             weakDocument->addConsoleMessage(MessageSource::Network, MessageLevel::Info, makeString("Successfuly preconnected to "_s, href.string()));
226     });
227 }
228
229 std::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, LinkLoader* loader)
230 {
231     if (!document.loader() || !relAttribute.isLinkPreload)
232         return nullptr;
233
234     ASSERT(RuntimeEnabledFeatures::sharedFeatures().linkPreloadEnabled());
235     if (!href.isValid()) {
236         document.addConsoleMessage(MessageSource::Other, MessageLevel::Error, "<link rel=preload> has an invalid `href` value"_s);
237         return nullptr;
238     }
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     if (!MediaQueryEvaluator::mediaAttributeMatches(document, media))
245         return nullptr;
246     if (!isSupportedType(type.value(), mimeType))
247         return nullptr;
248
249     auto options = CachedResourceLoader::defaultCachedResourceOptions();
250     auto linkRequest = createPotentialAccessControlRequest(document.completeURL(href), document, crossOriginMode, WTFMove(options));
251     linkRequest.setPriority(CachedResource::defaultPriorityForResourceType(type.value()));
252     linkRequest.setInitiator("link");
253     linkRequest.setIgnoreForRequestCount(true);
254     linkRequest.setIsLinkPreload();
255
256     auto cachedLinkResource = document.cachedResourceLoader().preload(type.value(), WTFMove(linkRequest)).value_or(nullptr);
257
258     if (cachedLinkResource && cachedLinkResource->type() != *type)
259         return nullptr;
260
261     if (cachedLinkResource && loader)
262         return createLinkPreloadResourceClient(*cachedLinkResource, *loader);
263     return nullptr;
264 }
265
266 void LinkLoader::prefetchIfNeeded(const LinkRelAttribute& relAttribute, const URL& href, Document& document)
267 {
268     if (!relAttribute.isLinkPrefetch || !href.isValid() || !document.frame() || !m_client.shouldLoadLink())
269         return;
270
271     ASSERT(RuntimeEnabledFeatures::sharedFeatures().linkPrefetchEnabled());
272     Optional<ResourceLoadPriority> priority;
273     CachedResource::Type type = CachedResource::Type::LinkPrefetch;
274
275     if (m_cachedLinkResource) {
276         m_cachedLinkResource->removeClient(*this);
277         m_cachedLinkResource = nullptr;
278     }
279     ResourceLoaderOptions options = CachedResourceLoader::defaultCachedResourceOptions();
280     options.contentSecurityPolicyImposition = ContentSecurityPolicyImposition::SkipPolicyCheck;
281     m_cachedLinkResource = document.cachedResourceLoader().requestLinkResource(type, CachedResourceRequest(ResourceRequest(document.completeURL(href)), options, priority)).value_or(nullptr);
282     if (m_cachedLinkResource)
283         m_cachedLinkResource->addClient(*this);
284 }
285
286 void LinkLoader::cancelLoad()
287 {
288     if (m_preloadResourceClient)
289         m_preloadResourceClient->clear();
290 }
291
292 bool LinkLoader::loadLink(const LinkRelAttribute& relAttribute, const URL& href, const String& as, const String& media, const String& mimeType, const String& crossOrigin, Document& document)
293 {
294     if (relAttribute.isDNSPrefetch) {
295         // FIXME: The href attribute of the link element can be in "//hostname" form, and we shouldn't attempt
296         // to complete that as URL <https://bugs.webkit.org/show_bug.cgi?id=48857>.
297         if (document.settings().dnsPrefetchingEnabled() && href.isValid() && !href.isEmpty() && document.frame())
298             document.frame()->loader().client().prefetchDNS(href.host().toString());
299     }
300
301     preconnectIfNeeded(relAttribute, href, document, crossOrigin);
302
303     if (m_client.shouldLoadLink()) {
304         auto resourceClient = preloadIfNeeded(relAttribute, href, document, as, media, mimeType, crossOrigin, this);
305         if (m_preloadResourceClient)
306             m_preloadResourceClient->clear();
307         if (resourceClient)
308             m_preloadResourceClient = WTFMove(resourceClient);
309     }
310
311     prefetchIfNeeded(relAttribute, href, document);
312
313     return true;
314 }
315
316 }