• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "ApplicationCacheStorage.h"
28 
29 #if ENABLE(OFFLINE_WEB_APPLICATIONS)
30 
31 #include "ApplicationCache.h"
32 #include "ApplicationCacheHost.h"
33 #include "ApplicationCacheGroup.h"
34 #include "ApplicationCacheResource.h"
35 #include "CString.h"
36 #include "FileSystem.h"
37 #include "KURL.h"
38 #include "SQLiteStatement.h"
39 #include "SQLiteTransaction.h"
40 #include <wtf/StdLibExtras.h>
41 #include <wtf/StringExtras.h>
42 
43 using namespace std;
44 
45 namespace WebCore {
46 
47 template <class T>
48 class StorageIDJournal {
49 public:
~StorageIDJournal()50     ~StorageIDJournal()
51     {
52         size_t size = m_records.size();
53         for (size_t i = 0; i < size; ++i)
54             m_records[i].restore();
55     }
56 
add(T * resource,unsigned storageID)57     void add(T* resource, unsigned storageID)
58     {
59         m_records.append(Record(resource, storageID));
60     }
61 
commit()62     void commit()
63     {
64         m_records.clear();
65     }
66 
67 private:
68     class Record {
69     public:
Record()70         Record() : m_resource(0), m_storageID(0) { }
Record(T * resource,unsigned storageID)71         Record(T* resource, unsigned storageID) : m_resource(resource), m_storageID(storageID) { }
72 
restore()73         void restore()
74         {
75             m_resource->setStorageID(m_storageID);
76         }
77 
78     private:
79         T* m_resource;
80         unsigned m_storageID;
81     };
82 
83     Vector<Record> m_records;
84 };
85 
urlHostHash(const KURL & url)86 static unsigned urlHostHash(const KURL& url)
87 {
88     unsigned hostStart = url.hostStart();
89     unsigned hostEnd = url.hostEnd();
90 
91     return AlreadyHashed::avoidDeletedValue(StringImpl::computeHash(url.string().characters() + hostStart, hostEnd - hostStart));
92 }
93 
loadCacheGroup(const KURL & manifestURL)94 ApplicationCacheGroup* ApplicationCacheStorage::loadCacheGroup(const KURL& manifestURL)
95 {
96     openDatabase(false);
97     if (!m_database.isOpen())
98         return 0;
99 
100     SQLiteStatement statement(m_database, "SELECT id, manifestURL, newestCache FROM CacheGroups WHERE newestCache IS NOT NULL AND manifestURL=?");
101     if (statement.prepare() != SQLResultOk)
102         return 0;
103 
104     statement.bindText(1, manifestURL);
105 
106     int result = statement.step();
107     if (result == SQLResultDone)
108         return 0;
109 
110     if (result != SQLResultRow) {
111         LOG_ERROR("Could not load cache group, error \"%s\"", m_database.lastErrorMsg());
112         return 0;
113     }
114 
115     unsigned newestCacheStorageID = static_cast<unsigned>(statement.getColumnInt64(2));
116 
117     RefPtr<ApplicationCache> cache = loadCache(newestCacheStorageID);
118     if (!cache)
119         return 0;
120 
121     ApplicationCacheGroup* group = new ApplicationCacheGroup(manifestURL);
122 
123     group->setStorageID(static_cast<unsigned>(statement.getColumnInt64(0)));
124     group->setNewestCache(cache.release());
125 
126     return group;
127 }
128 
findOrCreateCacheGroup(const KURL & manifestURL)129 ApplicationCacheGroup* ApplicationCacheStorage::findOrCreateCacheGroup(const KURL& manifestURL)
130 {
131     ASSERT(!manifestURL.hasFragmentIdentifier());
132 
133     std::pair<CacheGroupMap::iterator, bool> result = m_cachesInMemory.add(manifestURL, 0);
134 
135     if (!result.second) {
136         ASSERT(result.first->second);
137         return result.first->second;
138     }
139 
140     // Look up the group in the database
141     ApplicationCacheGroup* group = loadCacheGroup(manifestURL);
142 
143     // If the group was not found we need to create it
144     if (!group) {
145         group = new ApplicationCacheGroup(manifestURL);
146         m_cacheHostSet.add(urlHostHash(manifestURL));
147     }
148 
149     result.first->second = group;
150 
151     return group;
152 }
153 
loadManifestHostHashes()154 void ApplicationCacheStorage::loadManifestHostHashes()
155 {
156     static bool hasLoadedHashes = false;
157 
158     if (hasLoadedHashes)
159         return;
160 
161     // We set this flag to true before the database has been opened
162     // to avoid trying to open the database over and over if it doesn't exist.
163     hasLoadedHashes = true;
164 
165     openDatabase(false);
166     if (!m_database.isOpen())
167         return;
168 
169     // Fetch the host hashes.
170     SQLiteStatement statement(m_database, "SELECT manifestHostHash FROM CacheGroups");
171     if (statement.prepare() != SQLResultOk)
172         return;
173 
174     int result;
175     while ((result = statement.step()) == SQLResultRow)
176         m_cacheHostSet.add(static_cast<unsigned>(statement.getColumnInt64(0)));
177 }
178 
cacheGroupForURL(const KURL & url)179 ApplicationCacheGroup* ApplicationCacheStorage::cacheGroupForURL(const KURL& url)
180 {
181     ASSERT(!url.hasFragmentIdentifier());
182 
183     loadManifestHostHashes();
184 
185     // Hash the host name and see if there's a manifest with the same host.
186     if (!m_cacheHostSet.contains(urlHostHash(url)))
187         return 0;
188 
189     // Check if a cache already exists in memory.
190     CacheGroupMap::const_iterator end = m_cachesInMemory.end();
191     for (CacheGroupMap::const_iterator it = m_cachesInMemory.begin(); it != end; ++it) {
192         ApplicationCacheGroup* group = it->second;
193 
194         ASSERT(!group->isObsolete());
195 
196         if (!protocolHostAndPortAreEqual(url, group->manifestURL()))
197             continue;
198 
199         if (ApplicationCache* cache = group->newestCache()) {
200             ApplicationCacheResource* resource = cache->resourceForURL(url);
201             if (!resource)
202                 continue;
203             if (resource->type() & ApplicationCacheResource::Foreign)
204                 continue;
205             return group;
206         }
207     }
208 
209     if (!m_database.isOpen())
210         return 0;
211 
212     // Check the database. Look for all cache groups with a newest cache.
213     SQLiteStatement statement(m_database, "SELECT id, manifestURL, newestCache FROM CacheGroups WHERE newestCache IS NOT NULL");
214     if (statement.prepare() != SQLResultOk)
215         return 0;
216 
217     int result;
218     while ((result = statement.step()) == SQLResultRow) {
219         KURL manifestURL = KURL(ParsedURLString, statement.getColumnText(1));
220 
221         if (m_cachesInMemory.contains(manifestURL))
222             continue;
223 
224         if (!protocolHostAndPortAreEqual(url, manifestURL))
225             continue;
226 
227         // We found a cache group that matches. Now check if the newest cache has a resource with
228         // a matching URL.
229         unsigned newestCacheID = static_cast<unsigned>(statement.getColumnInt64(2));
230         RefPtr<ApplicationCache> cache = loadCache(newestCacheID);
231         if (!cache)
232             continue;
233 
234         ApplicationCacheResource* resource = cache->resourceForURL(url);
235         if (!resource)
236             continue;
237         if (resource->type() & ApplicationCacheResource::Foreign)
238             continue;
239 
240         ApplicationCacheGroup* group = new ApplicationCacheGroup(manifestURL);
241 
242         group->setStorageID(static_cast<unsigned>(statement.getColumnInt64(0)));
243         group->setNewestCache(cache.release());
244 
245         m_cachesInMemory.set(group->manifestURL(), group);
246 
247         return group;
248     }
249 
250     if (result != SQLResultDone)
251         LOG_ERROR("Could not load cache group, error \"%s\"", m_database.lastErrorMsg());
252 
253     return 0;
254 }
255 
fallbackCacheGroupForURL(const KURL & url)256 ApplicationCacheGroup* ApplicationCacheStorage::fallbackCacheGroupForURL(const KURL& url)
257 {
258     ASSERT(!url.hasFragmentIdentifier());
259 
260     // Check if an appropriate cache already exists in memory.
261     CacheGroupMap::const_iterator end = m_cachesInMemory.end();
262     for (CacheGroupMap::const_iterator it = m_cachesInMemory.begin(); it != end; ++it) {
263         ApplicationCacheGroup* group = it->second;
264 
265         ASSERT(!group->isObsolete());
266 
267         if (ApplicationCache* cache = group->newestCache()) {
268             KURL fallbackURL;
269             if (!cache->urlMatchesFallbackNamespace(url, &fallbackURL))
270                 continue;
271             if (cache->resourceForURL(fallbackURL)->type() & ApplicationCacheResource::Foreign)
272                 continue;
273             return group;
274         }
275     }
276 
277     if (!m_database.isOpen())
278         return 0;
279 
280     // Check the database. Look for all cache groups with a newest cache.
281     SQLiteStatement statement(m_database, "SELECT id, manifestURL, newestCache FROM CacheGroups WHERE newestCache IS NOT NULL");
282     if (statement.prepare() != SQLResultOk)
283         return 0;
284 
285     int result;
286     while ((result = statement.step()) == SQLResultRow) {
287         KURL manifestURL = KURL(ParsedURLString, statement.getColumnText(1));
288 
289         if (m_cachesInMemory.contains(manifestURL))
290             continue;
291 
292         // Fallback namespaces always have the same origin as manifest URL, so we can avoid loading caches that cannot match.
293         if (!protocolHostAndPortAreEqual(url, manifestURL))
294             continue;
295 
296         // We found a cache group that matches. Now check if the newest cache has a resource with
297         // a matching fallback namespace.
298         unsigned newestCacheID = static_cast<unsigned>(statement.getColumnInt64(2));
299         RefPtr<ApplicationCache> cache = loadCache(newestCacheID);
300 
301         KURL fallbackURL;
302         if (!cache->urlMatchesFallbackNamespace(url, &fallbackURL))
303             continue;
304         if (cache->resourceForURL(fallbackURL)->type() & ApplicationCacheResource::Foreign)
305             continue;
306 
307         ApplicationCacheGroup* group = new ApplicationCacheGroup(manifestURL);
308 
309         group->setStorageID(static_cast<unsigned>(statement.getColumnInt64(0)));
310         group->setNewestCache(cache.release());
311 
312         m_cachesInMemory.set(group->manifestURL(), group);
313 
314         return group;
315     }
316 
317     if (result != SQLResultDone)
318         LOG_ERROR("Could not load cache group, error \"%s\"", m_database.lastErrorMsg());
319 
320     return 0;
321 }
322 
cacheGroupDestroyed(ApplicationCacheGroup * group)323 void ApplicationCacheStorage::cacheGroupDestroyed(ApplicationCacheGroup* group)
324 {
325     if (group->isObsolete()) {
326         ASSERT(!group->storageID());
327         ASSERT(m_cachesInMemory.get(group->manifestURL()) != group);
328         return;
329     }
330 
331     ASSERT(m_cachesInMemory.get(group->manifestURL()) == group);
332 
333     m_cachesInMemory.remove(group->manifestURL());
334 
335     // If the cache group is half-created, we don't want it in the saved set (as it is not stored in database).
336     if (!group->storageID())
337         m_cacheHostSet.remove(urlHostHash(group->manifestURL()));
338 }
339 
cacheGroupMadeObsolete(ApplicationCacheGroup * group)340 void ApplicationCacheStorage::cacheGroupMadeObsolete(ApplicationCacheGroup* group)
341 {
342     ASSERT(m_cachesInMemory.get(group->manifestURL()) == group);
343     ASSERT(m_cacheHostSet.contains(urlHostHash(group->manifestURL())));
344 
345     if (ApplicationCache* newestCache = group->newestCache())
346         remove(newestCache);
347 
348     m_cachesInMemory.remove(group->manifestURL());
349     m_cacheHostSet.remove(urlHostHash(group->manifestURL()));
350 }
351 
setCacheDirectory(const String & cacheDirectory)352 void ApplicationCacheStorage::setCacheDirectory(const String& cacheDirectory)
353 {
354     ASSERT(m_cacheDirectory.isNull());
355     ASSERT(!cacheDirectory.isNull());
356 
357     m_cacheDirectory = cacheDirectory;
358 }
359 
cacheDirectory() const360 const String& ApplicationCacheStorage::cacheDirectory() const
361 {
362     return m_cacheDirectory;
363 }
364 
setMaximumSize(int64_t size)365 void ApplicationCacheStorage::setMaximumSize(int64_t size)
366 {
367     m_maximumSize = size;
368 }
369 
maximumSize() const370 int64_t ApplicationCacheStorage::maximumSize() const
371 {
372     return m_maximumSize;
373 }
374 
isMaximumSizeReached() const375 bool ApplicationCacheStorage::isMaximumSizeReached() const
376 {
377     return m_isMaximumSizeReached;
378 }
379 
spaceNeeded(int64_t cacheToSave)380 int64_t ApplicationCacheStorage::spaceNeeded(int64_t cacheToSave)
381 {
382     int64_t spaceNeeded = 0;
383     long long fileSize = 0;
384     if (!getFileSize(m_cacheFile, fileSize))
385         return 0;
386 
387     int64_t currentSize = fileSize;
388 
389     // Determine the amount of free space we have available.
390     int64_t totalAvailableSize = 0;
391     if (m_maximumSize < currentSize) {
392         // The max size is smaller than the actual size of the app cache file.
393         // This can happen if the client previously imposed a larger max size
394         // value and the app cache file has already grown beyond the current
395         // max size value.
396         // The amount of free space is just the amount of free space inside
397         // the database file. Note that this is always 0 if SQLite is compiled
398         // with AUTO_VACUUM = 1.
399         totalAvailableSize = m_database.freeSpaceSize();
400     } else {
401         // The max size is the same or larger than the current size.
402         // The amount of free space available is the amount of free space
403         // inside the database file plus the amount we can grow until we hit
404         // the max size.
405         totalAvailableSize = (m_maximumSize - currentSize) + m_database.freeSpaceSize();
406     }
407 
408     // The space needed to be freed in order to accommodate the failed cache is
409     // the size of the failed cache minus any already available free space.
410     spaceNeeded = cacheToSave - totalAvailableSize;
411     // The space needed value must be positive (or else the total already
412     // available free space would be larger than the size of the failed cache and
413     // saving of the cache should have never failed).
414     ASSERT(spaceNeeded);
415     return spaceNeeded;
416 }
417 
executeSQLCommand(const String & sql)418 bool ApplicationCacheStorage::executeSQLCommand(const String& sql)
419 {
420     ASSERT(m_database.isOpen());
421 
422     bool result = m_database.executeCommand(sql);
423     if (!result)
424         LOG_ERROR("Application Cache Storage: failed to execute statement \"%s\" error \"%s\"",
425                   sql.utf8().data(), m_database.lastErrorMsg());
426 
427     return result;
428 }
429 
430 static const int schemaVersion = 5;
431 
verifySchemaVersion()432 void ApplicationCacheStorage::verifySchemaVersion()
433 {
434     int version = SQLiteStatement(m_database, "PRAGMA user_version").getColumnInt(0);
435     if (version == schemaVersion)
436         return;
437 
438     m_database.clearAllTables();
439 
440     // Update user version.
441     SQLiteTransaction setDatabaseVersion(m_database);
442     setDatabaseVersion.begin();
443 
444     char userVersionSQL[32];
445     int unusedNumBytes = snprintf(userVersionSQL, sizeof(userVersionSQL), "PRAGMA user_version=%d", schemaVersion);
446     ASSERT_UNUSED(unusedNumBytes, static_cast<int>(sizeof(userVersionSQL)) >= unusedNumBytes);
447 
448     SQLiteStatement statement(m_database, userVersionSQL);
449     if (statement.prepare() != SQLResultOk)
450         return;
451 
452     executeStatement(statement);
453     setDatabaseVersion.commit();
454 }
455 
openDatabase(bool createIfDoesNotExist)456 void ApplicationCacheStorage::openDatabase(bool createIfDoesNotExist)
457 {
458     if (m_database.isOpen())
459         return;
460 
461     // The cache directory should never be null, but if it for some weird reason is we bail out.
462     if (m_cacheDirectory.isNull())
463         return;
464 
465     m_cacheFile = pathByAppendingComponent(m_cacheDirectory, "ApplicationCache.db");
466     if (!createIfDoesNotExist && !fileExists(m_cacheFile))
467         return;
468 
469     makeAllDirectories(m_cacheDirectory);
470     m_database.open(m_cacheFile);
471 
472     if (!m_database.isOpen())
473         return;
474 
475     verifySchemaVersion();
476 
477     // Create tables
478     executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheGroups (id INTEGER PRIMARY KEY AUTOINCREMENT, "
479                       "manifestHostHash INTEGER NOT NULL ON CONFLICT FAIL, manifestURL TEXT UNIQUE ON CONFLICT FAIL, newestCache INTEGER)");
480     executeSQLCommand("CREATE TABLE IF NOT EXISTS Caches (id INTEGER PRIMARY KEY AUTOINCREMENT, cacheGroup INTEGER, size INTEGER)");
481     executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheWhitelistURLs (url TEXT NOT NULL ON CONFLICT FAIL, cache INTEGER NOT NULL ON CONFLICT FAIL)");
482     executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheAllowsAllNetworkRequests (wildcard INTEGER NOT NULL ON CONFLICT FAIL, cache INTEGER NOT NULL ON CONFLICT FAIL)");
483     executeSQLCommand("CREATE TABLE IF NOT EXISTS FallbackURLs (namespace TEXT NOT NULL ON CONFLICT FAIL, fallbackURL TEXT NOT NULL ON CONFLICT FAIL, "
484                       "cache INTEGER NOT NULL ON CONFLICT FAIL)");
485     executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheEntries (cache INTEGER NOT NULL ON CONFLICT FAIL, type INTEGER, resource INTEGER NOT NULL)");
486     executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheResources (id INTEGER PRIMARY KEY AUTOINCREMENT, url TEXT NOT NULL ON CONFLICT FAIL, "
487                       "statusCode INTEGER NOT NULL, responseURL TEXT NOT NULL, mimeType TEXT, textEncodingName TEXT, headers TEXT, data INTEGER NOT NULL ON CONFLICT FAIL)");
488     executeSQLCommand("CREATE TABLE IF NOT EXISTS CacheResourceData (id INTEGER PRIMARY KEY AUTOINCREMENT, data BLOB)");
489 
490     // When a cache is deleted, all its entries and its whitelist should be deleted.
491     executeSQLCommand("CREATE TRIGGER IF NOT EXISTS CacheDeleted AFTER DELETE ON Caches"
492                       " FOR EACH ROW BEGIN"
493                       "  DELETE FROM CacheEntries WHERE cache = OLD.id;"
494                       "  DELETE FROM CacheWhitelistURLs WHERE cache = OLD.id;"
495                       "  DELETE FROM CacheAllowsAllNetworkRequests WHERE cache = OLD.id;"
496                       "  DELETE FROM FallbackURLs WHERE cache = OLD.id;"
497                       " END");
498 
499     // When a cache entry is deleted, its resource should also be deleted.
500     executeSQLCommand("CREATE TRIGGER IF NOT EXISTS CacheEntryDeleted AFTER DELETE ON CacheEntries"
501                       " FOR EACH ROW BEGIN"
502                       "  DELETE FROM CacheResources WHERE id = OLD.resource;"
503                       " END");
504 
505     // When a cache resource is deleted, its data blob should also be deleted.
506     executeSQLCommand("CREATE TRIGGER IF NOT EXISTS CacheResourceDeleted AFTER DELETE ON CacheResources"
507                       " FOR EACH ROW BEGIN"
508                       "  DELETE FROM CacheResourceData WHERE id = OLD.data;"
509                       " END");
510 }
511 
executeStatement(SQLiteStatement & statement)512 bool ApplicationCacheStorage::executeStatement(SQLiteStatement& statement)
513 {
514     bool result = statement.executeCommand();
515     if (!result)
516         LOG_ERROR("Application Cache Storage: failed to execute statement \"%s\" error \"%s\"",
517                   statement.query().utf8().data(), m_database.lastErrorMsg());
518 
519     return result;
520 }
521 
store(ApplicationCacheGroup * group,GroupStorageIDJournal * journal)522 bool ApplicationCacheStorage::store(ApplicationCacheGroup* group, GroupStorageIDJournal* journal)
523 {
524     ASSERT(group->storageID() == 0);
525     ASSERT(journal);
526 
527     SQLiteStatement statement(m_database, "INSERT INTO CacheGroups (manifestHostHash, manifestURL) VALUES (?, ?)");
528     if (statement.prepare() != SQLResultOk)
529         return false;
530 
531     statement.bindInt64(1, urlHostHash(group->manifestURL()));
532     statement.bindText(2, group->manifestURL());
533 
534     if (!executeStatement(statement))
535         return false;
536 
537     group->setStorageID(static_cast<unsigned>(m_database.lastInsertRowID()));
538     journal->add(group, 0);
539     return true;
540 }
541 
store(ApplicationCache * cache,ResourceStorageIDJournal * storageIDJournal)542 bool ApplicationCacheStorage::store(ApplicationCache* cache, ResourceStorageIDJournal* storageIDJournal)
543 {
544     ASSERT(cache->storageID() == 0);
545     ASSERT(cache->group()->storageID() != 0);
546     ASSERT(storageIDJournal);
547 
548     SQLiteStatement statement(m_database, "INSERT INTO Caches (cacheGroup, size) VALUES (?, ?)");
549     if (statement.prepare() != SQLResultOk)
550         return false;
551 
552     statement.bindInt64(1, cache->group()->storageID());
553     statement.bindInt64(2, cache->estimatedSizeInStorage());
554 
555     if (!executeStatement(statement))
556         return false;
557 
558     unsigned cacheStorageID = static_cast<unsigned>(m_database.lastInsertRowID());
559 
560     // Store all resources
561     {
562         ApplicationCache::ResourceMap::const_iterator end = cache->end();
563         for (ApplicationCache::ResourceMap::const_iterator it = cache->begin(); it != end; ++it) {
564             unsigned oldStorageID = it->second->storageID();
565             if (!store(it->second.get(), cacheStorageID))
566                 return false;
567 
568             // Storing the resource succeeded. Log its old storageID in case
569             // it needs to be restored later.
570             storageIDJournal->add(it->second.get(), oldStorageID);
571         }
572     }
573 
574     // Store the online whitelist
575     const Vector<KURL>& onlineWhitelist = cache->onlineWhitelist();
576     {
577         size_t whitelistSize = onlineWhitelist.size();
578         for (size_t i = 0; i < whitelistSize; ++i) {
579             SQLiteStatement statement(m_database, "INSERT INTO CacheWhitelistURLs (url, cache) VALUES (?, ?)");
580             statement.prepare();
581 
582             statement.bindText(1, onlineWhitelist[i]);
583             statement.bindInt64(2, cacheStorageID);
584 
585             if (!executeStatement(statement))
586                 return false;
587         }
588     }
589 
590     // Store online whitelist wildcard flag.
591     {
592         SQLiteStatement statement(m_database, "INSERT INTO CacheAllowsAllNetworkRequests (wildcard, cache) VALUES (?, ?)");
593         statement.prepare();
594 
595         statement.bindInt64(1, cache->allowsAllNetworkRequests());
596         statement.bindInt64(2, cacheStorageID);
597 
598         if (!executeStatement(statement))
599             return false;
600     }
601 
602     // Store fallback URLs.
603     const FallbackURLVector& fallbackURLs = cache->fallbackURLs();
604     {
605         size_t fallbackCount = fallbackURLs.size();
606         for (size_t i = 0; i < fallbackCount; ++i) {
607             SQLiteStatement statement(m_database, "INSERT INTO FallbackURLs (namespace, fallbackURL, cache) VALUES (?, ?, ?)");
608             statement.prepare();
609 
610             statement.bindText(1, fallbackURLs[i].first);
611             statement.bindText(2, fallbackURLs[i].second);
612             statement.bindInt64(3, cacheStorageID);
613 
614             if (!executeStatement(statement))
615                 return false;
616         }
617     }
618 
619     cache->setStorageID(cacheStorageID);
620     return true;
621 }
622 
store(ApplicationCacheResource * resource,unsigned cacheStorageID)623 bool ApplicationCacheStorage::store(ApplicationCacheResource* resource, unsigned cacheStorageID)
624 {
625     ASSERT(cacheStorageID);
626     ASSERT(!resource->storageID());
627 
628     openDatabase(true);
629 
630     // openDatabase(true) could still fail, for example when cacheStorage is full or no longer available.
631     if (!m_database.isOpen())
632         return false;
633 
634     // First, insert the data
635     SQLiteStatement dataStatement(m_database, "INSERT INTO CacheResourceData (data) VALUES (?)");
636     if (dataStatement.prepare() != SQLResultOk)
637         return false;
638 
639     if (resource->data()->size())
640         dataStatement.bindBlob(1, resource->data()->data(), resource->data()->size());
641 
642     if (!dataStatement.executeCommand())
643         return false;
644 
645     unsigned dataId = static_cast<unsigned>(m_database.lastInsertRowID());
646 
647     // Then, insert the resource
648 
649     // Serialize the headers
650     Vector<UChar> stringBuilder;
651 
652     HTTPHeaderMap::const_iterator end = resource->response().httpHeaderFields().end();
653     for (HTTPHeaderMap::const_iterator it = resource->response().httpHeaderFields().begin(); it!= end; ++it) {
654         stringBuilder.append(it->first.characters(), it->first.length());
655         stringBuilder.append((UChar)':');
656         stringBuilder.append(it->second.characters(), it->second.length());
657         stringBuilder.append((UChar)'\n');
658     }
659 
660     String headers = String::adopt(stringBuilder);
661 
662     SQLiteStatement resourceStatement(m_database, "INSERT INTO CacheResources (url, statusCode, responseURL, headers, data, mimeType, textEncodingName) VALUES (?, ?, ?, ?, ?, ?, ?)");
663     if (resourceStatement.prepare() != SQLResultOk)
664         return false;
665 
666     // The same ApplicationCacheResource are used in ApplicationCacheResource::size()
667     // to calculate the approximate size of an ApplicationCacheResource object. If
668     // you change the code below, please also change ApplicationCacheResource::size().
669     resourceStatement.bindText(1, resource->url());
670     resourceStatement.bindInt64(2, resource->response().httpStatusCode());
671     resourceStatement.bindText(3, resource->response().url());
672     resourceStatement.bindText(4, headers);
673     resourceStatement.bindInt64(5, dataId);
674     resourceStatement.bindText(6, resource->response().mimeType());
675     resourceStatement.bindText(7, resource->response().textEncodingName());
676 
677     if (!executeStatement(resourceStatement))
678         return false;
679 
680     unsigned resourceId = static_cast<unsigned>(m_database.lastInsertRowID());
681 
682     // Finally, insert the cache entry
683     SQLiteStatement entryStatement(m_database, "INSERT INTO CacheEntries (cache, type, resource) VALUES (?, ?, ?)");
684     if (entryStatement.prepare() != SQLResultOk)
685         return false;
686 
687     entryStatement.bindInt64(1, cacheStorageID);
688     entryStatement.bindInt64(2, resource->type());
689     entryStatement.bindInt64(3, resourceId);
690 
691     if (!executeStatement(entryStatement))
692         return false;
693 
694     resource->setStorageID(resourceId);
695     return true;
696 }
697 
storeUpdatedType(ApplicationCacheResource * resource,ApplicationCache * cache)698 bool ApplicationCacheStorage::storeUpdatedType(ApplicationCacheResource* resource, ApplicationCache* cache)
699 {
700     ASSERT_UNUSED(cache, cache->storageID());
701     ASSERT(resource->storageID());
702 
703     // First, insert the data
704     SQLiteStatement entryStatement(m_database, "UPDATE CacheEntries SET type=? WHERE resource=?");
705     if (entryStatement.prepare() != SQLResultOk)
706         return false;
707 
708     entryStatement.bindInt64(1, resource->type());
709     entryStatement.bindInt64(2, resource->storageID());
710 
711     return executeStatement(entryStatement);
712 }
713 
store(ApplicationCacheResource * resource,ApplicationCache * cache)714 bool ApplicationCacheStorage::store(ApplicationCacheResource* resource, ApplicationCache* cache)
715 {
716     ASSERT(cache->storageID());
717 
718     openDatabase(true);
719 
720     if (!m_database.isOpen())
721         return false;
722 
723     m_isMaximumSizeReached = false;
724     m_database.setMaximumSize(m_maximumSize);
725 
726     SQLiteTransaction storeResourceTransaction(m_database);
727     storeResourceTransaction.begin();
728 
729     if (!store(resource, cache->storageID())) {
730         checkForMaxSizeReached();
731         return false;
732     }
733 
734     // A resource was added to the cache. Update the total data size for the cache.
735     SQLiteStatement sizeUpdateStatement(m_database, "UPDATE Caches SET size=size+? WHERE id=?");
736     if (sizeUpdateStatement.prepare() != SQLResultOk)
737         return false;
738 
739     sizeUpdateStatement.bindInt64(1, resource->estimatedSizeInStorage());
740     sizeUpdateStatement.bindInt64(2, cache->storageID());
741 
742     if (!executeStatement(sizeUpdateStatement))
743         return false;
744 
745     storeResourceTransaction.commit();
746     return true;
747 }
748 
storeNewestCache(ApplicationCacheGroup * group)749 bool ApplicationCacheStorage::storeNewestCache(ApplicationCacheGroup* group)
750 {
751     openDatabase(true);
752 
753     if (!m_database.isOpen())
754         return false;
755 
756     m_isMaximumSizeReached = false;
757     m_database.setMaximumSize(m_maximumSize);
758 
759     SQLiteTransaction storeCacheTransaction(m_database);
760 
761     storeCacheTransaction.begin();
762 
763     GroupStorageIDJournal groupStorageIDJournal;
764     if (!group->storageID()) {
765         // Store the group
766         if (!store(group, &groupStorageIDJournal)) {
767             checkForMaxSizeReached();
768             return false;
769         }
770     }
771 
772     ASSERT(group->newestCache());
773     ASSERT(!group->isObsolete());
774     ASSERT(!group->newestCache()->storageID());
775 
776     // Log the storageID changes to the in-memory resource objects. The journal
777     // object will roll them back automatically in case a database operation
778     // fails and this method returns early.
779     ResourceStorageIDJournal resourceStorageIDJournal;
780 
781     // Store the newest cache
782     if (!store(group->newestCache(), &resourceStorageIDJournal)) {
783         checkForMaxSizeReached();
784         return false;
785     }
786 
787     // Update the newest cache in the group.
788 
789     SQLiteStatement statement(m_database, "UPDATE CacheGroups SET newestCache=? WHERE id=?");
790     if (statement.prepare() != SQLResultOk)
791         return false;
792 
793     statement.bindInt64(1, group->newestCache()->storageID());
794     statement.bindInt64(2, group->storageID());
795 
796     if (!executeStatement(statement))
797         return false;
798 
799     groupStorageIDJournal.commit();
800     resourceStorageIDJournal.commit();
801     storeCacheTransaction.commit();
802     return true;
803 }
804 
parseHeader(const UChar * header,size_t headerLength,ResourceResponse & response)805 static inline void parseHeader(const UChar* header, size_t headerLength, ResourceResponse& response)
806 {
807     int pos = find(header, headerLength, ':');
808     ASSERT(pos != -1);
809 
810     AtomicString headerName = AtomicString(header, pos);
811     String headerValue = String(header + pos + 1, headerLength - pos - 1);
812 
813     response.setHTTPHeaderField(headerName, headerValue);
814 }
815 
parseHeaders(const String & headers,ResourceResponse & response)816 static inline void parseHeaders(const String& headers, ResourceResponse& response)
817 {
818     int startPos = 0;
819     int endPos;
820     while ((endPos = headers.find('\n', startPos)) != -1) {
821         ASSERT(startPos != endPos);
822 
823         parseHeader(headers.characters() + startPos, endPos - startPos, response);
824 
825         startPos = endPos + 1;
826     }
827 
828     if (startPos != static_cast<int>(headers.length()))
829         parseHeader(headers.characters(), headers.length(), response);
830 }
831 
loadCache(unsigned storageID)832 PassRefPtr<ApplicationCache> ApplicationCacheStorage::loadCache(unsigned storageID)
833 {
834     SQLiteStatement cacheStatement(m_database,
835                                    "SELECT url, type, mimeType, textEncodingName, headers, CacheResourceData.data FROM CacheEntries INNER JOIN CacheResources ON CacheEntries.resource=CacheResources.id "
836                                    "INNER JOIN CacheResourceData ON CacheResourceData.id=CacheResources.data WHERE CacheEntries.cache=?");
837     if (cacheStatement.prepare() != SQLResultOk) {
838         LOG_ERROR("Could not prepare cache statement, error \"%s\"", m_database.lastErrorMsg());
839         return 0;
840     }
841 
842     cacheStatement.bindInt64(1, storageID);
843 
844     RefPtr<ApplicationCache> cache = ApplicationCache::create();
845 
846     int result;
847     while ((result = cacheStatement.step()) == SQLResultRow) {
848         KURL url(ParsedURLString, cacheStatement.getColumnText(0));
849 
850         unsigned type = static_cast<unsigned>(cacheStatement.getColumnInt64(1));
851 
852         Vector<char> blob;
853         cacheStatement.getColumnBlobAsVector(5, blob);
854 
855         RefPtr<SharedBuffer> data = SharedBuffer::adoptVector(blob);
856 
857         String mimeType = cacheStatement.getColumnText(2);
858         String textEncodingName = cacheStatement.getColumnText(3);
859 
860         ResourceResponse response(url, mimeType, data->size(), textEncodingName, "");
861 
862         String headers = cacheStatement.getColumnText(4);
863         parseHeaders(headers, response);
864 
865         RefPtr<ApplicationCacheResource> resource = ApplicationCacheResource::create(url, response, type, data.release());
866 
867         if (type & ApplicationCacheResource::Manifest)
868             cache->setManifestResource(resource.release());
869         else
870             cache->addResource(resource.release());
871     }
872 
873     if (result != SQLResultDone)
874         LOG_ERROR("Could not load cache resources, error \"%s\"", m_database.lastErrorMsg());
875 
876     // Load the online whitelist
877     SQLiteStatement whitelistStatement(m_database, "SELECT url FROM CacheWhitelistURLs WHERE cache=?");
878     if (whitelistStatement.prepare() != SQLResultOk)
879         return 0;
880     whitelistStatement.bindInt64(1, storageID);
881 
882     Vector<KURL> whitelist;
883     while ((result = whitelistStatement.step()) == SQLResultRow)
884         whitelist.append(KURL(ParsedURLString, whitelistStatement.getColumnText(0)));
885 
886     if (result != SQLResultDone)
887         LOG_ERROR("Could not load cache online whitelist, error \"%s\"", m_database.lastErrorMsg());
888 
889     cache->setOnlineWhitelist(whitelist);
890 
891     // Load online whitelist wildcard flag.
892     SQLiteStatement whitelistWildcardStatement(m_database, "SELECT wildcard FROM CacheAllowsAllNetworkRequests WHERE cache=?");
893     if (whitelistWildcardStatement.prepare() != SQLResultOk)
894         return 0;
895     whitelistWildcardStatement.bindInt64(1, storageID);
896 
897     result = whitelistWildcardStatement.step();
898     if (result != SQLResultRow)
899         LOG_ERROR("Could not load cache online whitelist wildcard flag, error \"%s\"", m_database.lastErrorMsg());
900 
901     cache->setAllowsAllNetworkRequests(whitelistWildcardStatement.getColumnInt64(0));
902 
903     if (whitelistWildcardStatement.step() != SQLResultDone)
904         LOG_ERROR("Too many rows for online whitelist wildcard flag");
905 
906     // Load fallback URLs.
907     SQLiteStatement fallbackStatement(m_database, "SELECT namespace, fallbackURL FROM FallbackURLs WHERE cache=?");
908     if (fallbackStatement.prepare() != SQLResultOk)
909         return 0;
910     fallbackStatement.bindInt64(1, storageID);
911 
912     FallbackURLVector fallbackURLs;
913     while ((result = fallbackStatement.step()) == SQLResultRow)
914         fallbackURLs.append(make_pair(KURL(ParsedURLString, fallbackStatement.getColumnText(0)), KURL(ParsedURLString, fallbackStatement.getColumnText(1))));
915 
916     if (result != SQLResultDone)
917         LOG_ERROR("Could not load fallback URLs, error \"%s\"", m_database.lastErrorMsg());
918 
919     cache->setFallbackURLs(fallbackURLs);
920 
921     cache->setStorageID(storageID);
922 
923     return cache.release();
924 }
925 
remove(ApplicationCache * cache)926 void ApplicationCacheStorage::remove(ApplicationCache* cache)
927 {
928     if (!cache->storageID())
929         return;
930 
931     openDatabase(false);
932     if (!m_database.isOpen())
933         return;
934 
935     ASSERT(cache->group());
936     ASSERT(cache->group()->storageID());
937 
938     // All associated data will be deleted by database triggers.
939     SQLiteStatement statement(m_database, "DELETE FROM Caches WHERE id=?");
940     if (statement.prepare() != SQLResultOk)
941         return;
942 
943     statement.bindInt64(1, cache->storageID());
944     executeStatement(statement);
945 
946     cache->clearStorageID();
947 
948     if (cache->group()->newestCache() == cache) {
949         // Currently, there are no triggers on the cache group, which is why the cache had to be removed separately above.
950         SQLiteStatement groupStatement(m_database, "DELETE FROM CacheGroups WHERE id=?");
951         if (groupStatement.prepare() != SQLResultOk)
952             return;
953 
954         groupStatement.bindInt64(1, cache->group()->storageID());
955         executeStatement(groupStatement);
956 
957         cache->group()->clearStorageID();
958     }
959 }
960 
empty()961 void ApplicationCacheStorage::empty()
962 {
963     openDatabase(false);
964 
965     if (!m_database.isOpen())
966         return;
967 
968     // Clear cache groups, caches and cache resources.
969     executeSQLCommand("DELETE FROM CacheGroups");
970     executeSQLCommand("DELETE FROM Caches");
971 
972     // Clear the storage IDs for the caches in memory.
973     // The caches will still work, but cached resources will not be saved to disk
974     // until a cache update process has been initiated.
975     CacheGroupMap::const_iterator end = m_cachesInMemory.end();
976     for (CacheGroupMap::const_iterator it = m_cachesInMemory.begin(); it != end; ++it)
977         it->second->clearStorageID();
978 }
979 
storeCopyOfCache(const String & cacheDirectory,ApplicationCacheHost * cacheHost)980 bool ApplicationCacheStorage::storeCopyOfCache(const String& cacheDirectory, ApplicationCacheHost* cacheHost)
981 {
982     ApplicationCache* cache = cacheHost->applicationCache();
983     if (!cache)
984         return true;
985 
986     // Create a new cache.
987     RefPtr<ApplicationCache> cacheCopy = ApplicationCache::create();
988 
989     cacheCopy->setOnlineWhitelist(cache->onlineWhitelist());
990     cacheCopy->setFallbackURLs(cache->fallbackURLs());
991 
992     // Traverse the cache and add copies of all resources.
993     ApplicationCache::ResourceMap::const_iterator end = cache->end();
994     for (ApplicationCache::ResourceMap::const_iterator it = cache->begin(); it != end; ++it) {
995         ApplicationCacheResource* resource = it->second.get();
996 
997         RefPtr<ApplicationCacheResource> resourceCopy = ApplicationCacheResource::create(resource->url(), resource->response(), resource->type(), resource->data());
998 
999         cacheCopy->addResource(resourceCopy.release());
1000     }
1001 
1002     // Now create a new cache group.
1003     OwnPtr<ApplicationCacheGroup> groupCopy(new ApplicationCacheGroup(cache->group()->manifestURL(), true));
1004 
1005     groupCopy->setNewestCache(cacheCopy);
1006 
1007     ApplicationCacheStorage copyStorage;
1008     copyStorage.setCacheDirectory(cacheDirectory);
1009 
1010     // Empty the cache in case something was there before.
1011     copyStorage.empty();
1012 
1013     return copyStorage.storeNewestCache(groupCopy.get());
1014 }
1015 
manifestURLs(Vector<KURL> * urls)1016 bool ApplicationCacheStorage::manifestURLs(Vector<KURL>* urls)
1017 {
1018     ASSERT(urls);
1019     openDatabase(false);
1020     if (!m_database.isOpen())
1021         return false;
1022 
1023     SQLiteStatement selectURLs(m_database, "SELECT manifestURL FROM CacheGroups");
1024 
1025     if (selectURLs.prepare() != SQLResultOk)
1026         return false;
1027 
1028     while (selectURLs.step() == SQLResultRow)
1029         urls->append(KURL(ParsedURLString, selectURLs.getColumnText(0)));
1030 
1031     return true;
1032 }
1033 
cacheGroupSize(const String & manifestURL,int64_t * size)1034 bool ApplicationCacheStorage::cacheGroupSize(const String& manifestURL, int64_t* size)
1035 {
1036     ASSERT(size);
1037     openDatabase(false);
1038     if (!m_database.isOpen())
1039         return false;
1040 
1041     SQLiteStatement statement(m_database, "SELECT sum(Caches.size) FROM Caches INNER JOIN CacheGroups ON Caches.cacheGroup=CacheGroups.id WHERE CacheGroups.manifestURL=?");
1042     if (statement.prepare() != SQLResultOk)
1043         return false;
1044 
1045     statement.bindText(1, manifestURL);
1046 
1047     int result = statement.step();
1048     if (result == SQLResultDone)
1049         return false;
1050 
1051     if (result != SQLResultRow) {
1052         LOG_ERROR("Could not get the size of the cache group, error \"%s\"", m_database.lastErrorMsg());
1053         return false;
1054     }
1055 
1056     *size = statement.getColumnInt64(0);
1057     return true;
1058 }
1059 
deleteCacheGroup(const String & manifestURL)1060 bool ApplicationCacheStorage::deleteCacheGroup(const String& manifestURL)
1061 {
1062     SQLiteTransaction deleteTransaction(m_database);
1063     // Check to see if the group is in memory.
1064     ApplicationCacheGroup* group = m_cachesInMemory.get(manifestURL);
1065     if (group)
1066         cacheGroupMadeObsolete(group);
1067     else {
1068         // The cache group is not in memory, so remove it from the disk.
1069         openDatabase(false);
1070         if (!m_database.isOpen())
1071             return false;
1072 
1073         SQLiteStatement idStatement(m_database, "SELECT id FROM CacheGroups WHERE manifestURL=?");
1074         if (idStatement.prepare() != SQLResultOk)
1075             return false;
1076 
1077         idStatement.bindText(1, manifestURL);
1078 
1079         int result = idStatement.step();
1080         if (result == SQLResultDone)
1081             return false;
1082 
1083         if (result != SQLResultRow) {
1084             LOG_ERROR("Could not load cache group id, error \"%s\"", m_database.lastErrorMsg());
1085             return false;
1086         }
1087 
1088         int64_t groupId = idStatement.getColumnInt64(0);
1089 
1090         SQLiteStatement cacheStatement(m_database, "DELETE FROM Caches WHERE cacheGroup=?");
1091         if (cacheStatement.prepare() != SQLResultOk)
1092             return false;
1093 
1094         SQLiteStatement groupStatement(m_database, "DELETE FROM CacheGroups WHERE id=?");
1095         if (groupStatement.prepare() != SQLResultOk)
1096             return false;
1097 
1098         cacheStatement.bindInt64(1, groupId);
1099         executeStatement(cacheStatement);
1100         groupStatement.bindInt64(1, groupId);
1101         executeStatement(groupStatement);
1102     }
1103 
1104     deleteTransaction.commit();
1105     return true;
1106 }
1107 
vacuumDatabaseFile()1108 void ApplicationCacheStorage::vacuumDatabaseFile()
1109 {
1110     openDatabase(false);
1111     if (!m_database.isOpen())
1112         return;
1113 
1114     m_database.runVacuumCommand();
1115 }
1116 
checkForMaxSizeReached()1117 void ApplicationCacheStorage::checkForMaxSizeReached()
1118 {
1119     if (m_database.lastError() == SQLResultFull)
1120         m_isMaximumSizeReached = true;
1121 }
1122 
ApplicationCacheStorage()1123 ApplicationCacheStorage::ApplicationCacheStorage()
1124     : m_maximumSize(INT_MAX)
1125     , m_isMaximumSizeReached(false)
1126 {
1127 }
1128 
cacheStorage()1129 ApplicationCacheStorage& cacheStorage()
1130 {
1131     DEFINE_STATIC_LOCAL(ApplicationCacheStorage, storage, ());
1132 
1133     return storage;
1134 }
1135 
1136 } // namespace WebCore
1137 
1138 #endif // ENABLE(OFFLINE_WEB_APPLICATIONS)
1139