d8a5ec6dd9f4bd6579760f28c0ee8239805e2981
[WebKit-https.git] / Source / WebCore / loader / appcache / ApplicationCacheHost.cpp
1 /*
2  * Copyright (C) 2008-2016 Apple Inc. All Rights Reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #include "config.h"
27 #include "ApplicationCacheHost.h"
28
29 #include "ApplicationCache.h"
30 #include "ApplicationCacheGroup.h"
31 #include "ApplicationCacheResource.h"
32 #include "ContentSecurityPolicy.h"
33 #include "DocumentLoader.h"
34 #include "DOMApplicationCache.h"
35 #include "EventNames.h"
36 #include "Frame.h"
37 #include "FrameLoader.h"
38 #include "FrameLoaderClient.h"
39 #include "InspectorInstrumentation.h"
40 #include "Page.h"
41 #include "ProgressEvent.h"
42 #include "ResourceHandle.h"
43 #include "ResourceRequest.h"
44 #include "Settings.h"
45 #include "SubresourceLoader.h"
46 #include <wtf/FileSystem.h>
47 #include <wtf/UUID.h>
48
49 namespace WebCore {
50
51 ApplicationCacheHost::ApplicationCacheHost(DocumentLoader& documentLoader)
52     : m_documentLoader(documentLoader)
53 {
54 }
55
56 ApplicationCacheHost::~ApplicationCacheHost()
57 {
58     ASSERT(!m_applicationCache || !m_candidateApplicationCacheGroup || m_applicationCache->group() == m_candidateApplicationCacheGroup);
59     if (m_applicationCache)
60         m_applicationCache->group()->disassociateDocumentLoader(m_documentLoader);
61     else if (m_candidateApplicationCacheGroup)
62         m_candidateApplicationCacheGroup->disassociateDocumentLoader(m_documentLoader);
63 }
64
65 void ApplicationCacheHost::selectCacheWithoutManifest()
66 {
67     ASSERT(m_documentLoader.frame());
68     ApplicationCacheGroup::selectCacheWithoutManifestURL(*m_documentLoader.frame());
69 }
70
71 void ApplicationCacheHost::selectCacheWithManifest(const URL& manifestURL)
72 {
73     ASSERT(m_documentLoader.frame());
74     ApplicationCacheGroup::selectCache(*m_documentLoader.frame(), manifestURL);
75 }
76
77 void ApplicationCacheHost::maybeLoadMainResource(const ResourceRequest& request, SubstituteData& substituteData)
78 {
79     // Check if this request should be loaded from the application cache
80     if (!substituteData.isValid() && isApplicationCacheEnabled() && !isApplicationCacheBlockedForRequest(request)) {
81         ASSERT(!m_mainResourceApplicationCache);
82
83         m_mainResourceApplicationCache = ApplicationCacheGroup::cacheForMainRequest(request, &m_documentLoader);
84
85         if (m_mainResourceApplicationCache) {
86             // Get the resource from the application cache. By definition, cacheForMainRequest() returns a cache that contains the resource.
87             ApplicationCacheResource* resource = m_mainResourceApplicationCache->resourceForRequest(request);
88
89             // ApplicationCache resources have fragment identifiers stripped off of their URLs,
90             // but we'll need to restore that for the SubstituteData.
91             ResourceResponse responseToUse = resource->response();
92             if (request.url().hasFragmentIdentifier()) {
93                 URL url = responseToUse.url();
94                 url.setFragmentIdentifier(request.url().fragmentIdentifier());
95                 responseToUse.setURL(url);
96             }
97
98             substituteData = SubstituteData(&resource->data(),
99                                             URL(),
100                                             responseToUse,
101                                             SubstituteData::SessionHistoryVisibility::Visible);
102         }
103     }
104 }
105
106 void ApplicationCacheHost::maybeLoadMainResourceForRedirect(const ResourceRequest& request, SubstituteData& substituteData)
107 {
108     ASSERT(status() == UNCACHED);
109     maybeLoadMainResource(request, substituteData);
110 }
111
112 bool ApplicationCacheHost::maybeLoadFallbackForMainResponse(const ResourceRequest& request, const ResourceResponse& r)
113 {
114     if (r.httpStatusCode() / 100 == 4 || r.httpStatusCode() / 100 == 5) {
115         ASSERT(!m_mainResourceApplicationCache);
116         if (isApplicationCacheEnabled() && !isApplicationCacheBlockedForRequest(request)) {
117             m_mainResourceApplicationCache = ApplicationCacheGroup::fallbackCacheForMainRequest(request, &m_documentLoader);
118
119             if (scheduleLoadFallbackResourceFromApplicationCache(m_documentLoader.mainResourceLoader(), m_mainResourceApplicationCache.get()))
120                 return true;
121         }
122     }
123     return false;
124 }
125
126 bool ApplicationCacheHost::maybeLoadFallbackForMainError(const ResourceRequest& request, const ResourceError& error)
127 {
128     if (!error.isCancellation()) {
129         ASSERT(!m_mainResourceApplicationCache);
130         if (isApplicationCacheEnabled() && !isApplicationCacheBlockedForRequest(request)) {
131             m_mainResourceApplicationCache = ApplicationCacheGroup::fallbackCacheForMainRequest(request, &m_documentLoader);
132
133             if (scheduleLoadFallbackResourceFromApplicationCache(m_documentLoader.mainResourceLoader(), m_mainResourceApplicationCache.get()))
134                 return true;
135         }
136     }
137     return false;
138 }
139
140 void ApplicationCacheHost::mainResourceDataReceived(const char*, int, long long, bool)
141 {
142 }
143
144 void ApplicationCacheHost::failedLoadingMainResource()
145 {
146     auto* group = m_candidateApplicationCacheGroup;
147     if (!group && m_applicationCache) {
148         if (mainResourceApplicationCache()) {
149             // Even when the main resource is being loaded from an application cache, loading can fail if aborted.
150             return;
151         }
152         group = m_applicationCache->group();
153     }
154     
155     if (group)
156         group->failedLoadingMainResource(m_documentLoader);
157 }
158
159 void ApplicationCacheHost::finishedLoadingMainResource()
160 {
161     auto* group = candidateApplicationCacheGroup();
162     if (!group && applicationCache() && !mainResourceApplicationCache())
163         group = applicationCache()->group();
164     
165     if (group)
166         group->finishedLoadingMainResource(m_documentLoader);
167 }
168
169 bool ApplicationCacheHost::maybeLoadResource(ResourceLoader& loader, const ResourceRequest& request, const URL& originalURL)
170 {
171     if (loader.options().applicationCacheMode != ApplicationCacheMode::Use)
172         return false;
173
174     if (!isApplicationCacheEnabled() && !isApplicationCacheBlockedForRequest(request))
175         return false;
176     
177     if (request.url() != originalURL)
178         return false;
179
180 #if ENABLE(SERVICE_WORKER)
181     if (loader.options().serviceWorkerRegistrationIdentifier)
182         return false;
183 #endif
184
185     ApplicationCacheResource* resource;
186     if (!shouldLoadResourceFromApplicationCache(request, resource))
187         return false;
188
189     if (resource)
190         m_documentLoader.scheduleSubstituteResourceLoad(loader, *resource);
191     else
192         m_documentLoader.scheduleCannotShowURLError(loader);
193     return true;
194 }
195
196 bool ApplicationCacheHost::maybeLoadFallbackForRedirect(ResourceLoader* resourceLoader, ResourceRequest& request, const ResourceResponse& redirectResponse)
197 {
198     if (resourceLoader && resourceLoader->options().applicationCacheMode != ApplicationCacheMode::Use)
199         return false;
200
201     if (!redirectResponse.isNull() && !protocolHostAndPortAreEqual(request.url(), redirectResponse.url())) {
202         if (scheduleLoadFallbackResourceFromApplicationCache(resourceLoader))
203             return true;
204     }
205     return false;
206 }
207
208 bool ApplicationCacheHost::maybeLoadFallbackForResponse(ResourceLoader* resourceLoader, const ResourceResponse& response)
209 {
210     if (resourceLoader && resourceLoader->options().applicationCacheMode != ApplicationCacheMode::Use)
211         return false;
212
213     if (response.httpStatusCode() / 100 == 4 || response.httpStatusCode() / 100 == 5) {
214         if (scheduleLoadFallbackResourceFromApplicationCache(resourceLoader))
215             return true;
216     }
217     return false;
218 }
219
220 bool ApplicationCacheHost::maybeLoadFallbackForError(ResourceLoader* resourceLoader, const ResourceError& error)
221 {
222     if (resourceLoader && resourceLoader->options().applicationCacheMode != ApplicationCacheMode::Use)
223         return false;
224
225     if (!error.isCancellation()) {
226         if (resourceLoader == m_documentLoader.mainResourceLoader())
227             return maybeLoadFallbackForMainError(resourceLoader->request(), error);
228         if (scheduleLoadFallbackResourceFromApplicationCache(resourceLoader))
229             return true;
230     }
231     return false;
232 }
233
234 URL ApplicationCacheHost::createFileURL(const String& path)
235 {
236     // FIXME: Can we just use fileURLWithFileSystemPath instead?
237 #if USE(CF) && PLATFORM(WIN)
238     URL url(adoptCF(CFURLCreateWithFileSystemPath(0, path.createCFString().get(), kCFURLWindowsPathStyle, false)).get());
239 #else
240     URL url;
241     url.setProtocol("file"_s);
242     url.setPath(path);
243 #endif
244     return url;
245 }
246
247 static inline RefPtr<SharedBuffer> bufferFromResource(ApplicationCacheResource& resource)
248 {
249     // FIXME: Clients probably do not need a copy of the SharedBuffer.
250     // Remove the call to copy() once we ensure SharedBuffer will not be modified.
251     if (resource.path().isEmpty())
252         return resource.data().copy();
253     return SharedBuffer::createWithContentsOfFile(resource.path());
254 }
255
256 bool ApplicationCacheHost::maybeLoadSynchronously(ResourceRequest& request, ResourceError& error, ResourceResponse& response, RefPtr<SharedBuffer>& data)
257 {
258     ApplicationCacheResource* resource;
259     if (!shouldLoadResourceFromApplicationCache(request, resource))
260         return false;
261
262     auto responseData = resource ? bufferFromResource(*resource) : nullptr;
263     if (!responseData) {
264         error = m_documentLoader.frameLoader()->client().cannotShowURLError(request);
265         return true;
266     }
267
268     response = resource->response();
269     data = WTFMove(responseData);
270     return true;
271 }
272
273 void ApplicationCacheHost::maybeLoadFallbackSynchronously(const ResourceRequest& request, ResourceError& error, ResourceResponse& response, RefPtr<SharedBuffer>& data)
274 {
275     // If normal loading results in a redirect to a resource with another origin (indicative of a captive portal), or a 4xx or 5xx status code or equivalent,
276     // or if there were network errors (but not if the user canceled the download), then instead get, from the cache, the resource of the fallback entry
277     // corresponding to the matched namespace.
278     if ((!error.isNull() && !error.isCancellation())
279          || response.httpStatusCode() / 100 == 4 || response.httpStatusCode() / 100 == 5
280          || !protocolHostAndPortAreEqual(request.url(), response.url())) {
281         ApplicationCacheResource* resource;
282         if (getApplicationCacheFallbackResource(request, resource)) {
283             response = resource->response();
284             // FIXME: Clients proably do not need a copy of the SharedBuffer.
285             // Remove the call to copy() once we ensure SharedBuffer will not be modified.
286             data = resource->data().copy();
287         }
288     }
289 }
290
291 bool ApplicationCacheHost::canCacheInPageCache()
292 {
293     return !applicationCache() && !candidateApplicationCacheGroup();
294 }
295
296 void ApplicationCacheHost::setDOMApplicationCache(DOMApplicationCache* domApplicationCache)
297 {
298     ASSERT(!m_domApplicationCache || !domApplicationCache);
299     m_domApplicationCache = makeWeakPtr(domApplicationCache);
300 }
301
302 void ApplicationCacheHost::notifyDOMApplicationCache(const AtomicString& eventType, int total, int done)
303 {
304     if (eventType != eventNames().progressEvent)
305         InspectorInstrumentation::updateApplicationCacheStatus(m_documentLoader.frame());
306
307     if (m_defersEvents) {
308         // Event dispatching is deferred until document.onload has fired.
309         m_deferredEvents.append({ eventType, total, done });
310         return;
311     }
312
313     dispatchDOMEvent(eventType, total, done);
314 }
315
316 void ApplicationCacheHost::stopLoadingInFrame(Frame& frame)
317 {
318     ASSERT(!m_applicationCache || !m_candidateApplicationCacheGroup || m_applicationCache->group() == m_candidateApplicationCacheGroup);
319
320     if (m_candidateApplicationCacheGroup)
321         m_candidateApplicationCacheGroup->stopLoadingInFrame(frame);
322     else if (m_applicationCache)
323         m_applicationCache->group()->stopLoadingInFrame(frame);
324 }
325
326 void ApplicationCacheHost::stopDeferringEvents()
327 {
328     Ref<DocumentLoader> protect(m_documentLoader);
329
330     // Note, do not cache the size in a local variable.
331     // This code needs to properly handle the case where more events are added to
332     // m_deferredEvents while iterating it. This is why we don't use a modern for loop.
333     for (size_t i = 0; i < m_deferredEvents.size(); ++i) {
334         auto& event = m_deferredEvents[i];
335         dispatchDOMEvent(event.eventType, event.progressTotal, event.progressDone);
336     }
337
338     m_deferredEvents.clear();
339     m_defersEvents = false;
340 }
341
342 Vector<ApplicationCacheHost::ResourceInfo> ApplicationCacheHost::resourceList()
343 {
344     auto* cache = applicationCache();
345     if (!cache || !cache->isComplete())
346         return { };
347
348     return WTF::map(cache->resources(), [] (auto& urlAndResource) -> ApplicationCacheHost::ResourceInfo {
349         ASSERT(urlAndResource.value);
350         auto& resource = *urlAndResource.value;
351
352         unsigned type = resource.type();
353         bool isMaster = type & ApplicationCacheResource::Master;
354         bool isManifest = type & ApplicationCacheResource::Manifest;
355         bool isExplicit = type & ApplicationCacheResource::Explicit;
356         bool isForeign = type & ApplicationCacheResource::Foreign;
357         bool isFallback = type & ApplicationCacheResource::Fallback;
358
359         return { resource.url(), isMaster, isManifest, isFallback, isForeign, isExplicit, resource.estimatedSizeInStorage() };
360     });
361 }
362
363 ApplicationCacheHost::CacheInfo ApplicationCacheHost::applicationCacheInfo()
364 {
365     auto* cache = applicationCache();
366     if (!cache || !cache->isComplete())
367         return { { }, 0, 0, 0 };
368
369     // FIXME: Add "Creation Time" and "Update Time" to Application Caches.
370     return { cache->manifestResource()->url(), 0, 0, cache->estimatedSizeInStorage() };
371 }
372
373 static Ref<Event> createApplicationCacheEvent(const AtomicString& eventType, int total, int done)
374 {
375     if (eventType == eventNames().progressEvent)
376         return ProgressEvent::create(eventType, true, done, total);
377     return Event::create(eventType, Event::CanBubble::No, Event::IsCancelable::No);
378 }
379
380 void ApplicationCacheHost::dispatchDOMEvent(const AtomicString& eventType, int total, int done)
381 {
382     if (!m_domApplicationCache || !m_domApplicationCache->frame())
383         return;
384
385     m_domApplicationCache->dispatchEvent(createApplicationCacheEvent(eventType, total, done));
386 }
387
388 void ApplicationCacheHost::setCandidateApplicationCacheGroup(ApplicationCacheGroup* group)
389 {
390     ASSERT(!m_applicationCache);
391     m_candidateApplicationCacheGroup = group;
392 }
393     
394 void ApplicationCacheHost::setApplicationCache(RefPtr<ApplicationCache>&& applicationCache)
395 {
396     if (m_candidateApplicationCacheGroup) {
397         ASSERT(!m_applicationCache);
398         m_candidateApplicationCacheGroup = nullptr;
399     }
400     m_applicationCache = WTFMove(applicationCache);
401 }
402
403 bool ApplicationCacheHost::shouldLoadResourceFromApplicationCache(const ResourceRequest& originalRequest, ApplicationCacheResource*& resource)
404 {
405     auto* cache = applicationCache();
406     if (!cache || !cache->isComplete())
407         return false;
408
409     ResourceRequest request(originalRequest);
410     if (auto* loaderFrame = m_documentLoader.frame()) {
411         if (auto* document = loaderFrame->document())
412             document->contentSecurityPolicy()->upgradeInsecureRequestIfNeeded(request, ContentSecurityPolicy::InsecureRequestType::Load);
413     }
414     
415     // If the resource is not to be fetched using the HTTP GET mechanism or equivalent, or if its URL has a different
416     // <scheme> component than the application cache's manifest, then fetch the resource normally.
417     if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request) || !equalIgnoringASCIICase(request.url().protocol(), cache->manifestResource()->url().protocol()))
418         return false;
419
420     // If the resource's URL is an master entry, the manifest, an explicit entry, or a fallback entry
421     // in the application cache, then get the resource from the cache (instead of fetching it).
422     resource = cache->resourceForURL(request.url());
423
424     // Resources that match fallback namespaces or online whitelist entries are fetched from the network,
425     // unless they are also cached.
426     if (!resource && (cache->allowsAllNetworkRequests() || cache->urlMatchesFallbackNamespace(request.url()) || cache->isURLInOnlineWhitelist(request.url())))
427         return false;
428
429     // Resources that are not present in the manifest will always fail to load (at least, after the
430     // cache has been primed the first time), making the testing of offline applications simpler.
431     return true;
432 }
433
434 bool ApplicationCacheHost::getApplicationCacheFallbackResource(const ResourceRequest& request, ApplicationCacheResource*& resource, ApplicationCache* cache)
435 {
436     if (!cache) {
437         cache = applicationCache();
438         if (!cache)
439             return false;
440     }
441     if (!cache->isComplete())
442         return false;
443     
444     // If the resource is not a HTTP/HTTPS GET, then abort
445     if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request))
446         return false;
447
448     URL fallbackURL;
449     if (cache->isURLInOnlineWhitelist(request.url()))
450         return false;
451     if (!cache->urlMatchesFallbackNamespace(request.url(), &fallbackURL))
452         return false;
453
454     resource = cache->resourceForURL(fallbackURL);
455     ASSERT(resource);
456     return true;
457 }
458
459 bool ApplicationCacheHost::scheduleLoadFallbackResourceFromApplicationCache(ResourceLoader* loader, ApplicationCache* cache)
460 {
461     if (!loader)
462         return false;
463
464     if (!isApplicationCacheEnabled() && !isApplicationCacheBlockedForRequest(loader->request()))
465         return false;
466
467 #if ENABLE(SERVICE_WORKER)
468     if (loader->options().serviceWorkerRegistrationIdentifier)
469         return false;
470 #endif
471
472     ApplicationCacheResource* resource;
473     if (!getApplicationCacheFallbackResource(loader->request(), resource, cache))
474         return false;
475
476     loader->willSwitchToSubstituteResource();
477     m_documentLoader.scheduleSubstituteResourceLoad(*loader, *resource);
478     return true;
479 }
480
481 ApplicationCacheHost::Status ApplicationCacheHost::status() const
482 {
483     auto* cache = applicationCache();
484     if (!cache)
485         return UNCACHED;
486
487     switch (cache->group()->updateStatus()) {
488     case ApplicationCacheGroup::Checking:
489         return CHECKING;
490     case ApplicationCacheGroup::Downloading:
491         return DOWNLOADING;
492     case ApplicationCacheGroup::Idle:
493         if (cache->group()->isObsolete())
494             return OBSOLETE;
495         if (cache != cache->group()->newestCache())
496             return UPDATEREADY;
497         return IDLE;
498     }
499
500     ASSERT_NOT_REACHED();
501     return UNCACHED;
502 }
503
504 bool ApplicationCacheHost::update()
505 {
506     auto* cache = applicationCache();
507     if (!cache)
508         return false;
509     auto* frame = m_documentLoader.frame();
510     if (!frame)
511         return false;
512     cache->group()->update(*frame, ApplicationCacheUpdateWithoutBrowsingContext);
513     return true;
514 }
515
516 bool ApplicationCacheHost::swapCache()
517 {
518     auto* cache = applicationCache();
519     if (!cache)
520         return false;
521
522     // If the group of application caches to which cache belongs has the lifecycle status obsolete, unassociate document from cache.
523     if (cache->group()->isObsolete()) {
524         cache->group()->disassociateDocumentLoader(m_documentLoader);
525         return true;
526     }
527
528     // If there is no newer cache, raise an InvalidStateError exception.
529     auto* newestCache = cache->group()->newestCache();
530     if (cache == newestCache)
531         return false;
532     
533     ASSERT(cache->group() == newestCache->group());
534     setApplicationCache(newestCache);
535     InspectorInstrumentation::updateApplicationCacheStatus(m_documentLoader.frame());
536     return true;
537 }
538
539 void ApplicationCacheHost::abort()
540 {
541     auto* frame = m_documentLoader.frame();
542     if (!frame)
543         return;
544     if (auto* cacheGroup = candidateApplicationCacheGroup())
545         cacheGroup->abort(*frame);
546     else if (auto* cache = applicationCache())
547         cache->group()->abort(*frame);
548 }
549
550 bool ApplicationCacheHost::isApplicationCacheEnabled()
551 {
552     return m_documentLoader.frame() && m_documentLoader.frame()->settings().offlineWebApplicationCacheEnabled() && !m_documentLoader.frame()->page()->usesEphemeralSession();
553 }
554
555 bool ApplicationCacheHost::isApplicationCacheBlockedForRequest(const ResourceRequest& request)
556 {
557     auto* frame = m_documentLoader.frame();
558     if (!frame)
559         return false;
560     if (frame->isMainFrame())
561         return false;
562     return !SecurityOrigin::create(request.url())->canAccessApplicationCache(frame->document()->topOrigin());
563 }
564
565 }  // namespace WebCore