37c2ecf7dd21f33509ed710aecb2c6a5557d9074
[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 "FileSystem.h"
36 #include "Frame.h"
37 #include "FrameLoader.h"
38 #include "FrameLoaderClient.h"
39 #include "InspectorInstrumentation.h"
40 #include "MainFrame.h"
41 #include "ProgressEvent.h"
42 #include "ResourceHandle.h"
43 #include "ResourceRequest.h"
44 #include "Settings.h"
45 #include "SubresourceLoader.h"
46
47 namespace WebCore {
48
49 ApplicationCacheHost::ApplicationCacheHost(DocumentLoader& documentLoader)
50     : m_domApplicationCache(nullptr)
51     , m_documentLoader(documentLoader)
52     , m_defersEvents(true)
53     , m_candidateApplicationCacheGroup(nullptr)
54 {
55 }
56
57 ApplicationCacheHost::~ApplicationCacheHost()
58 {
59     ASSERT(!m_applicationCache || !m_candidateApplicationCacheGroup || m_applicationCache->group() == m_candidateApplicationCacheGroup);
60
61     if (m_applicationCache)
62         m_applicationCache->group()->disassociateDocumentLoader(&m_documentLoader);
63     else if (m_candidateApplicationCacheGroup)
64         m_candidateApplicationCacheGroup->disassociateDocumentLoader(&m_documentLoader);
65 }
66
67 void ApplicationCacheHost::selectCacheWithoutManifest()
68 {
69     ApplicationCacheGroup::selectCacheWithoutManifestURL(m_documentLoader.frame());
70 }
71
72 void ApplicationCacheHost::selectCacheWithManifest(const URL& manifestURL)
73 {
74     ApplicationCacheGroup::selectCache(m_documentLoader.frame(), manifestURL);
75 }
76
77 void ApplicationCacheHost::maybeLoadMainResource(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(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     // This method is here to facilitate alternate implemetations of this interface by the host browser.
143 }
144
145 void ApplicationCacheHost::failedLoadingMainResource()
146 {
147     ApplicationCacheGroup* 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     ApplicationCacheGroup* 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     return false;
192 }
193
194 bool ApplicationCacheHost::maybeLoadFallbackForResponse(ResourceLoader* resourceLoader, const ResourceResponse& response)
195 {
196     if (response.httpStatusCode() / 100 == 4 || response.httpStatusCode() / 100 == 5)
197         if (scheduleLoadFallbackResourceFromApplicationCache(resourceLoader))
198             return true;
199     return false;
200 }
201
202 bool ApplicationCacheHost::maybeLoadFallbackForError(ResourceLoader* resourceLoader, const ResourceError& error)
203 {
204     if (!error.isCancellation()) {
205         if (resourceLoader == m_documentLoader.mainResourceLoader())
206             return maybeLoadFallbackForMainError(resourceLoader->request(), error);
207         if (scheduleLoadFallbackResourceFromApplicationCache(resourceLoader))
208             return true;
209     }
210     return false;
211 }
212
213 URL ApplicationCacheHost::createFileURL(const String& path)
214 {
215     // FIXME: Can we just use fileURLWithFileSystemPath instead?
216
217     // fileURLWithFileSystemPath function is not suitable because URL::setPath uses encodeWithURLEscapeSequences, which it notes
218     // does not correctly escape '#' and '?'. This function works for our purposes because
219     // app cache media files are always created with encodeForFileName(createCanonicalUUIDString()).
220
221 #if USE(CF) && PLATFORM(WIN)
222     RetainPtr<CFURLRef> cfURL = adoptCF(CFURLCreateWithFileSystemPath(0, path.createCFString().get(), kCFURLWindowsPathStyle, false));
223     URL url(cfURL.get());
224 #else
225     URL url;
226
227     url.setProtocol(ASCIILiteral("file"));
228     url.setPath(path);
229 #endif
230     return url;
231 }
232
233 bool ApplicationCacheHost::maybeLoadSynchronously(ResourceRequest& request, ResourceError& error, ResourceResponse& response, RefPtr<SharedBuffer>& data)
234 {
235     ApplicationCacheResource* resource;
236     if (shouldLoadResourceFromApplicationCache(request, resource)) {
237         if (resource) {
238             // FIXME: Clients proably do not need a copy of the SharedBuffer.
239             // Remove the call to copy() once we ensure SharedBuffer will not be modified.
240             if (resource->path().isEmpty())
241                 data = resource->data().copy();
242             else
243                 data = SharedBuffer::createWithContentsOfFile(resource->path());
244         }
245         if (!data)
246             error = m_documentLoader.frameLoader()->client().cannotShowURLError(request);
247         else
248             response = resource->response();
249         return true;
250     }
251     return false;
252 }
253
254 void ApplicationCacheHost::maybeLoadFallbackSynchronously(const ResourceRequest& request, ResourceError& error, ResourceResponse& response, RefPtr<SharedBuffer>& data)
255 {
256     // 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,
257     // 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
258     // corresponding to the matched namespace.
259     if ((!error.isNull() && !error.isCancellation())
260          || response.httpStatusCode() / 100 == 4 || response.httpStatusCode() / 100 == 5
261          || !protocolHostAndPortAreEqual(request.url(), response.url())) {
262         ApplicationCacheResource* resource;
263         if (getApplicationCacheFallbackResource(request, resource)) {
264             response = resource->response();
265             // FIXME: Clients proably do not need a copy of the SharedBuffer.
266             // Remove the call to copy() once we ensure SharedBuffer will not be modified.
267             data = resource->data().copy();
268         }
269     }
270 }
271
272 bool ApplicationCacheHost::canCacheInPageCache()
273 {
274     return !applicationCache() && !candidateApplicationCacheGroup();
275 }
276
277 void ApplicationCacheHost::setDOMApplicationCache(DOMApplicationCache* domApplicationCache)
278 {
279     ASSERT(!m_domApplicationCache || !domApplicationCache);
280     m_domApplicationCache = domApplicationCache;
281 }
282
283 void ApplicationCacheHost::notifyDOMApplicationCache(EventID id, int total, int done)
284 {
285     if (id != PROGRESS_EVENT)
286         InspectorInstrumentation::updateApplicationCacheStatus(m_documentLoader.frame());
287
288     if (m_defersEvents) {
289         // Event dispatching is deferred until document.onload has fired.
290         m_deferredEvents.append(DeferredEvent(id, total, done));
291         return;
292     }
293     dispatchDOMEvent(id, total, done);
294 }
295
296 void ApplicationCacheHost::stopLoadingInFrame(Frame* frame)
297 {
298     ASSERT(!m_applicationCache || !m_candidateApplicationCacheGroup || m_applicationCache->group() == m_candidateApplicationCacheGroup);
299
300     if (m_candidateApplicationCacheGroup)
301         m_candidateApplicationCacheGroup->stopLoadingInFrame(frame);
302     else if (m_applicationCache)
303         m_applicationCache->group()->stopLoadingInFrame(frame);
304 }
305
306 void ApplicationCacheHost::stopDeferringEvents()
307 {
308     Ref<DocumentLoader> protect(m_documentLoader);
309     for (auto& event : m_deferredEvents)
310         dispatchDOMEvent(event.eventID, event.progressTotal, event.progressDone);
311     m_deferredEvents.clear();
312     m_defersEvents = false;
313 }
314
315 void ApplicationCacheHost::fillResourceList(ResourceInfoList* resources)
316 {
317     ApplicationCache* cache = applicationCache();
318     if (!cache || !cache->isComplete())
319         return;
320
321     for (const auto& urlAndResource : cache->resources()) {
322         ApplicationCacheResource* resource = urlAndResource.value.get();
323
324         unsigned type = resource->type();
325         bool isMaster = type & ApplicationCacheResource::Master;
326         bool isManifest = type & ApplicationCacheResource::Manifest;
327         bool isExplicit = type & ApplicationCacheResource::Explicit;
328         bool isForeign = type & ApplicationCacheResource::Foreign;
329         bool isFallback = type & ApplicationCacheResource::Fallback;
330
331         resources->append(ResourceInfo(resource->url(), isMaster, isManifest, isFallback, isForeign, isExplicit, resource->estimatedSizeInStorage()));
332     }
333 }
334
335 ApplicationCacheHost::CacheInfo ApplicationCacheHost::applicationCacheInfo()
336 {
337     ApplicationCache* cache = applicationCache();
338     if (!cache || !cache->isComplete())
339         return CacheInfo(URL(), 0, 0, 0);
340
341     // FIXME: Add "Creation Time" and "Update Time" to Application Caches.
342     return CacheInfo(cache->manifestResource()->url(), 0, 0, cache->estimatedSizeInStorage());
343 }
344
345 void ApplicationCacheHost::dispatchDOMEvent(EventID id, int total, int done)
346 {
347     if (m_domApplicationCache) {
348         const AtomicString& eventType = DOMApplicationCache::toEventType(id);
349         RefPtr<Event> event;
350         if (id == PROGRESS_EVENT)
351             event = ProgressEvent::create(eventType, true, done, total);
352         else
353             event = Event::create(eventType, false, false);
354         m_domApplicationCache->dispatchEvent(*event);
355     }
356 }
357
358 void ApplicationCacheHost::setCandidateApplicationCacheGroup(ApplicationCacheGroup* group)
359 {
360     ASSERT(!m_applicationCache);
361     m_candidateApplicationCacheGroup = group;
362 }
363     
364 void ApplicationCacheHost::setApplicationCache(PassRefPtr<ApplicationCache> applicationCache)
365 {
366     if (m_candidateApplicationCacheGroup) {
367         ASSERT(!m_applicationCache);
368         m_candidateApplicationCacheGroup = nullptr;
369     }
370
371     m_applicationCache = applicationCache;
372 }
373
374 bool ApplicationCacheHost::shouldLoadResourceFromApplicationCache(const ResourceRequest& originalRequest, ApplicationCacheResource*& resource)
375 {
376     ApplicationCache* cache = applicationCache();
377     if (!cache || !cache->isComplete())
378         return false;
379
380     ResourceRequest request(originalRequest);
381     if (Frame* loaderFrame = m_documentLoader.frame()) {
382         if (Document* document = loaderFrame->document())
383             document->contentSecurityPolicy()->upgradeInsecureRequestIfNeeded(request, ContentSecurityPolicy::InsecureRequestType::Load);
384     }
385     
386     // If the resource is not to be fetched using the HTTP GET mechanism or equivalent, or if its URL has a different
387     // <scheme> component than the application cache's manifest, then fetch the resource normally.
388     if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request) || !equalIgnoringASCIICase(request.url().protocol(), cache->manifestResource()->url().protocol()))
389         return false;
390
391     // If the resource's URL is an master entry, the manifest, an explicit entry, or a fallback entry
392     // in the application cache, then get the resource from the cache (instead of fetching it).
393     resource = cache->resourceForURL(request.url());
394
395     // Resources that match fallback namespaces or online whitelist entries are fetched from the network,
396     // unless they are also cached.
397     if (!resource && (cache->allowsAllNetworkRequests() || cache->urlMatchesFallbackNamespace(request.url()) || cache->isURLInOnlineWhitelist(request.url())))
398         return false;
399
400     // Resources that are not present in the manifest will always fail to load (at least, after the
401     // cache has been primed the first time), making the testing of offline applications simpler.
402     return true;
403 }
404
405 bool ApplicationCacheHost::getApplicationCacheFallbackResource(const ResourceRequest& request, ApplicationCacheResource*& resource, ApplicationCache* cache)
406 {
407     if (!cache) {
408         cache = applicationCache();
409         if (!cache)
410             return false;
411     }
412     if (!cache->isComplete())
413         return false;
414     
415     // If the resource is not a HTTP/HTTPS GET, then abort
416     if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request))
417         return false;
418
419     URL fallbackURL;
420     if (cache->isURLInOnlineWhitelist(request.url()))
421         return false;
422     if (!cache->urlMatchesFallbackNamespace(request.url(), &fallbackURL))
423         return false;
424
425     resource = cache->resourceForURL(fallbackURL);
426     ASSERT(resource);
427
428     return true;
429 }
430
431 bool ApplicationCacheHost::scheduleLoadFallbackResourceFromApplicationCache(ResourceLoader* loader, ApplicationCache* cache)
432 {
433     if (!isApplicationCacheEnabled() && !isApplicationCacheBlockedForRequest(loader->request()))
434         return false;
435
436     ApplicationCacheResource* resource;
437     if (!getApplicationCacheFallbackResource(loader->request(), resource, cache))
438         return false;
439
440     loader->willSwitchToSubstituteResource();
441
442     m_documentLoader.scheduleSubstituteResourceLoad(*loader, *resource);
443
444     return true;
445 }
446
447 ApplicationCacheHost::Status ApplicationCacheHost::status() const
448 {
449     ApplicationCache* cache = applicationCache();    
450     if (!cache)
451         return UNCACHED;
452
453     switch (cache->group()->updateStatus()) {
454         case ApplicationCacheGroup::Checking:
455             return CHECKING;
456         case ApplicationCacheGroup::Downloading:
457             return DOWNLOADING;
458         case ApplicationCacheGroup::Idle: {
459             if (cache->group()->isObsolete())
460                 return OBSOLETE;
461             if (cache != cache->group()->newestCache())
462                 return UPDATEREADY;
463             return IDLE;
464         }
465     }
466
467     ASSERT_NOT_REACHED();
468     return UNCACHED;
469 }
470
471 bool ApplicationCacheHost::update()
472 {
473     ApplicationCache* cache = applicationCache();
474     if (!cache)
475         return false;
476     cache->group()->update(m_documentLoader.frame(), ApplicationCacheUpdateWithoutBrowsingContext);
477     return true;
478 }
479
480 bool ApplicationCacheHost::swapCache()
481 {
482     ApplicationCache* cache = applicationCache();
483     if (!cache)
484         return false;
485
486     // If the group of application caches to which cache belongs has the lifecycle status obsolete, unassociate document from cache.
487     if (cache->group()->isObsolete()) {
488         cache->group()->disassociateDocumentLoader(&m_documentLoader);
489         return true;
490     }
491
492     // If there is no newer cache, raise an INVALID_STATE_ERR exception.
493     ApplicationCache* newestCache = cache->group()->newestCache();
494     if (cache == newestCache)
495         return false;
496     
497     ASSERT(cache->group() == newestCache->group());
498     setApplicationCache(newestCache);
499     InspectorInstrumentation::updateApplicationCacheStatus(m_documentLoader.frame());
500     return true;
501 }
502
503 void ApplicationCacheHost::abort()
504 {
505     ApplicationCacheGroup* cacheGroup = candidateApplicationCacheGroup();
506     if (cacheGroup)
507         cacheGroup->abort(m_documentLoader.frame());
508     else {
509         ApplicationCache* cache = applicationCache();
510         if (cache)
511             cache->group()->abort(m_documentLoader.frame());
512     }
513 }
514
515 bool ApplicationCacheHost::isApplicationCacheEnabled()
516 {
517     return m_documentLoader.frame() && m_documentLoader.frame()->settings().offlineWebApplicationCacheEnabled() && !m_documentLoader.frame()->page()->usesEphemeralSession();
518 }
519
520 bool ApplicationCacheHost::isApplicationCacheBlockedForRequest(const ResourceRequest& request)
521 {
522     Frame* frame = m_documentLoader.frame();
523     if (!frame)
524         return false;
525
526     if (frame->isMainFrame())
527         return false;
528
529     Ref<SecurityOrigin> origin(SecurityOrigin::create(request.url()));
530     return !origin.get().canAccessApplicationCache(frame->document()->topOrigin());
531 }
532
533 }  // namespace WebCore