1 /*
2 * Copyright (C) 2011 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. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26 #include "config.h"
27 #include "StorageTracker.h"
28
29 #if ENABLE(DOM_STORAGE)
30
31 #include "DatabaseThread.h"
32 #include "FileSystem.h"
33 #include "LocalStorageTask.h"
34 #include "LocalStorageThread.h"
35 #include "Logging.h"
36 #include "PageGroup.h"
37 #include "SQLiteFileSystem.h"
38 #include "SQLiteStatement.h"
39 #include "SecurityOrigin.h"
40 #include "StorageTrackerClient.h"
41 #include "TextEncoding.h"
42 #include <wtf/MainThread.h>
43 #include <wtf/StdLibExtras.h>
44 #include <wtf/Vector.h>
45 #include <wtf/text/CString.h>
46
47 namespace WebCore {
48
49 static StorageTracker* storageTracker = 0;
50
initializeTracker(const String & storagePath)51 void StorageTracker::initializeTracker(const String& storagePath)
52 {
53 ASSERT(isMainThread());
54 ASSERT(!storageTracker);
55
56 if (!storageTracker)
57 storageTracker = new StorageTracker(storagePath);
58
59 // Make sure text encoding maps have been built on the main thread, as the StorageTracker thread might try to do it there instead.
60 // FIXME (<rdar://problem/9127819>): Is there a more explicit way of doing this besides accessing the UTF8Encoding?
61 UTF8Encoding();
62
63 SQLiteFileSystem::registerSQLiteVFS();
64 storageTracker->setIsActive(true);
65 storageTracker->m_thread->start();
66 storageTracker->importOriginIdentifiers();
67 }
68
tracker()69 StorageTracker& StorageTracker::tracker()
70 {
71 if (!storageTracker)
72 storageTracker = new StorageTracker("");
73
74 return *storageTracker;
75 }
76
StorageTracker(const String & storagePath)77 StorageTracker::StorageTracker(const String& storagePath)
78 : m_client(0)
79 , m_thread(LocalStorageThread::create())
80 , m_isActive(false)
81 {
82 setStorageDirectoryPath(storagePath);
83 }
84
setStorageDirectoryPath(const String & path)85 void StorageTracker::setStorageDirectoryPath(const String& path)
86 {
87 MutexLocker lockDatabase(m_databaseGuard);
88 ASSERT(!m_database.isOpen());
89
90 m_storageDirectoryPath = path.threadsafeCopy();
91 }
92
trackerDatabasePath()93 String StorageTracker::trackerDatabasePath()
94 {
95 ASSERT(!m_databaseGuard.tryLock());
96 return SQLiteFileSystem::appendDatabaseFileNameToPath(m_storageDirectoryPath, "StorageTracker.db");
97 }
98
openTrackerDatabase(bool createIfDoesNotExist)99 void StorageTracker::openTrackerDatabase(bool createIfDoesNotExist)
100 {
101 ASSERT(m_isActive);
102 ASSERT(!isMainThread());
103 ASSERT(!m_databaseGuard.tryLock());
104
105 if (m_database.isOpen())
106 return;
107
108 String databasePath = trackerDatabasePath();
109
110 if (!SQLiteFileSystem::ensureDatabaseFileExists(databasePath, createIfDoesNotExist)) {
111 if (createIfDoesNotExist)
112 LOG_ERROR("Failed to create database file '%s'", databasePath.ascii().data());
113 return;
114 }
115
116 if (!m_database.open(databasePath)) {
117 LOG_ERROR("Failed to open databasePath %s.", databasePath.ascii().data());
118 return;
119 }
120
121 m_database.disableThreadingChecks();
122
123 if (!m_database.tableExists("Origins")) {
124 if (!m_database.executeCommand("CREATE TABLE Origins (origin TEXT UNIQUE ON CONFLICT REPLACE, path TEXT);"))
125 LOG_ERROR("Failed to create Origins table.");
126 }
127 }
128
importOriginIdentifiers()129 void StorageTracker::importOriginIdentifiers()
130 {
131 if (!m_isActive)
132 return;
133
134 ASSERT(isMainThread());
135 ASSERT(m_thread);
136
137 m_thread->scheduleTask(LocalStorageTask::createOriginIdentifiersImport());
138 }
139
syncImportOriginIdentifiers()140 void StorageTracker::syncImportOriginIdentifiers()
141 {
142 ASSERT(m_isActive);
143
144 ASSERT(!isMainThread());
145
146 {
147 MutexLocker lockDatabase(m_databaseGuard);
148
149 // Don't force creation of StorageTracker's db just because a tracker
150 // was initialized. It will be created if local storage dbs are found
151 // by syncFileSystemAndTrackerDatabse() or the next time a local storage
152 // db is created by StorageAreaSync.
153 openTrackerDatabase(false);
154
155 if (m_database.isOpen()) {
156 SQLiteStatement statement(m_database, "SELECT origin FROM Origins");
157 if (statement.prepare() != SQLResultOk) {
158 LOG_ERROR("Failed to prepare statement.");
159 return;
160 }
161
162 int result;
163
164 {
165 MutexLocker lockOrigins(m_originSetGuard);
166 while ((result = statement.step()) == SQLResultRow)
167 m_originSet.add(statement.getColumnText(0).threadsafeCopy());
168 }
169
170 if (result != SQLResultDone) {
171 LOG_ERROR("Failed to read in all origins from the database.");
172 return;
173 }
174 }
175 }
176
177 syncFileSystemAndTrackerDatabase();
178
179 {
180 MutexLocker lockClient(m_clientGuard);
181 if (m_client) {
182 MutexLocker lockOrigins(m_originSetGuard);
183 OriginSet::const_iterator end = m_originSet.end();
184 for (OriginSet::const_iterator it = m_originSet.begin(); it != end; ++it)
185 m_client->dispatchDidModifyOrigin(*it);
186 }
187 }
188 }
189
syncFileSystemAndTrackerDatabase()190 void StorageTracker::syncFileSystemAndTrackerDatabase()
191 {
192 ASSERT(!isMainThread());
193 ASSERT(m_isActive);
194
195 m_databaseGuard.lock();
196 DEFINE_STATIC_LOCAL(const String, fileMatchPattern, ("*.localstorage"));
197 DEFINE_STATIC_LOCAL(const String, fileExt, (".localstorage"));
198 DEFINE_STATIC_LOCAL(const unsigned, fileExtLength, (fileExt.length()));
199 m_databaseGuard.unlock();
200
201 Vector<String> paths;
202 {
203 MutexLocker lock(m_databaseGuard);
204 paths = listDirectory(m_storageDirectoryPath, fileMatchPattern);
205 }
206
207 // Use a copy of m_originSet to find expired entries and to schedule their
208 // deletions from disk and from m_originSet.
209 OriginSet originSetCopy;
210 {
211 MutexLocker lock(m_originSetGuard);
212 OriginSet::const_iterator end = m_originSet.end();
213 for (OriginSet::const_iterator it = m_originSet.begin(); it != end; ++it)
214 originSetCopy.add((*it).threadsafeCopy());
215 }
216
217 // Add missing StorageTracker records.
218 OriginSet foundOrigins;
219 Vector<String>::const_iterator end = paths.end();
220 for (Vector<String>::const_iterator it = paths.begin(); it != end; ++it) {
221 String path = *it;
222 if (path.endsWith(fileExt, true) && path.length() > fileExtLength) {
223 String file = pathGetFileName(path);
224 String originIdentifier = file.substring(0, file.length() - fileExtLength);
225 if (!originSetCopy.contains(originIdentifier))
226 syncSetOriginDetails(originIdentifier, path);
227
228 foundOrigins.add(originIdentifier);
229 }
230 }
231
232 // Delete stale StorageTracker records.
233 OriginSet::const_iterator setEnd = originSetCopy.end();
234 for (OriginSet::const_iterator it = originSetCopy.begin(); it != setEnd; ++it) {
235 if (!foundOrigins.contains(*it)) {
236 RefPtr<StringImpl> originIdentifier = (*it).threadsafeCopy().impl();
237 callOnMainThread(deleteOriginOnMainThread, originIdentifier.release().leakRef());
238 }
239 }
240 }
241
setOriginDetails(const String & originIdentifier,const String & databaseFile)242 void StorageTracker::setOriginDetails(const String& originIdentifier, const String& databaseFile)
243 {
244 if (!m_isActive)
245 return;
246
247 {
248 MutexLocker lockOrigins(m_originSetGuard);
249
250 if (m_originSet.contains(originIdentifier))
251 return;
252
253 m_originSet.add(originIdentifier);
254 }
255
256 OwnPtr<LocalStorageTask> task = LocalStorageTask::createSetOriginDetails(originIdentifier.threadsafeCopy(), databaseFile);
257
258 if (isMainThread()) {
259 ASSERT(m_thread);
260 m_thread->scheduleTask(task.release());
261 } else
262 callOnMainThread(scheduleTask, reinterpret_cast<void*>(task.leakPtr()));
263 }
264
scheduleTask(void * taskIn)265 void StorageTracker::scheduleTask(void* taskIn)
266 {
267 ASSERT(isMainThread());
268 ASSERT(StorageTracker::tracker().m_thread);
269
270 OwnPtr<LocalStorageTask> task = adoptPtr(reinterpret_cast<LocalStorageTask*>(taskIn));
271
272 StorageTracker::tracker().m_thread->scheduleTask(task.release());
273 }
274
syncSetOriginDetails(const String & originIdentifier,const String & databaseFile)275 void StorageTracker::syncSetOriginDetails(const String& originIdentifier, const String& databaseFile)
276 {
277 ASSERT(!isMainThread());
278
279 MutexLocker lockDatabase(m_databaseGuard);
280
281 openTrackerDatabase(true);
282
283 if (!m_database.isOpen())
284 return;
285
286 SQLiteStatement statement(m_database, "INSERT INTO Origins VALUES (?, ?)");
287 if (statement.prepare() != SQLResultOk) {
288 LOG_ERROR("Unable to establish origin '%s' in the tracker", originIdentifier.ascii().data());
289 return;
290 }
291
292 statement.bindText(1, originIdentifier);
293 statement.bindText(2, databaseFile);
294
295 if (statement.step() != SQLResultDone)
296 LOG_ERROR("Unable to establish origin '%s' in the tracker", originIdentifier.ascii().data());
297
298 {
299 MutexLocker lockOrigins(m_originSetGuard);
300 if (!m_originSet.contains(originIdentifier))
301 m_originSet.add(originIdentifier);
302 }
303
304 {
305 MutexLocker lockClient(m_clientGuard);
306 if (m_client)
307 m_client->dispatchDidModifyOrigin(originIdentifier);
308 }
309 }
310
origins(Vector<RefPtr<SecurityOrigin>> & result)311 void StorageTracker::origins(Vector<RefPtr<SecurityOrigin> >& result)
312 {
313 ASSERT(m_isActive);
314
315 if (!m_isActive)
316 return;
317
318 MutexLocker lockOrigins(m_originSetGuard);
319
320 OriginSet::const_iterator end = m_originSet.end();
321 for (OriginSet::const_iterator it = m_originSet.begin(); it != end; ++it)
322 result.append(SecurityOrigin::createFromDatabaseIdentifier(*it));
323 }
324
deleteAllOrigins()325 void StorageTracker::deleteAllOrigins()
326 {
327 ASSERT(m_isActive);
328 ASSERT(isMainThread());
329 ASSERT(m_thread);
330
331 if (!m_isActive)
332 return;
333
334 {
335 MutexLocker lockOrigins(m_originSetGuard);
336 willDeleteAllOrigins();
337 m_originSet.clear();
338 }
339
340 PageGroup::clearLocalStorageForAllOrigins();
341
342 m_thread->scheduleTask(LocalStorageTask::createDeleteAllOrigins());
343 }
344
syncDeleteAllOrigins()345 void StorageTracker::syncDeleteAllOrigins()
346 {
347 ASSERT(!isMainThread());
348
349 MutexLocker lockDatabase(m_databaseGuard);
350
351 openTrackerDatabase(false);
352 if (!m_database.isOpen())
353 return;
354
355 SQLiteStatement statement(m_database, "SELECT origin, path FROM Origins");
356 if (statement.prepare() != SQLResultOk) {
357 LOG_ERROR("Failed to prepare statement.");
358 return;
359 }
360
361 int result;
362 while ((result = statement.step()) == SQLResultRow) {
363 if (!canDeleteOrigin(statement.getColumnText(0)))
364 continue;
365
366 SQLiteFileSystem::deleteDatabaseFile(statement.getColumnText(1));
367
368 {
369 MutexLocker lockClient(m_clientGuard);
370 if (m_client)
371 m_client->dispatchDidModifyOrigin(statement.getColumnText(0));
372 }
373 }
374
375 if (result != SQLResultDone)
376 LOG_ERROR("Failed to read in all origins from the database.");
377
378 if (m_database.isOpen())
379 m_database.close();
380
381 if (!SQLiteFileSystem::deleteDatabaseFile(trackerDatabasePath())) {
382 // In the case where it is not possible to delete the database file (e.g some other program
383 // like a virus scanner is accessing it), make sure to remove all entries.
384 openTrackerDatabase(false);
385 if (!m_database.isOpen())
386 return;
387 SQLiteStatement deleteStatement(m_database, "DELETE FROM Origins");
388 if (deleteStatement.prepare() != SQLResultOk) {
389 LOG_ERROR("Unable to prepare deletion of all origins");
390 return;
391 }
392 if (!deleteStatement.executeCommand()) {
393 LOG_ERROR("Unable to execute deletion of all origins");
394 return;
395 }
396 }
397 SQLiteFileSystem::deleteEmptyDatabaseDirectory(m_storageDirectoryPath);
398 }
399
deleteOriginOnMainThread(void * originIdentifier)400 void StorageTracker::deleteOriginOnMainThread(void* originIdentifier)
401 {
402 ASSERT(isMainThread());
403
404 String identifier = adoptRef(reinterpret_cast<StringImpl*>(originIdentifier));
405 tracker().deleteOrigin(identifier);
406 }
407
deleteOrigin(const String & originIdentifier)408 void StorageTracker::deleteOrigin(const String& originIdentifier)
409 {
410 deleteOrigin(SecurityOrigin::createFromDatabaseIdentifier(originIdentifier).get());
411 }
412
deleteOrigin(SecurityOrigin * origin)413 void StorageTracker::deleteOrigin(SecurityOrigin* origin)
414 {
415 ASSERT(m_isActive);
416 ASSERT(isMainThread());
417 ASSERT(m_thread);
418
419 if (!m_isActive)
420 return;
421
422 // Before deleting database, we need to clear in-memory local storage data
423 // in StorageArea, and to close the StorageArea db. It's possible for an
424 // item to be added immediately after closing the db and cause StorageAreaSync
425 // to reopen the db before the db is deleted by a StorageTracker thread.
426 // In this case, reopening the db in StorageAreaSync will cancel a pending
427 // StorageTracker db deletion.
428 PageGroup::clearLocalStorageForOrigin(origin);
429
430 String originId = origin->databaseIdentifier();
431
432 {
433 MutexLocker lockOrigins(m_originSetGuard);
434 willDeleteOrigin(originId);
435 m_originSet.remove(originId);
436 }
437
438 m_thread->scheduleTask(LocalStorageTask::createDeleteOrigin(originId));
439 }
440
syncDeleteOrigin(const String & originIdentifier)441 void StorageTracker::syncDeleteOrigin(const String& originIdentifier)
442 {
443 ASSERT(!isMainThread());
444
445 MutexLocker lockDatabase(m_databaseGuard);
446
447 if (!canDeleteOrigin(originIdentifier)) {
448 LOG_ERROR("Attempted to delete origin '%s' while it was being created\n", originIdentifier.ascii().data());
449 return;
450 }
451
452 openTrackerDatabase(false);
453 if (!m_database.isOpen())
454 return;
455
456 // Get origin's db file path, delete entry in tracker's db, then delete db file.
457 SQLiteStatement pathStatement(m_database, "SELECT path FROM Origins WHERE origin=?");
458 if (pathStatement.prepare() != SQLResultOk) {
459 LOG_ERROR("Unable to prepare selection of path for origin '%s'", originIdentifier.ascii().data());
460 return;
461 }
462 pathStatement.bindText(1, originIdentifier);
463 int result = pathStatement.step();
464 if (result != SQLResultRow) {
465 LOG_ERROR("Unable to find origin '%s' in Origins table", originIdentifier.ascii().data());
466 return;
467 }
468
469 String path = pathStatement.getColumnText(0);
470
471 ASSERT(!path.isEmpty());
472
473 SQLiteStatement deleteStatement(m_database, "DELETE FROM Origins where origin=?");
474 if (deleteStatement.prepare() != SQLResultOk) {
475 LOG_ERROR("Unable to prepare deletion of origin '%s'", originIdentifier.ascii().data());
476 return;
477 }
478 deleteStatement.bindText(1, originIdentifier);
479 if (!deleteStatement.executeCommand()) {
480 LOG_ERROR("Unable to execute deletion of origin '%s'", originIdentifier.ascii().data());
481 return;
482 }
483
484 SQLiteFileSystem::deleteDatabaseFile(path);
485
486 bool shouldDeleteTrackerFiles = false;
487 {
488 MutexLocker originLock(m_originSetGuard);
489 m_originSet.remove(originIdentifier);
490 shouldDeleteTrackerFiles = m_originSet.isEmpty();
491 }
492
493 if (shouldDeleteTrackerFiles) {
494 m_database.close();
495 SQLiteFileSystem::deleteDatabaseFile(trackerDatabasePath());
496 SQLiteFileSystem::deleteEmptyDatabaseDirectory(m_storageDirectoryPath);
497 }
498
499 {
500 MutexLocker lockClient(m_clientGuard);
501 if (m_client)
502 m_client->dispatchDidModifyOrigin(originIdentifier);
503 }
504 }
505
willDeleteAllOrigins()506 void StorageTracker::willDeleteAllOrigins()
507 {
508 ASSERT(!m_originSetGuard.tryLock());
509
510 OriginSet::const_iterator end = m_originSet.end();
511 for (OriginSet::const_iterator it = m_originSet.begin(); it != end; ++it)
512 m_originsBeingDeleted.add((*it).threadsafeCopy());
513 }
514
willDeleteOrigin(const String & originIdentifier)515 void StorageTracker::willDeleteOrigin(const String& originIdentifier)
516 {
517 ASSERT(isMainThread());
518 ASSERT(!m_originSetGuard.tryLock());
519
520 m_originsBeingDeleted.add(originIdentifier);
521 }
522
canDeleteOrigin(const String & originIdentifier)523 bool StorageTracker::canDeleteOrigin(const String& originIdentifier)
524 {
525 ASSERT(!m_databaseGuard.tryLock());
526 MutexLocker lockOrigins(m_originSetGuard);
527 return m_originsBeingDeleted.contains(originIdentifier);
528 }
529
cancelDeletingOrigin(const String & originIdentifier)530 void StorageTracker::cancelDeletingOrigin(const String& originIdentifier)
531 {
532 if (!m_isActive)
533 return;
534
535 MutexLocker lockDatabase(m_databaseGuard);
536 MutexLocker lockOrigins(m_originSetGuard);
537 if (!m_originsBeingDeleted.isEmpty())
538 m_originsBeingDeleted.remove(originIdentifier);
539 }
540
setClient(StorageTrackerClient * client)541 void StorageTracker::setClient(StorageTrackerClient* client)
542 {
543 MutexLocker lockClient(m_clientGuard);
544 m_client = client;
545 }
546
syncLocalStorage()547 void StorageTracker::syncLocalStorage()
548 {
549 PageGroup::syncLocalStorage();
550 }
551
isActive()552 bool StorageTracker::isActive()
553 {
554 return m_isActive;
555 }
556
setIsActive(bool flag)557 void StorageTracker::setIsActive(bool flag)
558 {
559 m_isActive = flag;
560 }
561
562 } // namespace WebCore
563
564 #endif // ENABLE(DOM_STORAGE)
565