d08c3f1f165dd4b10ad77d25387b278980bc4fda
[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     Vector<ResourceInfo> result;
331
332     auto* cache = applicationCache();
333     if (!cache || !cache->isComplete())
334         return result;
335
336     result.reserveInitialCapacity(cache->resources().size());
337
338     for (auto& urlAndResource : cache->resources()) {
339         ASSERT(urlAndResource.value);
340         auto& resource = *urlAndResource.value;
341
342         unsigned type = resource.type();
343         bool isMaster = type & ApplicationCacheResource::Master;
344         bool isManifest = type & ApplicationCacheResource::Manifest;
345         bool isExplicit = type & ApplicationCacheResource::Explicit;
346         bool isForeign = type & ApplicationCacheResource::Foreign;
347         bool isFallback = type & ApplicationCacheResource::Fallback;
348
349         result.uncheckedAppend({ resource.url(), isMaster, isManifest, isFallback, isForeign, isExplicit, resource.estimatedSizeInStorage() });
350     }
351
352     return result;
353 }
354
355 ApplicationCacheHost::CacheInfo ApplicationCacheHost::applicationCacheInfo()
356 {
357     auto* cache = applicationCache();
358     if (!cache || !cache->isComplete())
359         return { { }, 0, 0, 0 };
360
361     // FIXME: Add "Creation Time" and "Update Time" to Application Caches.
362     return { cache->manifestResource()->url(), 0, 0, cache->estimatedSizeInStorage() };
363 }
364
365 static Ref<Event> createApplicationCacheEvent(const AtomicString& eventType, int total, int done)
366 {
367     if (eventType == eventNames().progressEvent)
368         return ProgressEvent::create(eventType, true, done, total);
369     return Event::create(eventType, false, false);
370 }
371
372 void ApplicationCacheHost::dispatchDOMEvent(const AtomicString& eventType, int total, int done)
373 {
374     if (!m_domApplicationCache)
375         return;
376     m_domApplicationCache->dispatchEvent(createApplicationCacheEvent(eventType, total, done));
377 }
378
379 void ApplicationCacheHost::setCandidateApplicationCacheGroup(ApplicationCacheGroup* group)
380 {
381     ASSERT(!m_applicationCache);
382     m_candidateApplicationCacheGroup = group;
383 }
384     
385 void ApplicationCacheHost::setApplicationCache(RefPtr<ApplicationCache>&& applicationCache)
386 {
387     if (m_candidateApplicationCacheGroup) {
388         ASSERT(!m_applicationCache);
389         m_candidateApplicationCacheGroup = nullptr;
390     }
391     m_applicationCache = WTFMove(applicationCache);
392 }
393
394 bool ApplicationCacheHost::shouldLoadResourceFromApplicationCache(const ResourceRequest& originalRequest, ApplicationCacheResource*& resource)
395 {
396     auto* cache = applicationCache();
397     if (!cache || !cache->isComplete())
398         return false;
399
400     ResourceRequest request(originalRequest);
401     if (auto* loaderFrame = m_documentLoader.frame()) {
402         if (auto* document = loaderFrame->document())
403             document->contentSecurityPolicy()->upgradeInsecureRequestIfNeeded(request, ContentSecurityPolicy::InsecureRequestType::Load);
404     }
405     
406     // If the resource is not to be fetched using the HTTP GET mechanism or equivalent, or if its URL has a different
407     // <scheme> component than the application cache's manifest, then fetch the resource normally.
408     if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request) || !equalIgnoringASCIICase(request.url().protocol(), cache->manifestResource()->url().protocol()))
409         return false;
410
411     // If the resource's URL is an master entry, the manifest, an explicit entry, or a fallback entry
412     // in the application cache, then get the resource from the cache (instead of fetching it).
413     resource = cache->resourceForURL(request.url());
414
415     // Resources that match fallback namespaces or online whitelist entries are fetched from the network,
416     // unless they are also cached.
417     if (!resource && (cache->allowsAllNetworkRequests() || cache->urlMatchesFallbackNamespace(request.url()) || cache->isURLInOnlineWhitelist(request.url())))
418         return false;
419
420     // Resources that are not present in the manifest will always fail to load (at least, after the
421     // cache has been primed the first time), making the testing of offline applications simpler.
422     return true;
423 }
424
425 bool ApplicationCacheHost::getApplicationCacheFallbackResource(const ResourceRequest& request, ApplicationCacheResource*& resource, ApplicationCache* cache)
426 {
427     if (!cache) {
428         cache = applicationCache();
429         if (!cache)
430             return false;
431     }
432     if (!cache->isComplete())
433         return false;
434     
435     // If the resource is not a HTTP/HTTPS GET, then abort
436     if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request))
437         return false;
438
439     URL fallbackURL;
440     if (cache->isURLInOnlineWhitelist(request.url()))
441         return false;
442     if (!cache->urlMatchesFallbackNamespace(request.url(), &fallbackURL))
443         return false;
444
445     resource = cache->resourceForURL(fallbackURL);
446     ASSERT(resource);
447     return true;
448 }
449
450 bool ApplicationCacheHost::scheduleLoadFallbackResourceFromApplicationCache(ResourceLoader* loader, ApplicationCache* cache)
451 {
452     if (!isApplicationCacheEnabled() && !isApplicationCacheBlockedForRequest(loader->request()))
453         return false;
454
455     ApplicationCacheResource* resource;
456     if (!getApplicationCacheFallbackResource(loader->request(), resource, cache))
457         return false;
458
459     loader->willSwitchToSubstituteResource();
460     m_documentLoader.scheduleSubstituteResourceLoad(*loader, *resource);
461     return true;
462 }
463
464 ApplicationCacheHost::Status ApplicationCacheHost::status() const
465 {
466     auto* cache = applicationCache();
467     if (!cache)
468         return UNCACHED;
469
470     switch (cache->group()->updateStatus()) {
471     case ApplicationCacheGroup::Checking:
472         return CHECKING;
473     case ApplicationCacheGroup::Downloading:
474         return DOWNLOADING;
475     case ApplicationCacheGroup::Idle:
476         if (cache->group()->isObsolete())
477             return OBSOLETE;
478         if (cache != cache->group()->newestCache())
479             return UPDATEREADY;
480         return IDLE;
481     }
482
483     ASSERT_NOT_REACHED();
484     return UNCACHED;
485 }
486
487 bool ApplicationCacheHost::update()
488 {
489     auto* cache = applicationCache();
490     if (!cache)
491         return false;
492     auto* frame = m_documentLoader.frame();
493     if (!frame)
494         return false;
495     cache->group()->update(*frame, ApplicationCacheUpdateWithoutBrowsingContext);
496     return true;
497 }
498
499 bool ApplicationCacheHost::swapCache()
500 {
501     auto* cache = applicationCache();
502     if (!cache)
503         return false;
504
505     // If the group of application caches to which cache belongs has the lifecycle status obsolete, unassociate document from cache.
506     if (cache->group()->isObsolete()) {
507         cache->group()->disassociateDocumentLoader(m_documentLoader);
508         return true;
509     }
510
511     // If there is no newer cache, raise an InvalidStateError exception.
512     auto* newestCache = cache->group()->newestCache();
513     if (cache == newestCache)
514         return false;
515     
516     ASSERT(cache->group() == newestCache->group());
517     setApplicationCache(newestCache);
518     InspectorInstrumentation::updateApplicationCacheStatus(m_documentLoader.frame());
519     return true;
520 }
521
522 void ApplicationCacheHost::abort()
523 {
524     auto* frame = m_documentLoader.frame();
525     if (!frame)
526         return;
527     if (auto* cacheGroup = candidateApplicationCacheGroup())
528         cacheGroup->abort(*frame);
529     else if (auto* cache = applicationCache())
530         cache->group()->abort(*frame);
531 }
532
533 bool ApplicationCacheHost::isApplicationCacheEnabled()
534 {
535     return m_documentLoader.frame() && m_documentLoader.frame()->settings().offlineWebApplicationCacheEnabled() && !m_documentLoader.frame()->page()->usesEphemeralSession();
536 }
537
538 bool ApplicationCacheHost::isApplicationCacheBlockedForRequest(const ResourceRequest& request)
539 {
540     auto* frame = m_documentLoader.frame();
541     if (!frame)
542         return false;
543     if (frame->isMainFrame())
544         return false;
545     return !SecurityOrigin::create(request.url())->canAccessApplicationCache(frame->document()->topOrigin());
546 }
547
548 }  // namespace WebCore