• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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