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