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