diff options
Diffstat (limited to 'WebCore/loader/appcache')
-rw-r--r-- | WebCore/loader/appcache/ApplicationCache.cpp | 192 | ||||
-rw-r--r-- | WebCore/loader/appcache/ApplicationCache.h | 103 | ||||
-rw-r--r-- | WebCore/loader/appcache/ApplicationCacheGroup.cpp | 715 | ||||
-rw-r--r-- | WebCore/loader/appcache/ApplicationCacheGroup.h | 152 | ||||
-rw-r--r-- | WebCore/loader/appcache/ApplicationCacheResource.cpp | 71 | ||||
-rw-r--r-- | WebCore/loader/appcache/ApplicationCacheResource.h | 74 | ||||
-rw-r--r-- | WebCore/loader/appcache/ApplicationCacheStorage.cpp | 663 | ||||
-rw-r--r-- | WebCore/loader/appcache/ApplicationCacheStorage.h | 96 | ||||
-rw-r--r-- | WebCore/loader/appcache/DOMApplicationCache.cpp | 286 | ||||
-rw-r--r-- | WebCore/loader/appcache/DOMApplicationCache.h | 140 | ||||
-rw-r--r-- | WebCore/loader/appcache/DOMApplicationCache.idl | 74 | ||||
-rw-r--r-- | WebCore/loader/appcache/ManifestParser.cpp | 166 | ||||
-rw-r--r-- | WebCore/loader/appcache/ManifestParser.h | 52 |
13 files changed, 2784 insertions, 0 deletions
diff --git a/WebCore/loader/appcache/ApplicationCache.cpp b/WebCore/loader/appcache/ApplicationCache.cpp new file mode 100644 index 0000000..d29eda8 --- /dev/null +++ b/WebCore/loader/appcache/ApplicationCache.cpp @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "ApplicationCache.h" + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + +#include "ApplicationCacheGroup.h" +#include "ApplicationCacheResource.h" +#include "ApplicationCacheStorage.h" +#include "ResourceRequest.h" +#include <stdio.h> + +namespace WebCore { + +ApplicationCache::ApplicationCache() + : m_group(0) + , m_manifest(0) + , m_storageID(0) +{ +} + +ApplicationCache::~ApplicationCache() +{ + if (m_group && !m_group->isCopy()) + m_group->cacheDestroyed(this); +} + +void ApplicationCache::setGroup(ApplicationCacheGroup* group) +{ + ASSERT(!m_group); + m_group = group; +} + +void ApplicationCache::setManifestResource(PassRefPtr<ApplicationCacheResource> manifest) +{ + ASSERT(manifest); + ASSERT(!m_manifest); + ASSERT(manifest->type() & ApplicationCacheResource::Manifest); + + m_manifest = manifest.get(); + + addResource(manifest); +} + +void ApplicationCache::addResource(PassRefPtr<ApplicationCacheResource> resource) +{ + ASSERT(resource); + + const String& url = resource->url(); + + ASSERT(!m_resources.contains(url)); + + if (m_storageID) { + ASSERT(!resource->storageID()); + + // Add the resource to the storage. + cacheStorage().store(resource.get(), this); + } + + m_resources.set(url, resource); +} + +unsigned ApplicationCache::removeResource(const String& url) +{ + HashMap<String, RefPtr<ApplicationCacheResource> >::iterator it = m_resources.find(url); + if (it == m_resources.end()) + return 0; + + // The resource exists, get its type so we can return it. + unsigned type = it->second->type(); + + m_resources.remove(it); + + return type; +} + +ApplicationCacheResource* ApplicationCache::resourceForURL(const String& url) +{ + return m_resources.get(url).get(); +} + +bool ApplicationCache::requestIsHTTPOrHTTPSGet(const ResourceRequest& request) +{ + if (!request.url().protocolIs("http") && !request.url().protocolIs("https")) + return false; + + if (!equalIgnoringCase(request.httpMethod(), "GET")) + return false; + + return true; +} + +ApplicationCacheResource* ApplicationCache::resourceForRequest(const ResourceRequest& request) +{ + // We only care about HTTP/HTTPS GET requests. + if (!requestIsHTTPOrHTTPSGet(request)) + return false; + + return resourceForURL(request.url()); +} + +unsigned ApplicationCache::numDynamicEntries() const +{ + // FIXME: Implement + return 0; +} + +String ApplicationCache::dynamicEntry(unsigned index) const +{ + // FIXME: Implement + return String(); +} + +bool ApplicationCache::addDynamicEntry(const String& url) +{ + if (!equalIgnoringCase(m_group->manifestURL().protocol(), + KURL(url).protocol())) + return false; + + // FIXME: Implement + return true; +} + +void ApplicationCache::removeDynamicEntry(const String& url) +{ + // FIXME: Implement +} + +void ApplicationCache::setOnlineWhitelist(const HashSet<String>& onlineWhitelist) +{ + ASSERT(m_onlineWhitelist.isEmpty()); + m_onlineWhitelist = onlineWhitelist; +} + +bool ApplicationCache::isURLInOnlineWhitelist(const KURL& url) +{ + if (!url.hasRef()) + return m_onlineWhitelist.contains(url); + + KURL copy(url); + copy.setRef(String()); + return m_onlineWhitelist.contains(copy); +} + +void ApplicationCache::clearStorageID() +{ + m_storageID = 0; + + ResourceMap::const_iterator end = m_resources.end(); + for (ResourceMap::const_iterator it = m_resources.begin(); it != end; ++it) + it->second->clearStorageID(); +} + +#ifndef NDEBUG +void ApplicationCache::dump() +{ + HashMap<String, RefPtr<ApplicationCacheResource> >::const_iterator end = m_resources.end(); + + for (HashMap<String, RefPtr<ApplicationCacheResource> >::const_iterator it = m_resources.begin(); it != end; ++it) { + printf("%s ", it->first.ascii().data()); + ApplicationCacheResource::dumpType(it->second->type()); + } +} +#endif + +} + +#endif // ENABLE(OFFLINE_WEB_APPLICATIONS) diff --git a/WebCore/loader/appcache/ApplicationCache.h b/WebCore/loader/appcache/ApplicationCache.h new file mode 100644 index 0000000..e536cbe --- /dev/null +++ b/WebCore/loader/appcache/ApplicationCache.h @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef ApplicationCache_h +#define ApplicationCache_h + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + +#include <wtf/HashMap.h> +#include <wtf/HashSet.h> +#include <wtf/PassRefPtr.h> +#include <wtf/RefCounted.h> + +#include "StringHash.h" +#include "PlatformString.h" + +namespace WebCore { + +class ApplicationCacheGroup; +class ApplicationCacheResource; +class DocumentLoader; +class KURL; +class ResourceRequest; + +class ApplicationCache : public RefCounted<ApplicationCache> { +public: + static PassRefPtr<ApplicationCache> create() { return adoptRef(new ApplicationCache); } + ~ApplicationCache(); + + void addResource(PassRefPtr<ApplicationCacheResource> resource); + unsigned removeResource(const String& url); + + void setManifestResource(PassRefPtr<ApplicationCacheResource> manifest); + ApplicationCacheResource* manifestResource() const { return m_manifest; } + + void setGroup(ApplicationCacheGroup*); + ApplicationCacheGroup* group() const { return m_group; } + + ApplicationCacheResource* resourceForRequest(const ResourceRequest&); + ApplicationCacheResource* resourceForURL(const String& url); + + unsigned numDynamicEntries() const; + String dynamicEntry(unsigned index) const; + + bool addDynamicEntry(const String& url); + void removeDynamicEntry(const String& url); + + void setOnlineWhitelist(const HashSet<String>& onlineWhitelist); + const HashSet<String>& onlineWhitelist() const { return m_onlineWhitelist; } + bool isURLInOnlineWhitelist(const KURL&); + +#ifndef NDEBUG + void dump(); +#endif + + typedef HashMap<String, RefPtr<ApplicationCacheResource> > ResourceMap; + ResourceMap::const_iterator begin() const { return m_resources.begin(); } + ResourceMap::const_iterator end() const { return m_resources.end(); } + + void setStorageID(unsigned storageID) { m_storageID = storageID; } + unsigned storageID() const { return m_storageID; } + void clearStorageID(); + + static bool requestIsHTTPOrHTTPSGet(const ResourceRequest&); +private: + ApplicationCache(); + + ApplicationCacheGroup* m_group; + ResourceMap m_resources; + ApplicationCacheResource* m_manifest; + + HashSet<String> m_onlineWhitelist; + + unsigned m_storageID; +}; + +} // namespace WebCore + +#endif // ENABLE(OFFLINE_WEB_APPLICATIONS) + +#endif // ApplicationCache_h diff --git a/WebCore/loader/appcache/ApplicationCacheGroup.cpp b/WebCore/loader/appcache/ApplicationCacheGroup.cpp new file mode 100644 index 0000000..ff26767 --- /dev/null +++ b/WebCore/loader/appcache/ApplicationCacheGroup.cpp @@ -0,0 +1,715 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "ApplicationCacheGroup.h" + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + +#include "ApplicationCache.h" +#include "ApplicationCacheResource.h" +#include "ApplicationCacheStorage.h" +#include "DocumentLoader.h" +#include "DOMApplicationCache.h" +#include "DOMWindow.h" +#include "Frame.h" +#include "FrameLoader.h" +#include "MainResourceLoader.h" +#include "ManifestParser.h" +#include "Page.h" +#include "Settings.h" +#include <wtf/HashMap.h> + +namespace WebCore { + +ApplicationCacheGroup::ApplicationCacheGroup(const KURL& manifestURL, bool isCopy) + : m_manifestURL(manifestURL) + , m_status(Idle) + , m_savedNewestCachePointer(0) + , m_frame(0) + , m_storageID(0) + , m_isCopy(isCopy) +{ +} + +ApplicationCacheGroup::~ApplicationCacheGroup() +{ + if (m_isCopy) { + ASSERT(m_newestCache); + ASSERT(m_caches.size() == 1); + ASSERT(m_caches.contains(m_newestCache.get())); + ASSERT(!m_cacheBeingUpdated); + ASSERT(m_associatedDocumentLoaders.isEmpty()); + ASSERT(m_cacheCandidates.isEmpty()); + ASSERT(m_newestCache->group() == this); + + return; + } + + ASSERT(!m_newestCache); + ASSERT(m_caches.isEmpty()); + + stopLoading(); + + cacheStorage().cacheGroupDestroyed(this); +} + +ApplicationCache* ApplicationCacheGroup::cacheForMainRequest(const ResourceRequest& request, DocumentLoader* loader) +{ + if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request)) + return 0; + + ASSERT(loader->frame()); + ASSERT(loader->frame()->page()); + if (loader->frame() != loader->frame()->page()->mainFrame()) + return 0; + + if (ApplicationCacheGroup* group = cacheStorage().cacheGroupForURL(request.url())) { + ASSERT(group->newestCache()); + + return group->newestCache(); + } + + return 0; +} + +void ApplicationCacheGroup::selectCache(Frame* frame, const KURL& manifestURL) +{ + ASSERT(frame && frame->page()); + + if (!frame->settings()->offlineWebApplicationCacheEnabled()) + return; + + DocumentLoader* documentLoader = frame->loader()->documentLoader(); + ASSERT(!documentLoader->applicationCache()); + + if (manifestURL.isNull()) { + selectCacheWithoutManifestURL(frame); + return; + } + + ApplicationCache* mainResourceCache = documentLoader->mainResourceApplicationCache(); + + // Check if the main resource is being loaded as part of navigation of the main frame + bool isMainFrame = frame->page()->mainFrame() == frame; + + if (!isMainFrame) { + if (mainResourceCache && manifestURL != mainResourceCache->group()->manifestURL()) { + ApplicationCacheResource* resource = mainResourceCache->resourceForURL(documentLoader->originalURL()); + ASSERT(resource); + + resource->addType(ApplicationCacheResource::Foreign); + } + + return; + } + + if (mainResourceCache) { + if (manifestURL == mainResourceCache->group()->m_manifestURL) { + mainResourceCache->group()->associateDocumentLoaderWithCache(documentLoader, mainResourceCache); + mainResourceCache->group()->update(frame); + } else { + // FIXME: If the resource being loaded was loaded from an application cache and the URI of + // that application cache's manifest is not the same as the manifest URI with which the algorithm was invoked + // then we should "undo" the navigation. + } + + return; + } + + // The resource was loaded from the network, check if it is a HTTP/HTTPS GET. + const ResourceRequest& request = frame->loader()->activeDocumentLoader()->request(); + + if (!ApplicationCache::requestIsHTTPOrHTTPSGet(request)) { + selectCacheWithoutManifestURL(frame); + return; + } + + // Check that the resource URL has the same scheme/host/port as the manifest URL. + if (!protocolHostAndPortAreEqual(manifestURL, request.url())) { + selectCacheWithoutManifestURL(frame); + return; + } + + ApplicationCacheGroup* group = cacheStorage().findOrCreateCacheGroup(manifestURL); + + if (ApplicationCache* cache = group->newestCache()) { + ASSERT(cache->manifestResource()); + + group->associateDocumentLoaderWithCache(frame->loader()->documentLoader(), cache); + + if (!frame->loader()->documentLoader()->isLoadingMainResource()) + group->finishedLoadingMainResource(frame->loader()->documentLoader()); + + group->update(frame); + } else { + bool isUpdating = group->m_cacheBeingUpdated; + + if (!isUpdating) + group->m_cacheBeingUpdated = ApplicationCache::create(); + documentLoader->setCandidateApplicationCacheGroup(group); + group->m_cacheCandidates.add(documentLoader); + + const KURL& url = frame->loader()->documentLoader()->originalURL(); + + unsigned type = 0; + + // If the resource has already been downloaded, remove it so that it will be replaced with the implicit resource + if (isUpdating) + type = group->m_cacheBeingUpdated->removeResource(url); + + // Add the main resource URL as an implicit entry. + group->addEntry(url, type | ApplicationCacheResource::Implicit); + + if (!frame->loader()->documentLoader()->isLoadingMainResource()) + group->finishedLoadingMainResource(frame->loader()->documentLoader()); + + if (!isUpdating) + group->update(frame); + } +} + +void ApplicationCacheGroup::selectCacheWithoutManifestURL(Frame* frame) +{ + if (!frame->settings()->offlineWebApplicationCacheEnabled()) + return; + + DocumentLoader* documentLoader = frame->loader()->documentLoader(); + ASSERT(!documentLoader->applicationCache()); + + ApplicationCache* mainResourceCache = documentLoader->mainResourceApplicationCache(); + bool isMainFrame = frame->page()->mainFrame() == frame; + + if (isMainFrame && mainResourceCache) { + mainResourceCache->group()->associateDocumentLoaderWithCache(documentLoader, mainResourceCache); + mainResourceCache->group()->update(frame); + } +} + +void ApplicationCacheGroup::finishedLoadingMainResource(DocumentLoader* loader) +{ + const KURL& url = loader->originalURL(); + + if (ApplicationCache* cache = loader->applicationCache()) { + RefPtr<ApplicationCacheResource> resource = ApplicationCacheResource::create(url, loader->response(), ApplicationCacheResource::Implicit, loader->mainResourceData()); + cache->addResource(resource.release()); + + if (!m_cacheBeingUpdated) + return; + } + + ASSERT(m_pendingEntries.contains(url)); + + EntryMap::iterator it = m_pendingEntries.find(url); + ASSERT(it->second & ApplicationCacheResource::Implicit); + + RefPtr<ApplicationCacheResource> resource = ApplicationCacheResource::create(url, loader->response(), it->second, loader->mainResourceData()); + + ASSERT(m_cacheBeingUpdated); + m_cacheBeingUpdated->addResource(resource.release()); + + m_pendingEntries.remove(it); + + checkIfLoadIsComplete(); +} + +void ApplicationCacheGroup::failedLoadingMainResource(DocumentLoader* loader) +{ + ASSERT(m_cacheCandidates.contains(loader) || m_associatedDocumentLoaders.contains(loader)); + + // Note that cacheUpdateFailed() can cause the cache group to be deleted. + cacheUpdateFailed(); +} + +void ApplicationCacheGroup::stopLoading() +{ + if (m_manifestHandle) { + ASSERT(!m_currentHandle); + ASSERT(!m_cacheBeingUpdated); + + m_manifestHandle->setClient(0); + m_manifestHandle->cancel(); + m_manifestHandle = 0; + } + + if (m_currentHandle) { + ASSERT(!m_manifestHandle); + ASSERT(m_cacheBeingUpdated); + + m_currentHandle->setClient(0); + m_currentHandle->cancel(); + m_currentHandle = 0; + } + + m_cacheBeingUpdated = 0; +} + +void ApplicationCacheGroup::documentLoaderDestroyed(DocumentLoader* loader) +{ + HashSet<DocumentLoader*>::iterator it = m_associatedDocumentLoaders.find(loader); + + if (it != m_associatedDocumentLoaders.end()) { + ASSERT(!m_cacheCandidates.contains(loader)); + + m_associatedDocumentLoaders.remove(it); + } else { + ASSERT(m_cacheCandidates.contains(loader)); + m_cacheCandidates.remove(loader); + } + + if (!m_associatedDocumentLoaders.isEmpty() || !m_cacheCandidates.isEmpty()) + return; + + // We should only have the newest cache remaining, or there is an initial cache attempt in progress. + ASSERT(m_caches.size() == 1 || m_cacheBeingUpdated); + + // If a cache update is in progress, stop it. + if (m_caches.size() == 1) { + ASSERT(m_caches.contains(m_newestCache.get())); + + // Release our reference to the newest cache. + m_savedNewestCachePointer = m_newestCache.get(); + + // This could cause us to be deleted. + m_newestCache = 0; + + return; + } + + // There is an initial cache attempt in progress + ASSERT(m_cacheBeingUpdated); + ASSERT(m_caches.size() == 0); + + // Delete ourselves, causing the cache attempt to be stopped. + delete this; +} + +void ApplicationCacheGroup::cacheDestroyed(ApplicationCache* cache) +{ + ASSERT(m_caches.contains(cache)); + + m_caches.remove(cache); + + if (cache != m_savedNewestCachePointer) + cacheStorage().remove(cache); + + if (m_caches.isEmpty()) + delete this; +} + +void ApplicationCacheGroup::setNewestCache(PassRefPtr<ApplicationCache> newestCache) +{ + ASSERT(!m_newestCache); + ASSERT(!m_caches.contains(newestCache.get())); + ASSERT(!newestCache->group()); + + m_newestCache = newestCache; + m_caches.add(m_newestCache.get()); + m_newestCache->setGroup(this); +} + +void ApplicationCacheGroup::update(Frame* frame) +{ + if (m_status == Checking || m_status == Downloading) + return; + + ASSERT(!m_frame); + m_frame = frame; + + m_status = Checking; + + callListenersOnAssociatedDocuments(&DOMApplicationCache::callCheckingListener); + + ASSERT(!m_manifestHandle); + ASSERT(!m_manifestResource); + + // FIXME: Handle defer loading + + ResourceRequest request(m_manifestURL); + m_frame->loader()->applyUserAgent(request); + + m_manifestHandle = ResourceHandle::create(request, this, m_frame, false, true, false); +} + +void ApplicationCacheGroup::didReceiveResponse(ResourceHandle* handle, const ResourceResponse& response) +{ + if (handle == m_manifestHandle) { + didReceiveManifestResponse(response); + return; + } + + ASSERT(handle == m_currentHandle); + + int statusCode = response.httpStatusCode() / 100; + if (statusCode == 4 || statusCode == 5) { + // Note that cacheUpdateFailed() can cause the cache group to be deleted. + cacheUpdateFailed(); + return; + } + + const KURL& url = handle->request().url(); + + ASSERT(!m_currentResource); + ASSERT(m_pendingEntries.contains(url)); + + unsigned type = m_pendingEntries.get(url); + + // If this is an initial cache attempt, we should not get implicit resources delivered here. + if (!m_newestCache) + ASSERT(!(type & ApplicationCacheResource::Implicit)); + + m_currentResource = ApplicationCacheResource::create(url, response, type); +} + +void ApplicationCacheGroup::didReceiveData(ResourceHandle* handle, const char* data, int length, int lengthReceived) +{ + if (handle == m_manifestHandle) { + didReceiveManifestData(data, length); + return; + } + + ASSERT(handle == m_currentHandle); + + ASSERT(m_currentResource); + m_currentResource->data()->append(data, length); +} + +void ApplicationCacheGroup::didFinishLoading(ResourceHandle* handle) +{ + if (handle == m_manifestHandle) { + didFinishLoadingManifest(); + return; + } + + ASSERT(m_currentHandle == handle); + ASSERT(m_pendingEntries.contains(handle->request().url())); + + m_pendingEntries.remove(handle->request().url()); + + ASSERT(m_cacheBeingUpdated); + + m_cacheBeingUpdated->addResource(m_currentResource.release()); + m_currentHandle = 0; + + // Load the next file. + if (!m_pendingEntries.isEmpty()) { + startLoadingEntry(); + return; + } + + checkIfLoadIsComplete(); +} + +void ApplicationCacheGroup::didFail(ResourceHandle* handle, const ResourceError&) +{ + if (handle == m_manifestHandle) { + didFailToLoadManifest(); + return; + } + + // Note that cacheUpdateFailed() can cause the cache group to be deleted. + cacheUpdateFailed(); +} + +void ApplicationCacheGroup::didReceiveManifestResponse(const ResourceResponse& response) +{ + int statusCode = response.httpStatusCode() / 100; + + if (statusCode == 4 || statusCode == 5 || + !equalIgnoringCase(response.mimeType(), "text/cache-manifest")) { + didFailToLoadManifest(); + return; + } + + ASSERT(!m_manifestResource); + ASSERT(m_manifestHandle); + m_manifestResource = ApplicationCacheResource::create(m_manifestHandle->request().url(), response, + ApplicationCacheResource::Manifest); +} + +void ApplicationCacheGroup::didReceiveManifestData(const char* data, int length) +{ + ASSERT(m_manifestResource); + m_manifestResource->data()->append(data, length); +} + +void ApplicationCacheGroup::didFinishLoadingManifest() +{ + if (!m_manifestResource) { + didFailToLoadManifest(); + return; + } + + bool isUpgradeAttempt = m_newestCache; + + m_manifestHandle = 0; + + // Check if the manifest is byte-for-byte identical. + if (isUpgradeAttempt) { + ApplicationCacheResource* newestManifest = m_newestCache->manifestResource(); + ASSERT(newestManifest); + + if (newestManifest->data()->size() == m_manifestResource->data()->size() && + !memcmp(newestManifest->data()->data(), m_manifestResource->data()->data(), newestManifest->data()->size())) { + + callListenersOnAssociatedDocuments(&DOMApplicationCache::callNoUpdateListener); + + m_status = Idle; + m_frame = 0; + m_manifestResource = 0; + return; + } + } + + Manifest manifest; + if (!parseManifest(m_manifestURL, m_manifestResource->data()->data(), m_manifestResource->data()->size(), manifest)) { + didFailToLoadManifest(); + return; + } + + // FIXME: Add the opportunistic caching namespaces and their fallbacks. + + // We have the manifest, now download the resources. + m_status = Downloading; + + callListenersOnAssociatedDocuments(&DOMApplicationCache::callDownloadingListener); + +#ifndef NDEBUG + // We should only have implicit entries. + { + EntryMap::const_iterator end = m_pendingEntries.end(); + for (EntryMap::const_iterator it = m_pendingEntries.begin(); it != end; ++it) + ASSERT(it->second & ApplicationCacheResource::Implicit); + } +#endif + + if (isUpgradeAttempt) { + ASSERT(!m_cacheBeingUpdated); + + m_cacheBeingUpdated = ApplicationCache::create(); + + ApplicationCache::ResourceMap::const_iterator end = m_newestCache->end(); + for (ApplicationCache::ResourceMap::const_iterator it = m_newestCache->begin(); it != end; ++it) { + unsigned type = it->second->type(); + if (type & (ApplicationCacheResource::Opportunistic | ApplicationCacheResource::Implicit | ApplicationCacheResource::Dynamic)) + addEntry(it->first, type); + } + } + + HashSet<String>::const_iterator end = manifest.explicitURLs.end(); + for (HashSet<String>::const_iterator it = manifest.explicitURLs.begin(); it != end; ++it) + addEntry(*it, ApplicationCacheResource::Explicit); + + m_cacheBeingUpdated->setOnlineWhitelist(manifest.onlineWhitelistedURLs); + + startLoadingEntry(); +} + +void ApplicationCacheGroup::cacheUpdateFailed() +{ + stopLoading(); + + callListenersOnAssociatedDocuments(&DOMApplicationCache::callErrorListener); + + m_pendingEntries.clear(); + m_manifestResource = 0; + + while (!m_cacheCandidates.isEmpty()) { + HashSet<DocumentLoader*>::iterator it = m_cacheCandidates.begin(); + + ASSERT((*it)->candidateApplicationCacheGroup() == this); + (*it)->setCandidateApplicationCacheGroup(0); + m_cacheCandidates.remove(it); + } + + m_status = Idle; + m_frame = 0; + + // If there are no associated caches, delete ourselves + if (m_associatedDocumentLoaders.isEmpty()) + delete this; +} + + +void ApplicationCacheGroup::didFailToLoadManifest() +{ + // Note that cacheUpdateFailed() can cause the cache group to be deleted. + cacheUpdateFailed(); +} + +void ApplicationCacheGroup::checkIfLoadIsComplete() +{ + ASSERT(m_cacheBeingUpdated); + + if (m_manifestHandle) + return; + + if (!m_pendingEntries.isEmpty()) + return; + + // We're done + bool isUpgradeAttempt = m_newestCache; + + m_cacheBeingUpdated->setManifestResource(m_manifestResource.release()); + + m_status = Idle; + m_frame = 0; + + Vector<RefPtr<DocumentLoader> > documentLoaders; + + if (isUpgradeAttempt) { + ASSERT(m_cacheCandidates.isEmpty()); + + copyToVector(m_associatedDocumentLoaders, documentLoaders); + } else { + while (!m_cacheCandidates.isEmpty()) { + HashSet<DocumentLoader*>::iterator it = m_cacheCandidates.begin(); + + DocumentLoader* loader = *it; + ASSERT(!loader->applicationCache()); + ASSERT(loader->candidateApplicationCacheGroup() == this); + + associateDocumentLoaderWithCache(loader, m_cacheBeingUpdated.get()); + + documentLoaders.append(loader); + + m_cacheCandidates.remove(it); + } + } + + setNewestCache(m_cacheBeingUpdated.release()); + + // Store the cache + cacheStorage().storeNewestCache(this); + + callListeners(isUpgradeAttempt ? &DOMApplicationCache::callUpdateReadyListener : &DOMApplicationCache::callCachedListener, + documentLoaders); +} + +void ApplicationCacheGroup::startLoadingEntry() +{ + ASSERT(m_cacheBeingUpdated); + + if (m_pendingEntries.isEmpty()) { + checkIfLoadIsComplete(); + return; + } + + EntryMap::const_iterator it = m_pendingEntries.begin(); + + // If this is an initial cache attempt, we do not want to fetch any implicit entries, + // since those are fed to us by the normal loader machinery. + if (!m_newestCache) { + // Get the first URL in the entry table that is not implicit + EntryMap::const_iterator end = m_pendingEntries.end(); + + while (it->second & ApplicationCacheResource::Implicit) { + ++it; + + if (it == end) + return; + } + } + + callListenersOnAssociatedDocuments(&DOMApplicationCache::callProgressListener); + + // FIXME: If this is an upgrade attempt, the newest cache should be used as an HTTP cache. + + ASSERT(!m_currentHandle); + + ResourceRequest request(it->first); + m_frame->loader()->applyUserAgent(request); + + m_currentHandle = ResourceHandle::create(request, this, m_frame, false, true, false); +} + +void ApplicationCacheGroup::addEntry(const String& url, unsigned type) +{ + ASSERT(m_cacheBeingUpdated); + + // Don't add the URL if we already have an implicit resource in the cache + if (ApplicationCacheResource* resource = m_cacheBeingUpdated->resourceForURL(url)) { + ASSERT(resource->type() & ApplicationCacheResource::Implicit); + + resource->addType(type); + return; + } + + // Don't add the URL if it's the same as the manifest URL. + if (m_manifestResource && m_manifestResource->url() == url) { + m_manifestResource->addType(type); + return; + } + + pair<EntryMap::iterator, bool> result = m_pendingEntries.add(url, type); + + if (!result.second) + result.first->second |= type; +} + +void ApplicationCacheGroup::associateDocumentLoaderWithCache(DocumentLoader* loader, ApplicationCache* cache) +{ + loader->setApplicationCache(cache); + + ASSERT(!m_associatedDocumentLoaders.contains(loader)); + m_associatedDocumentLoaders.add(loader); +} + +void ApplicationCacheGroup::callListenersOnAssociatedDocuments(ListenerFunction listenerFunction) +{ + Vector<RefPtr<DocumentLoader> > loaders; + copyToVector(m_associatedDocumentLoaders, loaders); + + callListeners(listenerFunction, loaders); +} + +void ApplicationCacheGroup::callListeners(ListenerFunction listenerFunction, const Vector<RefPtr<DocumentLoader> >& loaders) +{ + for (unsigned i = 0; i < loaders.size(); i++) { + Frame* frame = loaders[i]->frame(); + if (!frame) + continue; + + ASSERT(frame->loader()->documentLoader() == loaders[i]); + DOMWindow* window = frame->domWindow(); + + if (DOMApplicationCache* domCache = window->optionalApplicationCache()) + (domCache->*listenerFunction)(); + } +} + +void ApplicationCacheGroup::clearStorageID() +{ + m_storageID = 0; + + HashSet<ApplicationCache*>::const_iterator end = m_caches.end(); + for (HashSet<ApplicationCache*>::const_iterator it = m_caches.begin(); it != end; ++it) + (*it)->clearStorageID(); +} + + +} + +#endif // ENABLE(OFFLINE_WEB_APPLICATIONS) diff --git a/WebCore/loader/appcache/ApplicationCacheGroup.h b/WebCore/loader/appcache/ApplicationCacheGroup.h new file mode 100644 index 0000000..d5b7563 --- /dev/null +++ b/WebCore/loader/appcache/ApplicationCacheGroup.h @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef ApplicationCacheGroup_h +#define ApplicationCacheGroup_h + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + +#include <wtf/Noncopyable.h> +#include <wtf/HashMap.h> +#include <wtf/HashSet.h> + +#include "KURL.h" +#include "PlatformString.h" +#include "ResourceHandle.h" +#include "ResourceHandleClient.h" +#include "SharedBuffer.h" + +namespace WebCore { + +class ApplicationCache; +class ApplicationCacheResource; +class DOMApplicationCache; +class Document; +class DocumentLoader; +class Frame; + +class ApplicationCacheGroup : Noncopyable, ResourceHandleClient { +public: + ApplicationCacheGroup(const KURL& manifestURL, bool isCopy = false); + ~ApplicationCacheGroup(); + + enum Status { Idle, Checking, Downloading }; + + static ApplicationCache* cacheForMainRequest(const ResourceRequest&, DocumentLoader*); + + static void selectCache(Frame*, const KURL& manifestURL); + static void selectCacheWithoutManifestURL(Frame*); + + const KURL& manifestURL() const { return m_manifestURL; } + Status status() const { return m_status; } + + void setStorageID(unsigned storageID) { m_storageID = storageID; } + unsigned storageID() const { return m_storageID; } + void clearStorageID(); + + void update(Frame*); + void cacheDestroyed(ApplicationCache*); + + ApplicationCache* newestCache() const { return m_newestCache.get(); } + ApplicationCache* savedNewestCachePointer() const { return m_savedNewestCachePointer; } + + void finishedLoadingMainResource(DocumentLoader*); + void failedLoadingMainResource(DocumentLoader*); + void documentLoaderDestroyed(DocumentLoader*); + + void setNewestCache(PassRefPtr<ApplicationCache> newestCache); + + bool isCopy() const { return m_isCopy; } +private: + typedef void (DOMApplicationCache::*ListenerFunction)(); + void callListenersOnAssociatedDocuments(ListenerFunction); + void callListeners(ListenerFunction, const Vector<RefPtr<DocumentLoader> >& loaders); + + virtual void didReceiveResponse(ResourceHandle*, const ResourceResponse&); + virtual void didReceiveData(ResourceHandle*, const char*, int, int lengthReceived); + virtual void didFinishLoading(ResourceHandle*); + virtual void didFail(ResourceHandle*, const ResourceError&); + + void didReceiveManifestResponse(const ResourceResponse&); + void didReceiveManifestData(const char*, int); + void didFinishLoadingManifest(); + void didFailToLoadManifest(); + + void startLoadingEntry(); + void checkIfLoadIsComplete(); + void cacheUpdateFailed(); + + void addEntry(const String&, unsigned type); + + void associateDocumentLoaderWithCache(DocumentLoader*, ApplicationCache*); + + void stopLoading(); + + KURL m_manifestURL; + Status m_status; + + // This is the newest cache in the group. + RefPtr<ApplicationCache> m_newestCache; + + // During tear-down we save the pointer to the newest cache to prevent reference cycles. + ApplicationCache* m_savedNewestCachePointer; + + // The caches in this cache group. + HashSet<ApplicationCache*> m_caches; + + // The cache being updated (if any). + RefPtr<ApplicationCache> m_cacheBeingUpdated; + + // When a cache group does not yet have a complete cache, this contains the document loaders + // that should be associated with the cache once it has been downloaded. + HashSet<DocumentLoader*> m_cacheCandidates; + + // These are all the document loaders that are associated with a cache in this group. + HashSet<DocumentLoader*> m_associatedDocumentLoaders; + + // The URLs and types of pending cache entries. + typedef HashMap<String, unsigned> EntryMap; + EntryMap m_pendingEntries; + + // Frame used for fetching resources when updating + Frame* m_frame; + + unsigned m_storageID; + + // Whether this cache group is a copy that's only used for transferring the cache to another file. + bool m_isCopy; + + RefPtr<ResourceHandle> m_currentHandle; + RefPtr<ApplicationCacheResource> m_currentResource; + + RefPtr<ApplicationCacheResource> m_manifestResource; + RefPtr<ResourceHandle> m_manifestHandle; +}; + +} // namespace WebCore + +#endif // ENABLE(OFFLINE_WEB_APPLICATIONS) + +#endif // ApplicationCacheGroup_h diff --git a/WebCore/loader/appcache/ApplicationCacheResource.cpp b/WebCore/loader/appcache/ApplicationCacheResource.cpp new file mode 100644 index 0000000..ee82ff5 --- /dev/null +++ b/WebCore/loader/appcache/ApplicationCacheResource.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "ApplicationCacheResource.h" +#include <stdio.h> + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + +namespace WebCore { + +ApplicationCacheResource::ApplicationCacheResource(const KURL& url, const ResourceResponse& response, unsigned type, PassRefPtr<SharedBuffer> data) + : SubstituteResource(url, response, data) + , m_type(type) + , m_storageID(0) +{ +} + +void ApplicationCacheResource::addType(unsigned type) +{ + ASSERT(!m_storageID); + m_type |= type; +} + +#ifndef NDEBUG +void ApplicationCacheResource::dumpType(unsigned type) +{ + if (type & Implicit) + printf("implicit "); + if (type & Manifest) + printf("manifest "); + if (type & Explicit) + printf("explicit "); + if (type & Foreign) + printf("foreign "); + if (type & Fallback) + printf("fallback "); + if (type & Opportunistic) + printf("opportunistic "); + if (type & Dynamic) + printf("dynamic "); + + printf("\n"); +} +#endif + +} // namespace WebCore + +#endif // ENABLE(OFFLINE_WEB_APPLICATIONS) diff --git a/WebCore/loader/appcache/ApplicationCacheResource.h b/WebCore/loader/appcache/ApplicationCacheResource.h new file mode 100644 index 0000000..1d7b853 --- /dev/null +++ b/WebCore/loader/appcache/ApplicationCacheResource.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef ApplicationCacheResource_h +#define ApplicationCacheResource_h + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + +#include "SubstituteResource.h" + +namespace WebCore { + +class ApplicationCacheResource : public SubstituteResource { +public: + enum Type { + Implicit = 1 << 0, + Manifest = 1 << 1, + Explicit = 1 << 2, + Foreign = 1 << 3, + Fallback = 1 << 4, + Opportunistic = 1 << 5, + Dynamic = 1 << 6 + }; + + static PassRefPtr<ApplicationCacheResource> create(const KURL& url, const ResourceResponse& response, unsigned type, PassRefPtr<SharedBuffer> buffer = SharedBuffer::create()) + { + return adoptRef(new ApplicationCacheResource(url, response, type, buffer)); + } + + unsigned type() const { return m_type; } + void addType(unsigned type); + + void setStorageID(unsigned storageID) { m_storageID = storageID; } + unsigned storageID() const { return m_storageID; } + void clearStorageID() { m_storageID = 0; } + +#ifndef NDEBUG + static void dumpType(unsigned type); +#endif + +private: + ApplicationCacheResource(const KURL& url, const ResourceResponse& response, unsigned type, PassRefPtr<SharedBuffer> buffer); + + unsigned m_type; + unsigned m_storageID; +}; + +} // namespace WebCore + +#endif // ENABLE(OFFLINE_WEB_APPLICATIONS) + +#endif // ApplicationCacheResource_h diff --git a/WebCore/loader/appcache/ApplicationCacheStorage.cpp b/WebCore/loader/appcache/ApplicationCacheStorage.cpp new file mode 100644 index 0000000..910d00c --- /dev/null +++ b/WebCore/loader/appcache/ApplicationCacheStorage.cpp @@ -0,0 +1,663 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "ApplicationCacheStorage.h" + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + +#include "ApplicationCache.h" +#include "ApplicationCacheGroup.h" +#include "ApplicationCacheResource.h" +#include "FileSystem.h" +#include "CString.h" +#include "KURL.h" +#include "SQLiteStatement.h" +#include "SQLiteTransaction.h" + +namespace WebCore { + +static unsigned urlHostHash(const KURL& url) +{ + unsigned hostStart = url.hostStart(); + unsigned hostEnd = url.hostEnd(); + + return AlreadyHashed::avoidDeletedValue(StringImpl::computeHash(url.string().characters() + hostStart, hostEnd - hostStart)); +} + +ApplicationCacheGroup* ApplicationCacheStorage::loadCacheGroup(const KURL& manifestURL) +{ + openDatabase(false); + if (!m_database.isOpen()) + return 0; + + SQLiteStatement statement(m_database, "SELECT id, manifestURL, newestCache FROM CacheGroups WHERE newestCache IS NOT NULL AND manifestURL=?"); + if (statement.prepare() != SQLResultOk) + return 0; + + statement.bindText(1, manifestURL); + + int result = statement.step(); + if (result == SQLResultDone) + return 0; + + if (result != SQLResultRow) { + LOG_ERROR("Could not load cache group, error \"%s\"", m_database.lastErrorMsg()); + return 0; + } + + unsigned newestCacheStorageID = (unsigned)statement.getColumnInt64(2); + + RefPtr<ApplicationCache> cache = loadCache(newestCacheStorageID); + if (!cache) + return 0; + + ApplicationCacheGroup* group = new ApplicationCacheGroup(manifestURL); + + group->setStorageID((unsigned)statement.getColumnInt64(0)); + group->setNewestCache(cache.release()); + + return group; +} + +ApplicationCacheGroup* ApplicationCacheStorage::findOrCreateCacheGroup(const KURL& manifestURL) +{ + std::pair<CacheGroupMap::iterator, bool> result = m_cachesInMemory.add(manifestURL, 0); + + if (!result.second) { + ASSERT(result.first->second); + + return result.first->second; + } + + // Look up the group in the database + ApplicationCacheGroup* group = loadCacheGroup(manifestURL); + + // If the group was not found we need to create it + if (!group) { + group = new ApplicationCacheGroup(manifestURL); + m_cacheHostSet.add(urlHostHash(manifestURL)); + } + + result.first->second = group; + + return group; +} + +void ApplicationCacheStorage::loadManifestHostHashes() +{ + static bool hasLoadedHashes = false; + + if (hasLoadedHashes) + return; + + // We set this flag to true before the database has been opened + // to avoid trying to open the database over and over if it doesn't exist. + hasLoadedHashes = true; + + openDatabase(false); + if (!m_database.isOpen()) + return; + + // Fetch the host hashes. + SQLiteStatement statement(m_database, "SELECT manifestHostHash FROM CacheGroups"); + if (statement.prepare() != SQLResultOk) + return; + + int result; + while ((result = statement.step()) == SQLResultRow) + m_cacheHostSet.add((unsigned)statement.getColumnInt64(0)); +} + +ApplicationCacheGroup* ApplicationCacheStorage::cacheGroupForURL(const KURL& url) +{ + loadManifestHostHashes(); + + // Hash the host name and see if there's a manifest with the same host. + if (!m_cacheHostSet.contains(urlHostHash(url))) + return 0; + + // Check if a cache already exists in memory. + CacheGroupMap::const_iterator end = m_cachesInMemory.end(); + for (CacheGroupMap::const_iterator it = m_cachesInMemory.begin(); it != end; ++it) { + ApplicationCacheGroup* group = it->second; + + if (!protocolHostAndPortAreEqual(url, group->manifestURL())) + continue; + + if (ApplicationCache* cache = group->newestCache()) { + if (cache->resourceForURL(url)) + return group; + } + } + + if (!m_database.isOpen()) + return 0; + + // Check the database. Look for all cache groups with a newest cache. + SQLiteStatement statement(m_database, "SELECT id, manifestURL, newestCache FROM CacheGroups WHERE newestCache IS NOT NULL"); + if (statement.prepare() != SQLResultOk) + return 0; + + int result; + while ((result = statement.step()) == SQLResultRow) { + KURL manifestURL = KURL(statement.getColumnText(1)); + + if (!protocolHostAndPortAreEqual(url, manifestURL)) + continue; + + // We found a cache group that matches. Now check if the newest cache has a resource with + // a matching URL. + unsigned newestCacheID = (unsigned)statement.getColumnInt64(2); + RefPtr<ApplicationCache> cache = loadCache(newestCacheID); + + if (!cache->resourceForURL(url)) + continue; + + ApplicationCacheGroup* group = new ApplicationCacheGroup(manifestURL); + + group->setStorageID((unsigned)statement.getColumnInt64(0)); + group->setNewestCache(cache.release()); + + ASSERT(!m_cachesInMemory.contains(manifestURL)); + m_cachesInMemory.set(group->manifestURL(), group); + + return group; + } + + if (result != SQLResultDone) + LOG_ERROR("Could not load cache group, error \"%s\"", m_database.lastErrorMsg()); + + return 0; +} + +void ApplicationCacheStorage::cacheGroupDestroyed(ApplicationCacheGroup* group) +{ + ASSERT(m_cachesInMemory.get(group->manifestURL()) == group); + + m_cachesInMemory.remove(group->manifestURL()); + + // If the cache is half-created, we don't want it in the saved set. + if (!group->savedNewestCachePointer()) + m_cacheHostSet.remove(urlHostHash(group->manifestURL())); +} + +void ApplicationCacheStorage::setCacheDirectory(const String& cacheDirectory) +{ + ASSERT(m_cacheDirectory.isNull()); + ASSERT(!cacheDirectory.isNull()); + + m_cacheDirectory = cacheDirectory; +} + +bool ApplicationCacheStorage::executeSQLCommand(const String& sql) +{ + ASSERT(m_database.isOpen()); + + bool result = m_database.executeCommand(sql); + if (!result) + LOG_ERROR("Application Cache Storage: failed to execute statement \"%s\" error \"%s\"", + sql.utf8().data(), m_database.lastErrorMsg()); + + return result; +} + +static const int SchemaVersion = 2; + +void ApplicationCacheStorage::verifySchemaVersion() +{ + if (m_database.tableExists("SchemaVersion")) { + int version = SQLiteStatement(m_database, "SELECT version from SchemaVersion").getColumnInt(0); + + if (version == SchemaVersion) + return; + } + + m_database.clearAllTables(); + + SQLiteTransaction createSchemaVersionTable(m_database); + createSchemaVersionTable.begin(); + + executeSQLCommand("CREATE TABLE SchemaVersion (version INTEGER NOT NULL)"); + SQLiteStatement statement(m_database, "INSERT INTO SchemaVersion (version) VALUES (?)"); + if (statement.prepare() != SQLResultOk) + return; + + statement.bindInt64(1, SchemaVersion); + executeStatement(statement); + createSchemaVersionTable.commit(); +} + +void ApplicationCacheStorage::openDatabase(bool createIfDoesNotExist) +{ + if (m_database.isOpen()) + return; + + // The cache directory should never be null, but if it for some weird reason is we bail out. + if (m_cacheDirectory.isNull()) + return; + + String applicationCachePath = pathByAppendingComponent(m_cacheDirectory, "ApplicationCache.db"); + if (!createIfDoesNotExist && !fileExists(applicationCachePath)) + return; + + makeAllDirectories(m_cacheDirectory); + m_database.open(applicationCachePath); + + if (!m_database.isOpen()) + return; + + verifySchemaVersion(); + + // Create tables + executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheGroups (id INTEGER PRIMARY KEY AUTOINCREMENT, " + "manifestHostHash INTEGER NOT NULL ON CONFLICT FAIL, manifestURL TEXT UNIQUE ON CONFLICT FAIL, newestCache INTEGER)"); + executeSQLCommand("CREATE TABLE IF NOT EXISTS Caches (id INTEGER PRIMARY KEY AUTOINCREMENT, cacheGroup INTEGER)"); + executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheWhitelistURLs (url TEXT NOT NULL ON CONFLICT FAIL, cache INTEGER NOT NULL ON CONFLICT FAIL)"); + executeSQLCommand("CREATE TABLE IF NOT EXISTS FallbackURLs (namespace TEXT NOT NULL ON CONFLICT FAIL, fallbackURL TEXT NOT NULL ON CONFLICT FAIL, " + "cache INTEGER NOT NULL ON CONFLICT FAIL)"); + executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheEntries (cache INTEGER NOT NULL ON CONFLICT FAIL, type INTEGER, resource INTEGER NOT NULL)"); + executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheResources (id INTEGER PRIMARY KEY AUTOINCREMENT, url TEXT NOT NULL ON CONFLICT FAIL, " + "statusCode INTEGER NOT NULL, responseURL TEXT NOT NULL, mimeType TEXT, textEncodingName TEXT, headers TEXT, data INTEGER NOT NULL ON CONFLICT FAIL)"); + executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheResourceData (id INTEGER PRIMARY KEY AUTOINCREMENT, data BLOB)"); + + // When a cache is deleted, all its entries and its whitelist should be deleted. + executeSQLCommand("CREATE TRIGGER IF NOT EXISTS CacheDeleted AFTER DELETE ON Caches" + " FOR EACH ROW BEGIN" + " DELETE FROM CacheEntries WHERE cache = OLD.id;" + " DELETE FROM CacheWhitelistURLs WHERE cache = OLD.id;" + " DELETE FROM FallbackURLs WHERE cache = OLD.id;" + " END"); + + // When a cache resource is deleted, its data blob should also be deleted. + executeSQLCommand("CREATE TRIGGER IF NOT EXISTS CacheResourceDeleted AFTER DELETE ON CacheResources" + " FOR EACH ROW BEGIN" + " DELETE FROM CacheResourceData WHERE id = OLD.data;" + " END"); +} + +bool ApplicationCacheStorage::executeStatement(SQLiteStatement& statement) +{ + bool result = statement.executeCommand(); + if (!result) + LOG_ERROR("Application Cache Storage: failed to execute statement \"%s\" error \"%s\"", + statement.query().utf8().data(), m_database.lastErrorMsg()); + + return result; +} + +bool ApplicationCacheStorage::store(ApplicationCacheGroup* group) +{ + ASSERT(group->storageID() == 0); + + SQLiteStatement statement(m_database, "INSERT INTO CacheGroups (manifestHostHash, manifestURL) VALUES (?, ?)"); + if (statement.prepare() != SQLResultOk) + return false; + + statement.bindInt64(1, urlHostHash(group->manifestURL())); + statement.bindText(2, group->manifestURL()); + + if (!executeStatement(statement)) + return false; + + group->setStorageID((unsigned)m_database.lastInsertRowID()); + return true; +} + +bool ApplicationCacheStorage::store(ApplicationCache* cache) +{ + ASSERT(cache->storageID() == 0); + ASSERT(cache->group()->storageID() != 0); + + SQLiteStatement statement(m_database, "INSERT INTO Caches (cacheGroup) VALUES (?)"); + if (statement.prepare() != SQLResultOk) + return false; + + statement.bindInt64(1, cache->group()->storageID()); + + if (!executeStatement(statement)) + return false; + + unsigned cacheStorageID = (unsigned)m_database.lastInsertRowID(); + + // Store all resources + { + ApplicationCache::ResourceMap::const_iterator end = cache->end(); + for (ApplicationCache::ResourceMap::const_iterator it = cache->begin(); it != end; ++it) { + if (!store(it->second.get(), cacheStorageID)) + return false; + } + } + + // Store the online whitelist + const HashSet<String>& onlineWhitelist = cache->onlineWhitelist(); + { + HashSet<String>::const_iterator end = onlineWhitelist.end(); + for (HashSet<String>::const_iterator it = onlineWhitelist.begin(); it != end; ++it) { + SQLiteStatement statement(m_database, "INSERT INTO CacheWhitelistURLs (url, cache) VALUES (?, ?)"); + statement.prepare(); + + statement.bindText(1, *it); + statement.bindInt64(2, cacheStorageID); + + if (!executeStatement(statement)) + return false; + } + } + + cache->setStorageID(cacheStorageID); + return true; +} + +bool ApplicationCacheStorage::store(ApplicationCacheResource* resource, unsigned cacheStorageID) +{ + ASSERT(cacheStorageID); + ASSERT(!resource->storageID()); + + openDatabase(true); + + // First, insert the data + SQLiteStatement dataStatement(m_database, "INSERT INTO CacheResourceData (data) VALUES (?)"); + if (dataStatement.prepare() != SQLResultOk) + return false; + + if (resource->data()->size()) + dataStatement.bindBlob(1, resource->data()->data(), resource->data()->size()); + + if (!dataStatement.executeCommand()) + return false; + + unsigned dataId = (unsigned)m_database.lastInsertRowID(); + + // Then, insert the resource + + // Serialize the headers + Vector<UChar> stringBuilder; + + HTTPHeaderMap::const_iterator end = resource->response().httpHeaderFields().end(); + for (HTTPHeaderMap::const_iterator it = resource->response().httpHeaderFields().begin(); it!= end; ++it) { + stringBuilder.append(it->first.characters(), it->first.length()); + stringBuilder.append((UChar)':'); + stringBuilder.append(it->second.characters(), it->second.length()); + stringBuilder.append((UChar)'\n'); + } + + String headers = String::adopt(stringBuilder); + + SQLiteStatement resourceStatement(m_database, "INSERT INTO CacheResources (url, statusCode, responseURL, headers, data, mimeType, textEncodingName) VALUES (?, ?, ?, ?, ?, ?, ?)"); + if (resourceStatement.prepare() != SQLResultOk) + return false; + + resourceStatement.bindText(1, resource->url()); + resourceStatement.bindInt64(2, resource->response().httpStatusCode()); + resourceStatement.bindText(3, resource->response().url()); + resourceStatement.bindText(4, headers); + resourceStatement.bindInt64(5, dataId); + resourceStatement.bindText(6, resource->response().mimeType()); + resourceStatement.bindText(7, resource->response().textEncodingName()); + + if (!executeStatement(resourceStatement)) + return false; + + unsigned resourceId = (unsigned)m_database.lastInsertRowID(); + + // Finally, insert the cache entry + SQLiteStatement entryStatement(m_database, "INSERT INTO CacheEntries (cache, type, resource) VALUES (?, ?, ?)"); + if (entryStatement.prepare() != SQLResultOk) + return false; + + entryStatement.bindInt64(1, cacheStorageID); + entryStatement.bindInt64(2, resource->type()); + entryStatement.bindInt64(3, resourceId); + + if (!executeStatement(entryStatement)) + return false; + + resource->setStorageID(resourceId); + return true; +} + +void ApplicationCacheStorage::store(ApplicationCacheResource* resource, ApplicationCache* cache) +{ + ASSERT(cache->storageID()); + + openDatabase(true); + + SQLiteTransaction storeResourceTransaction(m_database); + storeResourceTransaction.begin(); + + if (!store(resource, cache->storageID())) + return; + + storeResourceTransaction.commit(); +} + +bool ApplicationCacheStorage::storeNewestCache(ApplicationCacheGroup* group) +{ + openDatabase(true); + + SQLiteTransaction storeCacheTransaction(m_database); + + storeCacheTransaction.begin(); + + if (!group->storageID()) { + // Store the group + if (!store(group)) + return false; + } + + ASSERT(group->newestCache()); + ASSERT(!group->newestCache()->storageID()); + + // Store the newest cache + if (!store(group->newestCache())) + return false; + + // Update the newest cache in the group. + + SQLiteStatement statement(m_database, "UPDATE CacheGroups SET newestCache=? WHERE id=?"); + if (statement.prepare() != SQLResultOk) + return false; + + statement.bindInt64(1, group->newestCache()->storageID()); + statement.bindInt64(2, group->storageID()); + + if (!executeStatement(statement)) + return false; + + storeCacheTransaction.commit(); + return true; +} + +static inline void parseHeader(const UChar* header, size_t headerLength, ResourceResponse& response) +{ + int pos = find(header, headerLength, ':'); + ASSERT(pos != -1); + + String headerName = String(header, pos); + String headerValue = String(header + pos + 1, headerLength - pos - 1); + + response.setHTTPHeaderField(headerName, headerValue); +} + +static inline void parseHeaders(const String& headers, ResourceResponse& response) +{ + int startPos = 0; + int endPos; + while ((endPos = headers.find('\n', startPos)) != -1) { + ASSERT(startPos != endPos); + + parseHeader(headers.characters() + startPos, endPos - startPos, response); + + startPos = endPos + 1; + } + + if (startPos != static_cast<int>(headers.length())) + parseHeader(headers.characters(), headers.length(), response); +} + +PassRefPtr<ApplicationCache> ApplicationCacheStorage::loadCache(unsigned storageID) +{ + SQLiteStatement cacheStatement(m_database, + "SELECT url, type, mimeType, textEncodingName, headers, CacheResourceData.data FROM CacheEntries INNER JOIN CacheResources ON CacheEntries.resource=CacheResources.id " + "INNER JOIN CacheResourceData ON CacheResourceData.id=CacheResources.data WHERE CacheEntries.cache=?"); + if (cacheStatement.prepare() != SQLResultOk) { + LOG_ERROR("Could not prepare cache statement, error \"%s\"", m_database.lastErrorMsg()); + return 0; + } + + cacheStatement.bindInt64(1, storageID); + + RefPtr<ApplicationCache> cache = ApplicationCache::create(); + + int result; + while ((result = cacheStatement.step()) == SQLResultRow) { + KURL url(cacheStatement.getColumnText(0)); + + unsigned type = (unsigned)cacheStatement.getColumnInt64(1); + + Vector<char> blob; + cacheStatement.getColumnBlobAsVector(5, blob); + + RefPtr<SharedBuffer> data = SharedBuffer::adoptVector(blob); + + String mimeType = cacheStatement.getColumnText(2); + String textEncodingName = cacheStatement.getColumnText(3); + + ResourceResponse response(url, mimeType, data->size(), textEncodingName, ""); + + String headers = cacheStatement.getColumnText(4); + parseHeaders(headers, response); + + RefPtr<ApplicationCacheResource> resource = ApplicationCacheResource::create(url, response, type, data.release()); + + if (type & ApplicationCacheResource::Manifest) + cache->setManifestResource(resource.release()); + else + cache->addResource(resource.release()); + } + + if (result != SQLResultDone) + LOG_ERROR("Could not load cache resources, error \"%s\"", m_database.lastErrorMsg()); + + // Load the online whitelist + SQLiteStatement whitelistStatement(m_database, "SELECT url FROM CacheWhitelistURLs WHERE cache=?"); + if (whitelistStatement.prepare() != SQLResultOk) + return 0; + whitelistStatement.bindInt64(1, storageID); + + HashSet<String> whitelist; + while ((result = whitelistStatement.step()) == SQLResultRow) + whitelist.add(whitelistStatement.getColumnText(0)); + + if (result != SQLResultDone) + LOG_ERROR("Could not load cache online whitelist, error \"%s\"", m_database.lastErrorMsg()); + + cache->setOnlineWhitelist(whitelist); + + cache->setStorageID(storageID); + + return cache.release(); +} + +void ApplicationCacheStorage::remove(ApplicationCache* cache) +{ + if (!cache->storageID()) + return; + + openDatabase(false); + if (!m_database.isOpen()) + return; + + SQLiteStatement statement(m_database, "DELETE FROM Caches WHERE id=?"); + if (statement.prepare() != SQLResultOk) + return; + + statement.bindInt64(1, cache->storageID()); + executeStatement(statement); +} + +void ApplicationCacheStorage::empty() +{ + openDatabase(false); + + if (!m_database.isOpen()) + return; + + // Clear cache groups, caches and cache resources. + executeSQLCommand("DELETE FROM CacheGroups"); + executeSQLCommand("DELETE FROM Caches"); + executeSQLCommand("DELETE FROM CacheResources"); + + // Clear the storage IDs for the caches in memory. + // The caches will still work, but cached resources will not be saved to disk + // until a cache update process has been initiated. + CacheGroupMap::const_iterator end = m_cachesInMemory.end(); + for (CacheGroupMap::const_iterator it = m_cachesInMemory.begin(); it != end; ++it) + it->second->clearStorageID(); +} + +bool ApplicationCacheStorage::storeCopyOfCache(const String& cacheDirectory, ApplicationCache* cache) +{ + // Create a new cache. + RefPtr<ApplicationCache> cacheCopy = ApplicationCache::create(); + + // Set the online whitelist + cacheCopy->setOnlineWhitelist(cache->onlineWhitelist()); + + // Traverse the cache and add copies of all resources. + ApplicationCache::ResourceMap::const_iterator end = cache->end(); + for (ApplicationCache::ResourceMap::const_iterator it = cache->begin(); it != end; ++it) { + ApplicationCacheResource* resource = it->second.get(); + + RefPtr<ApplicationCacheResource> resourceCopy = ApplicationCacheResource::create(resource->url(), resource->response(), resource->type(), resource->data()); + + cacheCopy->addResource(resourceCopy.release()); + } + + // Now create a new cache group. + OwnPtr<ApplicationCacheGroup> groupCopy(new ApplicationCacheGroup(cache->group()->manifestURL(), true)); + + groupCopy->setNewestCache(cacheCopy); + + ApplicationCacheStorage copyStorage; + copyStorage.setCacheDirectory(cacheDirectory); + + // Empty the cache in case something was there before. + copyStorage.empty(); + + return copyStorage.storeNewestCache(groupCopy.get()); +} + +ApplicationCacheStorage& cacheStorage() +{ + static ApplicationCacheStorage storage; + + return storage; +} + +} // namespace WebCore + +#endif // ENABLE(OFFLINE_WEB_APPLICATIONS) diff --git a/WebCore/loader/appcache/ApplicationCacheStorage.h b/WebCore/loader/appcache/ApplicationCacheStorage.h new file mode 100644 index 0000000..6bd9ba1 --- /dev/null +++ b/WebCore/loader/appcache/ApplicationCacheStorage.h @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef ApplicationCacheStorage_h +#define ApplicationCacheStorage_h + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + +#include "PlatformString.h" +#include "SQLiteDatabase.h" +#include "StringHash.h" + +#include <wtf/HashCountedSet.h> + +namespace WebCore { + +class ApplicationCache; +class ApplicationCacheGroup; +class ApplicationCacheResource; +class KURL; + +class ApplicationCacheStorage { +public: + void setCacheDirectory(const String&); + + ApplicationCacheGroup* cacheGroupForURL(const KURL&); + + ApplicationCacheGroup* findOrCreateCacheGroup(const KURL& manifestURL); + void cacheGroupDestroyed(ApplicationCacheGroup*); + + bool storeNewestCache(ApplicationCacheGroup*); + void store(ApplicationCacheResource*, ApplicationCache*); + + void remove(ApplicationCache*); + + void empty(); + + static bool storeCopyOfCache(const String& cacheDirectory, ApplicationCache*); +private: + PassRefPtr<ApplicationCache> loadCache(unsigned storageID); + ApplicationCacheGroup* loadCacheGroup(const KURL& manifestURL); + + bool store(ApplicationCacheGroup*); + bool store(ApplicationCache*); + bool store(ApplicationCacheResource*, unsigned cacheStorageID); + + void loadManifestHostHashes(); + + void verifySchemaVersion(); + + void openDatabase(bool createIfDoesNotExist); + + bool executeStatement(SQLiteStatement&); + bool executeSQLCommand(const String&); + + String m_cacheDirectory; + + SQLiteDatabase m_database; + + // In order to quickly determinate if a given resource exists in an application cache, + // we keep a hash set of the hosts of the manifest URLs of all cache groups. + HashCountedSet<unsigned, AlreadyHashed> m_cacheHostSet; + + typedef HashMap<String, ApplicationCacheGroup*> CacheGroupMap; + CacheGroupMap m_cachesInMemory; +}; + +ApplicationCacheStorage& cacheStorage(); + +} // namespace WebCore + +#endif // ENABLE(OFFLINE_WEB_APPLICATIONS) + +#endif // ApplicationCacheStorage_h diff --git a/WebCore/loader/appcache/DOMApplicationCache.cpp b/WebCore/loader/appcache/DOMApplicationCache.cpp new file mode 100644 index 0000000..f872a50 --- /dev/null +++ b/WebCore/loader/appcache/DOMApplicationCache.cpp @@ -0,0 +1,286 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "DOMApplicationCache.h" + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + +#include "ApplicationCache.h" +#include "ApplicationCacheGroup.h" +#include "DocumentLoader.h" +#include "Event.h" +#include "EventException.h" +#include "EventListener.h" +#include "EventNames.h" +#include "Frame.h" +#include "FrameLoader.h" + +namespace WebCore { + +DOMApplicationCache::DOMApplicationCache(Frame* frame) + : m_frame(frame) +{ +} + +void DOMApplicationCache::disconnectFrame() +{ + m_frame = 0; +} + +ApplicationCache* DOMApplicationCache::associatedCache() const +{ + if (!m_frame) + return 0; + + return m_frame->loader()->documentLoader()->topLevelApplicationCache(); +} + +unsigned short DOMApplicationCache::status() const +{ + ApplicationCache* cache = associatedCache(); + if (!cache) + return UNCACHED; + + switch (cache->group()->status()) { + case ApplicationCacheGroup::Checking: + return CHECKING; + case ApplicationCacheGroup::Downloading: + return DOWNLOADING; + case ApplicationCacheGroup::Idle: { + if (cache != cache->group()->newestCache()) + return UPDATEREADY; + + return IDLE; + } + default: + ASSERT_NOT_REACHED(); + } + + return 0; +} + +void DOMApplicationCache::update(ExceptionCode& ec) +{ + ApplicationCache* cache = associatedCache(); + if (!cache) { + ec = INVALID_STATE_ERR; + return; + } + + cache->group()->update(m_frame); +} + +bool DOMApplicationCache::swapCache() +{ + if (!m_frame) + return false; + + ApplicationCache* cache = m_frame->loader()->documentLoader()->applicationCache(); + if (!cache) + return false; + + // Check if we already have the newest cache + ApplicationCache* newestCache = cache->group()->newestCache(); + if (cache == newestCache) + return false; + + ASSERT(cache->group() == newestCache->group()); + m_frame->loader()->documentLoader()->setApplicationCache(newestCache); + + return true; +} + +void DOMApplicationCache::swapCache(ExceptionCode& ec) +{ + if (!swapCache()) + ec = INVALID_STATE_ERR; +} + +unsigned DOMApplicationCache::length() const +{ + ApplicationCache* cache = associatedCache(); + if (!cache) + return 0; + + return cache->numDynamicEntries(); +} + +String DOMApplicationCache::item(unsigned item, ExceptionCode& ec) +{ + ApplicationCache* cache = associatedCache(); + if (!cache) { + ec = INVALID_STATE_ERR; + return String(); + } + + if (item >= length()) { + ec = INDEX_SIZE_ERR; + return String(); + } + + return cache->dynamicEntry(item); +} + +void DOMApplicationCache::add(const KURL& url, ExceptionCode& ec) +{ + ApplicationCache* cache = associatedCache(); + if (!cache) { + ec = INVALID_STATE_ERR; + return; + } + + if (!url.isValid()) { + ec = SYNTAX_ERR; + return; + } + + if (!cache->addDynamicEntry(url)) { + // This should use the (currently not specified) security exceptions in HTML5 4.3.4 + ec = SECURITY_ERR; + } +} + +void DOMApplicationCache::remove(const KURL& url, ExceptionCode& ec) +{ + ApplicationCache* cache = associatedCache(); + if (!cache) { + ec = INVALID_STATE_ERR; + return; + } + + cache->removeDynamicEntry(url); +} + +ScriptExecutionContext* DOMApplicationCache::scriptExecutionContext() const +{ + return m_frame->document(); +} + +void DOMApplicationCache::addEventListener(const AtomicString& eventType, PassRefPtr<EventListener> eventListener, bool) +{ + EventListenersMap::iterator iter = m_eventListeners.find(eventType); + if (iter == m_eventListeners.end()) { + ListenerVector listeners; + listeners.append(eventListener); + m_eventListeners.add(eventType, listeners); + } else { + ListenerVector& listeners = iter->second; + for (ListenerVector::iterator listenerIter = listeners.begin(); listenerIter != listeners.end(); ++listenerIter) { + if (*listenerIter == eventListener) + return; + } + + listeners.append(eventListener); + m_eventListeners.add(eventType, listeners); + } +} + +void DOMApplicationCache::removeEventListener(const AtomicString& eventType, EventListener* eventListener, bool useCapture) +{ + EventListenersMap::iterator iter = m_eventListeners.find(eventType); + if (iter == m_eventListeners.end()) + return; + + ListenerVector& listeners = iter->second; + for (ListenerVector::const_iterator listenerIter = listeners.begin(); listenerIter != listeners.end(); ++listenerIter) { + if (*listenerIter == eventListener) { + listeners.remove(listenerIter - listeners.begin()); + return; + } + } +} + +bool DOMApplicationCache::dispatchEvent(PassRefPtr<Event> event, ExceptionCode& ec) +{ + if (event->type().isEmpty()) { + ec = EventException::UNSPECIFIED_EVENT_TYPE_ERR; + return true; + } + + ListenerVector listenersCopy = m_eventListeners.get(event->type()); + for (ListenerVector::const_iterator listenerIter = listenersCopy.begin(); listenerIter != listenersCopy.end(); ++listenerIter) { + event->setTarget(this); + event->setCurrentTarget(this); + listenerIter->get()->handleEvent(event.get(), false); + } + + return !event->defaultPrevented(); +} + +void DOMApplicationCache::callListener(const AtomicString& eventType, EventListener* listener) +{ + ASSERT(m_frame); + + RefPtr<Event> event = Event::create(eventType, false, false); + if (listener) { + event->setTarget(this); + event->setCurrentTarget(this); + listener->handleEvent(event.get(), false); + } + + ExceptionCode ec = 0; + dispatchEvent(event.release(), ec); + ASSERT(!ec); +} + +void DOMApplicationCache::callCheckingListener() +{ + callListener(eventNames().checkingEvent, m_onCheckingListener.get()); +} + +void DOMApplicationCache::callErrorListener() +{ + callListener(eventNames().errorEvent, m_onErrorListener.get()); +} + +void DOMApplicationCache::callNoUpdateListener() +{ + callListener(eventNames().noupdateEvent, m_onNoUpdateListener.get()); +} + +void DOMApplicationCache::callDownloadingListener() +{ + callListener(eventNames().downloadingEvent, m_onDownloadingListener.get()); +} + +void DOMApplicationCache::callProgressListener() +{ + callListener(eventNames().progressEvent, m_onProgressListener.get()); +} + +void DOMApplicationCache::callUpdateReadyListener() +{ + callListener(eventNames().updatereadyEvent, m_onUpdateReadyListener.get()); +} + +void DOMApplicationCache::callCachedListener() +{ + callListener(eventNames().cachedEvent, m_onCachedListener.get()); +} + +} // namespace WebCore + +#endif // ENABLE(OFFLINE_WEB_APPLICATIONS) diff --git a/WebCore/loader/appcache/DOMApplicationCache.h b/WebCore/loader/appcache/DOMApplicationCache.h new file mode 100644 index 0000000..30c0b7e --- /dev/null +++ b/WebCore/loader/appcache/DOMApplicationCache.h @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef DOMApplicationCache_h +#define DOMApplicationCache_h + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + +#include "AtomicStringHash.h" +#include "EventTarget.h" +#include "EventListener.h" +#include <wtf/HashMap.h> +#include <wtf/PassRefPtr.h> +#include <wtf/RefCounted.h> +#include <wtf/Vector.h> + +namespace WebCore { + +class ApplicationCache; +class AtomicStringImpl; +class Frame; +class KURL; +class String; + +class DOMApplicationCache : public RefCounted<DOMApplicationCache>, public EventTarget { +public: + static PassRefPtr<DOMApplicationCache> create(Frame* frame) { return adoptRef(new DOMApplicationCache(frame)); } + void disconnectFrame(); + + enum Status { + UNCACHED = 0, + IDLE = 1, + CHECKING = 2, + DOWNLOADING = 3, + UPDATEREADY = 4, + }; + + unsigned short status() const; + + void update(ExceptionCode&); + void swapCache(ExceptionCode&); + + unsigned length() const; + String item(unsigned item, ExceptionCode&); + void add(const KURL&, ExceptionCode&); + void remove(const KURL&, ExceptionCode&); + + virtual void addEventListener(const AtomicString& eventType, PassRefPtr<EventListener>, bool useCapture); + virtual void removeEventListener(const AtomicString& eventType, EventListener*, bool useCapture); + virtual bool dispatchEvent(PassRefPtr<Event>, ExceptionCode&); + + typedef Vector<RefPtr<EventListener> > ListenerVector; + typedef HashMap<AtomicString, ListenerVector> EventListenersMap; + EventListenersMap& eventListeners() { return m_eventListeners; } + + using RefCounted<DOMApplicationCache>::ref; + using RefCounted<DOMApplicationCache>::deref; + + void setOnchecking(PassRefPtr<EventListener> eventListener) { m_onCheckingListener = eventListener; } + EventListener* onchecking() const { return m_onCheckingListener.get(); } + + void setOnerror(PassRefPtr<EventListener> eventListener) { m_onErrorListener = eventListener; } + EventListener* onerror() const { return m_onErrorListener.get(); } + + void setOnnoupdate(PassRefPtr<EventListener> eventListener) { m_onNoUpdateListener = eventListener; } + EventListener* onnoupdate() const { return m_onNoUpdateListener.get(); } + + void setOndownloading(PassRefPtr<EventListener> eventListener) { m_onDownloadingListener = eventListener; } + EventListener* ondownloading() const { return m_onDownloadingListener.get(); } + + void setOnprogress(PassRefPtr<EventListener> eventListener) { m_onProgressListener = eventListener; } + EventListener* onprogress() const { return m_onProgressListener.get(); } + + void setOnupdateready(PassRefPtr<EventListener> eventListener) { m_onUpdateReadyListener = eventListener; } + EventListener* onupdateready() const { return m_onUpdateReadyListener.get(); } + + void setOncached(PassRefPtr<EventListener> eventListener) { m_onCachedListener = eventListener; } + EventListener* oncached() const { return m_onCachedListener.get(); } + + virtual ScriptExecutionContext* scriptExecutionContext() const; + DOMApplicationCache* toDOMApplicationCache() { return this; } + + void callCheckingListener(); + void callErrorListener(); + void callNoUpdateListener(); + void callDownloadingListener(); + void callProgressListener(); + void callUpdateReadyListener(); + void callCachedListener(); + +private: + DOMApplicationCache(Frame*); + void callListener(const AtomicString& eventType, EventListener*); + + virtual void refEventTarget() { ref(); } + virtual void derefEventTarget() { deref(); } + + ApplicationCache* associatedCache() const; + bool swapCache(); + + RefPtr<EventListener> m_onCheckingListener; + RefPtr<EventListener> m_onErrorListener; + RefPtr<EventListener> m_onNoUpdateListener; + RefPtr<EventListener> m_onDownloadingListener; + RefPtr<EventListener> m_onProgressListener; + RefPtr<EventListener> m_onUpdateReadyListener; + RefPtr<EventListener> m_onCachedListener; + + EventListenersMap m_eventListeners; + + Frame* m_frame; +}; + +} // namespace WebCore + +#endif // ENABLE(OFFLINE_WEB_APPLICATIONS) + +#endif // DOMApplicationCache_h diff --git a/WebCore/loader/appcache/DOMApplicationCache.idl b/WebCore/loader/appcache/DOMApplicationCache.idl new file mode 100644 index 0000000..94326f3 --- /dev/null +++ b/WebCore/loader/appcache/DOMApplicationCache.idl @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +module offline { + + interface [ + Conditional=OFFLINE_WEB_APPLICATIONS, + CustomMarkFunction + ] DOMApplicationCache { + // update status + const unsigned short UNCACHED = 0; + const unsigned short IDLE = 1; + const unsigned short CHECKING = 2; + const unsigned short DOWNLOADING = 3; + const unsigned short UPDATEREADY = 4; + readonly attribute unsigned short status; + + void update() + raises(DOMException); + void swapCache() + raises(DOMException); + + // dynamic entries + readonly attribute unsigned long length; + DOMString item(in [IsIndex] unsigned long index) + raises(DOMException); + [Custom] void add(in DOMString uri) + raises(DOMException); + [Custom] void remove(in DOMString uri) + raises(DOMException); + + // events + attribute EventListener onchecking; + attribute EventListener onerror; + attribute EventListener onnoupdate; + attribute EventListener ondownloading; + attribute EventListener onprogress; + attribute EventListener onupdateready; + attribute EventListener oncached; + + // EventTarget interface + [Custom] void addEventListener(in DOMString type, + in EventListener listener, + in boolean useCapture); + [Custom] void removeEventListener(in DOMString type, + in EventListener listener, + in boolean useCapture); + boolean dispatchEvent(in Event evt) + raises(EventException); + }; + +} diff --git a/WebCore/loader/appcache/ManifestParser.cpp b/WebCore/loader/appcache/ManifestParser.cpp new file mode 100644 index 0000000..778d22d --- /dev/null +++ b/WebCore/loader/appcache/ManifestParser.cpp @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "ManifestParser.h" + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + +#include "CharacterNames.h" +#include "KURL.h" +#include "TextEncoding.h" + +namespace WebCore { + +enum Mode { Explicit, Fallback, OnlineWhitelist }; + +bool parseManifest(const KURL& manifestURL, const char* data, int length, Manifest& manifest) +{ + ASSERT(manifest.explicitURLs.isEmpty()); + ASSERT(manifest.onlineWhitelistedURLs.isEmpty()); + ASSERT(manifest.fallbackURLs.isEmpty()); + + Mode mode = Explicit; + String s = UTF8Encoding().decode(data, length); + + if (s.isEmpty()) + return false; + + // Replace nulls with U+FFFD REPLACEMENT CHARACTER + s.replace(0, replacementCharacter); + + // Look for the magic signature + if (!s.startsWith("CACHE MANIFEST")) { + // The magic signature was not found. + return false; + } + + const UChar* end = s.characters() + s.length(); + const UChar* p = s.characters() + 14; // "CACHE MANIFEST" is 14 characters. + + while (p < end) { + // Skip whitespace + if (*p == ' ' || *p == '\t') { + p++; + } else + break; + } + + if (p < end && *p != '\n' && *p != '\r') { + // The magic signature was invalid + return false; + } + + while (1) { + // Skip whitespace + while (p < end && (*p == '\n' || *p == '\r' || *p == ' ' || *p == '\t')) + p++; + + if (p == end) + break; + + const UChar* lineStart = p; + + // Find the end of the line + while (p < end && *p != '\r' && *p != '\n') + p++; + + // Check if we have a comment + if (*lineStart == '#') + continue; + + // Get rid of trailing whitespace + const UChar* tmp = p - 1; + while (tmp > lineStart && (*tmp == ' ' || *tmp == '\t')) + tmp--; + + String line(lineStart, tmp - lineStart + 1); + + if (line == "CACHE:") + mode = Explicit; + else if (line == "FALLBACK:") + mode = Fallback; + else if (line == "NETWORK:") + mode = OnlineWhitelist; + else if (mode == Explicit || mode == OnlineWhitelist) { + KURL url(manifestURL, line); + + if (!url.isValid()) + continue; + + if (url.hasRef()) + url.setRef(String()); + + if (!equalIgnoringCase(url.protocol(), manifestURL.protocol())) + continue; + + if (mode == Explicit) + manifest.explicitURLs.add(url.string()); + else + manifest.onlineWhitelistedURLs.add(url.string()); + + } else if (mode == Fallback) { + const UChar *p = line.characters(); + const UChar *lineEnd = p + line.length(); + + // Look for whitespace separating the two URLs + while (p < lineEnd && *p != '\t' && *p != ' ') + p++; + + if (p == lineEnd) { + // There was no whitespace separating the URLs. + continue; + } + + KURL namespaceURL(manifestURL, String(line.characters(), p - line.characters())); + if (!namespaceURL.isValid()) + continue; + + // Check that the namespace URL has the same scheme/host/port as the manifest URL. + if (!protocolHostAndPortAreEqual(manifestURL, namespaceURL)) + continue; + + while (p < lineEnd && (*p == '\t' || *p == ' ')) + p++; + + KURL fallbackURL(String(p, line.length() - (p - line.characters()))); + + if (!fallbackURL.isValid()) + continue; + + if (!equalIgnoringCase(fallbackURL.protocol(), manifestURL.protocol())) + continue; + + manifest.fallbackURLs.add(namespaceURL, fallbackURL); + } else + ASSERT_NOT_REACHED(); + } + + return true; +} + +} + +#endif // ENABLE(OFFLINE_WEB_APPLICATIONS) diff --git a/WebCore/loader/appcache/ManifestParser.h b/WebCore/loader/appcache/ManifestParser.h new file mode 100644 index 0000000..f4fe31a --- /dev/null +++ b/WebCore/loader/appcache/ManifestParser.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2008 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef ManifestParser_h +#define ManifestParser_h + +#if ENABLE(OFFLINE_WEB_APPLICATIONS) + +#include <wtf/HashMap.h> +#include <wtf/HashSet.h> +#include "StringHash.h" +#include "PlatformString.h" + +namespace WebCore { + +class KURL; + +struct Manifest { + HashSet<String> onlineWhitelistedURLs; + HashSet<String> explicitURLs; + HashMap<String, String> fallbackURLs; +}; + +bool parseManifest(const KURL& manifestURL, const char* data, int length, Manifest&); + +} + +#endif // ENABLE(OFFLINE_WEB_APPLICATIONS) + +#endif // ManifestParser_h |