1 /*
2 * Copyright (C) 2008, 2009 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 #if ENABLE(OFFLINE_WEB_APPLICATIONS)
30
31 #include "ApplicationCache.h"
32 #include "ApplicationCacheGroup.h"
33 #include "ApplicationCacheResource.h"
34 #include "DocumentLoader.h"
35 #include "DOMApplicationCache.h"
36 #include "Frame.h"
37 #include "FrameLoader.h"
38 #include "FrameLoaderClient.h"
39 #include "MainResourceLoader.h"
40 #include "ResourceLoader.h"
41 #include "ResourceRequest.h"
42 #include "Settings.h"
43
44 namespace WebCore {
45
ApplicationCacheHost(DocumentLoader * documentLoader)46 ApplicationCacheHost::ApplicationCacheHost(DocumentLoader* documentLoader)
47 : m_domApplicationCache(0)
48 , m_documentLoader(documentLoader)
49 , m_candidateApplicationCacheGroup(0)
50 {
51 ASSERT(m_documentLoader);
52 }
53
~ApplicationCacheHost()54 ApplicationCacheHost::~ApplicationCacheHost()
55 {
56 if (m_applicationCache)
57 m_applicationCache->group()->disassociateDocumentLoader(m_documentLoader);
58 else if (m_candidateApplicationCacheGroup)
59 m_candidateApplicationCacheGroup->disassociateDocumentLoader(m_documentLoader);
60 }
61
selectCacheWithoutManifest()62 void ApplicationCacheHost::selectCacheWithoutManifest()
63 {
64 ApplicationCacheGroup::selectCacheWithoutManifestURL(m_documentLoader->frame());
65 }
66
selectCacheWithManifest(const KURL & manifestURL)67 void ApplicationCacheHost::selectCacheWithManifest(const KURL& manifestURL)
68 {
69 ApplicationCacheGroup::selectCache(m_documentLoader->frame(), manifestURL);
70 }
71
maybeLoadMainResource(ResourceRequest & request,SubstituteData & substituteData)72 void ApplicationCacheHost::maybeLoadMainResource(ResourceRequest& request, SubstituteData& substituteData)
73 {
74 // Check if this request should be loaded from the application cache
75 if (!substituteData.isValid() && isApplicationCacheEnabled()) {
76 ASSERT(!m_mainResourceApplicationCache);
77
78 m_mainResourceApplicationCache = ApplicationCacheGroup::cacheForMainRequest(request, m_documentLoader);
79
80 if (m_mainResourceApplicationCache) {
81 // Get the resource from the application cache. By definition, cacheForMainRequest() returns a cache that contains the resource.
82 ApplicationCacheResource* resource = m_mainResourceApplicationCache->resourceForRequest(request);
83 substituteData = SubstituteData(resource->data(),
84 resource->response().mimeType(),
85 resource->response().textEncodingName(), KURL());
86 }
87 }
88 }
89
maybeLoadFallbackForMainResponse(const ResourceRequest & request,const ResourceResponse & r)90 bool ApplicationCacheHost::maybeLoadFallbackForMainResponse(const ResourceRequest& request, const ResourceResponse& r)
91 {
92 if (r.httpStatusCode() / 100 == 4 || r.httpStatusCode() / 100 == 5) {
93 ASSERT(!m_mainResourceApplicationCache);
94 if (isApplicationCacheEnabled()) {
95 m_mainResourceApplicationCache = ApplicationCacheGroup::fallbackCacheForMainRequest(request, documentLoader());
96
97 if (scheduleLoadFallbackResourceFromApplicationCache(documentLoader()->mainResourceLoader(), m_mainResourceApplicationCache.get()))
98 return true;
99 }
100 }
101 return false;
102 }
103
maybeLoadFallbackForMainError(const ResourceRequest & request,const ResourceError & error)104 bool ApplicationCacheHost::maybeLoadFallbackForMainError(const ResourceRequest& request, const ResourceError& error)
105 {
106 if (!error.isCancellation()) {
107 ASSERT(!m_mainResourceApplicationCache);
108 if (isApplicationCacheEnabled()) {
109 m_mainResourceApplicationCache = ApplicationCacheGroup::fallbackCacheForMainRequest(request, m_documentLoader);
110
111 if (scheduleLoadFallbackResourceFromApplicationCache(documentLoader()->mainResourceLoader(), m_mainResourceApplicationCache.get()))
112 return true;
113 }
114 }
115 return false;
116 }
117
mainResourceDataReceived(const char *,int,long long,bool)118 void ApplicationCacheHost::mainResourceDataReceived(const char*, int, long long, bool)
119 {
120 // This method is here to facilitate alternate implemetations of this interface by the host browser.
121 }
122
failedLoadingMainResource()123 void ApplicationCacheHost::failedLoadingMainResource()
124 {
125 ApplicationCacheGroup* group = m_candidateApplicationCacheGroup;
126 if (!group && m_applicationCache) {
127 ASSERT(!mainResourceApplicationCache()); // If the main resource were loaded from a cache, it wouldn't fail.
128 group = m_applicationCache->group();
129 }
130
131 if (group)
132 group->failedLoadingMainResource(m_documentLoader);
133 }
134
finishedLoadingMainResource()135 void ApplicationCacheHost::finishedLoadingMainResource()
136 {
137 ApplicationCacheGroup* group = candidateApplicationCacheGroup();
138 if (!group && applicationCache() && !mainResourceApplicationCache())
139 group = applicationCache()->group();
140
141 if (group)
142 group->finishedLoadingMainResource(m_documentLoader);
143 }
144
maybeLoadResource(ResourceLoader * loader,ResourceRequest & request,const KURL & originalURL)145 bool ApplicationCacheHost::maybeLoadResource(ResourceLoader* loader, ResourceRequest& request, const KURL& originalURL)
146 {
147 if (!isApplicationCacheEnabled())
148 return false;
149
150 if (request.url() != originalURL)
151 return false;
152
153 ApplicationCacheResource* resource;
154 if (!shouldLoadResourceFromApplicationCache(request, resource))
155 return false;
156
157 m_documentLoader->m_pendingSubstituteResources.set(loader, resource);
158 m_documentLoader->deliverSubstituteResourcesAfterDelay();
159
160 return true;
161 }
162
maybeLoadFallbackForRedirect(ResourceLoader * resourceLoader,ResourceRequest & request,const ResourceResponse & redirectResponse)163 bool ApplicationCacheHost::maybeLoadFallbackForRedirect(ResourceLoader* resourceLoader, ResourceRequest& request, const ResourceResponse& redirectResponse)
164 {
165 if (!redirectResponse.isNull() && !protocolHostAndPortAreEqual(request.url(), redirectResponse.url()))
166 if (scheduleLoadFallbackResourceFromApplicationCache(resourceLoader))
167 return true;
168 return false;
169 }
170
maybeLoadFallbackForResponse(ResourceLoader * resourceLoader,const ResourceResponse & response)171 bool ApplicationCacheHost::maybeLoadFallbackForResponse(ResourceLoader* resourceLoader, const ResourceResponse& response)
172 {
173 if (response.httpStatusCode() / 100 == 4 || response.httpStatusCode() / 100 == 5)
174 if (scheduleLoadFallbackResourceFromApplicationCache(resourceLoader))
175 return true;
176 return false;
177 }
178
maybeLoadFallbackForError(ResourceLoader * resourceLoader,const ResourceError & error)179 bool ApplicationCacheHost::maybeLoadFallbackForError(ResourceLoader* resourceLoader, const ResourceError& error)
180 {
181 if (!error.isCancellation())
182 if (scheduleLoadFallbackResourceFromApplicationCache(resourceLoader))
183 return true;
184 return false;
185 }
186
maybeLoadSynchronously(ResourceRequest & request,ResourceError & error,ResourceResponse & response,Vector<char> & data)187 bool ApplicationCacheHost::maybeLoadSynchronously(ResourceRequest& request, ResourceError& error, ResourceResponse& response, Vector<char>& data)
188 {
189 ApplicationCacheResource* resource;
190 if (shouldLoadResourceFromApplicationCache(request, resource)) {
191 if (resource) {
192 response = resource->response();
193 data.append(resource->data()->data(), resource->data()->size());
194 } else {
195 error = documentLoader()->frameLoader()->client()->cannotShowURLError(request);
196 }
197 return true;
198 }
199 return false;
200 }
201
maybeLoadFallbackSynchronously(const ResourceRequest & request,ResourceError & error,ResourceResponse & response,Vector<char> & data)202 void ApplicationCacheHost::maybeLoadFallbackSynchronously(const ResourceRequest& request, ResourceError& error, ResourceResponse& response, Vector<char>& data)
203 {
204 // 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,
205 // 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
206 // corresponding to the matched namespace.
207 if ((!error.isNull() && !error.isCancellation())
208 || response.httpStatusCode() / 100 == 4 || response.httpStatusCode() / 100 == 5
209 || !protocolHostAndPortAreEqual(request.url(), response.url())) {
210 ApplicationCacheResource* resource;
211 if (getApplicationCacheFallbackResource(request, resource)) {
212 response = resource->response();
213 data.clear();
214 data.append(resource->data()->data(), resource->data()->size());
215 }
216 }
217 }
218
canCacheInPageCache() const219 bool ApplicationCacheHost::canCacheInPageCache() const
220 {
221 return !applicationCache() && !candidateApplicationCacheGroup();
222 }
223
setDOMApplicationCache(DOMApplicationCache * domApplicationCache)224 void ApplicationCacheHost::setDOMApplicationCache(DOMApplicationCache* domApplicationCache)
225 {
226 ASSERT(!m_domApplicationCache || !domApplicationCache);
227 m_domApplicationCache = domApplicationCache;
228 }
229
notifyEventListener(EventID id)230 void ApplicationCacheHost::notifyEventListener(EventID id)
231 {
232 if (m_domApplicationCache)
233 m_domApplicationCache->callEventListener(id);
234 }
235
setCandidateApplicationCacheGroup(ApplicationCacheGroup * group)236 void ApplicationCacheHost::setCandidateApplicationCacheGroup(ApplicationCacheGroup* group)
237 {
238 ASSERT(!m_applicationCache);
239 m_candidateApplicationCacheGroup = group;
240 }
241
setApplicationCache(PassRefPtr<ApplicationCache> applicationCache)242 void ApplicationCacheHost::setApplicationCache(PassRefPtr<ApplicationCache> applicationCache)
243 {
244 if (m_candidateApplicationCacheGroup) {
245 ASSERT(!m_applicationCache);
246 m_candidateApplicationCacheGroup = 0;
247 }
248
249 m_applicationCache = applicationCache;
250 }
251
shouldLoadResourceFromApplicationCache(const ResourceRequest & request,ApplicationCacheResource * & resource)252 bool ApplicationCacheHost::shouldLoadResourceFromApplicationCache(const ResourceRequest& request, ApplicationCacheResource*& resource)
253 {
254 ApplicationCache* cache = applicationCache();
255 if (!cache || !cache->isComplete())
256 return false;
257
258 // If the resource is not a HTTP/HTTPS GET, then abort
259 if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request))
260 return false;
261
262 // If the resource's URL is an master entry, the manifest, an explicit entry, or a fallback entry
263 // in the application cache, then get the resource from the cache (instead of fetching it).
264 resource = cache->resourceForURL(request.url());
265
266 // Resources that match fallback namespaces or online whitelist entries are fetched from the network,
267 // unless they are also cached.
268 if (!resource && (cache->urlMatchesFallbackNamespace(request.url()) || cache->isURLInOnlineWhitelist(request.url())))
269 return false;
270
271 // Resources that are not present in the manifest will always fail to load (at least, after the
272 // cache has been primed the first time), making the testing of offline applications simpler.
273 return true;
274 }
275
getApplicationCacheFallbackResource(const ResourceRequest & request,ApplicationCacheResource * & resource,ApplicationCache * cache)276 bool ApplicationCacheHost::getApplicationCacheFallbackResource(const ResourceRequest& request, ApplicationCacheResource*& resource, ApplicationCache* cache)
277 {
278 if (!cache) {
279 cache = applicationCache();
280 if (!cache)
281 return false;
282 }
283 if (!cache->isComplete())
284 return false;
285
286 // If the resource is not a HTTP/HTTPS GET, then abort
287 if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request))
288 return false;
289
290 KURL fallbackURL;
291 if (!cache->urlMatchesFallbackNamespace(request.url(), &fallbackURL))
292 return false;
293
294 resource = cache->resourceForURL(fallbackURL);
295 ASSERT(resource);
296
297 return true;
298 }
299
scheduleLoadFallbackResourceFromApplicationCache(ResourceLoader * loader,ApplicationCache * cache)300 bool ApplicationCacheHost::scheduleLoadFallbackResourceFromApplicationCache(ResourceLoader* loader, ApplicationCache* cache)
301 {
302 if (!isApplicationCacheEnabled())
303 return false;
304
305 ApplicationCacheResource* resource;
306 if (!getApplicationCacheFallbackResource(loader->request(), resource, cache))
307 return false;
308
309 m_documentLoader->m_pendingSubstituteResources.set(loader, resource);
310 m_documentLoader->deliverSubstituteResourcesAfterDelay();
311
312 loader->handle()->cancel();
313
314 return true;
315 }
316
status() const317 ApplicationCacheHost::Status ApplicationCacheHost::status() const
318 {
319 ApplicationCache* cache = applicationCache();
320 if (!cache)
321 return UNCACHED;
322
323 switch (cache->group()->updateStatus()) {
324 case ApplicationCacheGroup::Checking:
325 return CHECKING;
326 case ApplicationCacheGroup::Downloading:
327 return DOWNLOADING;
328 case ApplicationCacheGroup::Idle: {
329 if (cache->group()->isObsolete())
330 return OBSOLETE;
331 if (cache != cache->group()->newestCache())
332 return UPDATEREADY;
333 return IDLE;
334 }
335 }
336
337 ASSERT_NOT_REACHED();
338 return UNCACHED;
339 }
340
update()341 bool ApplicationCacheHost::update()
342 {
343 ApplicationCache* cache = applicationCache();
344 if (!cache)
345 return false;
346 cache->group()->update(m_documentLoader->frame(), ApplicationCacheUpdateWithoutBrowsingContext);
347 return true;
348 }
349
swapCache()350 bool ApplicationCacheHost::swapCache()
351 {
352 ApplicationCache* cache = applicationCache();
353 if (!cache)
354 return false;
355
356 // If the group of application caches to which cache belongs has the lifecycle status obsolete, unassociate document from cache.
357 if (cache->group()->isObsolete()) {
358 cache->group()->disassociateDocumentLoader(m_documentLoader);
359 return true;
360 }
361
362 // If there is no newer cache, raise an INVALID_STATE_ERR exception.
363 ApplicationCache* newestCache = cache->group()->newestCache();
364 if (cache == newestCache)
365 return false;
366
367 ASSERT(cache->group() == newestCache->group());
368 setApplicationCache(newestCache);
369
370 return true;
371 }
372
isApplicationCacheEnabled()373 bool ApplicationCacheHost::isApplicationCacheEnabled()
374 {
375 return m_documentLoader->frame()->settings()
376 && m_documentLoader->frame()->settings()->offlineWebApplicationCacheEnabled();
377 }
378
379 } // namespace WebCore
380
381 #endif // ENABLE(OFFLINE_WEB_APPLICATIONS)
382