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#include "DatabaseThread.h"
30#include "FileSystem.h"
31#include "Logging.h"
32#include "PageGroup.h"
33#include "SQLiteDatabaseTracker.h"
34#include "SQLiteFileSystem.h"
35#include "SQLiteStatement.h"
36#include "SecurityOrigin.h"
37#include "StorageThread.h"
38#include "StorageTrackerClient.h"
39#include "TextEncoding.h"
40#include <wtf/Functional.h>
41#include <wtf/MainThread.h>
42#include <wtf/StdLibExtras.h>
43#include <wtf/Vector.h>
44#include <wtf/text/CString.h>
45
46namespace WebCore {
47
48static StorageTracker* storageTracker = 0;
49
50// If there is no document referencing a storage database, close the underlying database
51// after it has been idle for m_StorageDatabaseIdleInterval seconds.
52static const double DefaultStorageDatabaseIdleInterval = 300;
53
54void StorageTracker::initializeTracker(const String& storagePath, StorageTrackerClient* client)
55{
56    ASSERT(isMainThread());
57    ASSERT(!storageTracker || !storageTracker->m_client);
58
59    if (!storageTracker)
60        storageTracker = new StorageTracker(storagePath);
61
62    storageTracker->m_client = client;
63    storageTracker->m_needsInitialization = true;
64}
65
66void StorageTracker::internalInitialize()
67{
68    m_needsInitialization = false;
69
70    ASSERT(isMainThread());
71
72    // Make sure text encoding maps have been built on the main thread, as the StorageTracker thread might try to do it there instead.
73    // FIXME (<rdar://problem/9127819>): Is there a more explicit way of doing this besides accessing the UTF8Encoding?
74    UTF8Encoding();
75
76    storageTracker->setIsActive(true);
77    storageTracker->m_thread->start();
78    storageTracker->importOriginIdentifiers();
79}
80
81StorageTracker& StorageTracker::tracker()
82{
83    if (!storageTracker)
84        storageTracker = new StorageTracker("");
85    if (storageTracker->m_needsInitialization)
86        storageTracker->internalInitialize();
87
88    return *storageTracker;
89}
90
91StorageTracker::StorageTracker(const String& storagePath)
92    : m_storageDirectoryPath(storagePath.isolatedCopy())
93    , m_client(0)
94    , m_thread(std::make_unique<StorageThread>())
95    , m_isActive(false)
96    , m_needsInitialization(false)
97    , m_StorageDatabaseIdleInterval(DefaultStorageDatabaseIdleInterval)
98{
99}
100
101void StorageTracker::setDatabaseDirectoryPath(const String& path)
102{
103    MutexLocker locker(m_databaseMutex);
104
105    if (m_database.isOpen())
106        m_database.close();
107
108    m_storageDirectoryPath = path.isolatedCopy();
109
110    {
111        MutexLocker locker(m_originSetMutex);
112        m_originSet.clear();
113    }
114
115    if (!m_isActive)
116        return;
117
118    importOriginIdentifiers();
119}
120
121String StorageTracker::databaseDirectoryPath() const
122{
123    return m_storageDirectoryPath.isolatedCopy();
124}
125
126String StorageTracker::trackerDatabasePath()
127{
128    ASSERT(!m_databaseMutex.tryLock());
129    return SQLiteFileSystem::appendDatabaseFileNameToPath(m_storageDirectoryPath, "StorageTracker.db");
130}
131
132void StorageTracker::openTrackerDatabase(bool createIfDoesNotExist)
133{
134    ASSERT(m_isActive);
135    ASSERT(!isMainThread());
136
137    SQLiteTransactionInProgressAutoCounter transactionCounter;
138
139    ASSERT(!m_databaseMutex.tryLock());
140
141    if (m_database.isOpen())
142        return;
143
144    String databasePath = trackerDatabasePath();
145
146    if (!SQLiteFileSystem::ensureDatabaseFileExists(databasePath, createIfDoesNotExist)) {
147        if (createIfDoesNotExist)
148            LOG_ERROR("Failed to create database file '%s'", databasePath.ascii().data());
149        return;
150    }
151
152    if (!m_database.open(databasePath)) {
153        LOG_ERROR("Failed to open databasePath %s.", databasePath.ascii().data());
154        return;
155    }
156
157    m_database.disableThreadingChecks();
158
159    if (!m_database.tableExists("Origins")) {
160        if (!m_database.executeCommand("CREATE TABLE Origins (origin TEXT UNIQUE ON CONFLICT REPLACE, path TEXT);"))
161            LOG_ERROR("Failed to create Origins table.");
162    }
163}
164
165void StorageTracker::importOriginIdentifiers()
166{
167    if (!m_isActive)
168        return;
169
170    ASSERT(isMainThread());
171    ASSERT(m_thread);
172
173    m_thread->dispatch(bind(&StorageTracker::syncImportOriginIdentifiers, this));
174}
175
176void StorageTracker::finishedImportingOriginIdentifiers()
177{
178    MutexLocker locker(m_databaseMutex);
179    if (m_client)
180        m_client->didFinishLoadingOrigins();
181}
182
183void StorageTracker::syncImportOriginIdentifiers()
184{
185    ASSERT(m_isActive);
186
187    ASSERT(!isMainThread());
188
189    {
190        MutexLocker locker(m_databaseMutex);
191
192        // Don't force creation of StorageTracker's db just because a tracker
193        // was initialized. It will be created if local storage dbs are found
194        // by syncFileSystemAndTrackerDatabse() or the next time a local storage
195        // db is created by StorageAreaSync.
196        openTrackerDatabase(false);
197
198        if (m_database.isOpen()) {
199            SQLiteTransactionInProgressAutoCounter transactionCounter;
200
201            SQLiteStatement statement(m_database, "SELECT origin FROM Origins");
202            if (statement.prepare() != SQLResultOk) {
203                LOG_ERROR("Failed to prepare statement.");
204                return;
205            }
206
207            int result;
208
209            {
210                MutexLocker lockOrigins(m_originSetMutex);
211                while ((result = statement.step()) == SQLResultRow)
212                    m_originSet.add(statement.getColumnText(0).isolatedCopy());
213            }
214
215            if (result != SQLResultDone) {
216                LOG_ERROR("Failed to read in all origins from the database.");
217                return;
218            }
219        }
220    }
221
222    syncFileSystemAndTrackerDatabase();
223
224    {
225        MutexLocker locker(m_clientMutex);
226
227        if (m_client) {
228            MutexLocker locker(m_originSetMutex);
229            OriginSet::const_iterator end = m_originSet.end();
230            for (OriginSet::const_iterator it = m_originSet.begin(); it != end; ++it)
231                m_client->dispatchDidModifyOrigin(*it);
232        }
233    }
234
235    callOnMainThread(bind(&StorageTracker::finishedImportingOriginIdentifiers, this));
236}
237
238void StorageTracker::syncFileSystemAndTrackerDatabase()
239{
240    ASSERT(!isMainThread());
241
242    SQLiteTransactionInProgressAutoCounter transactionCounter;
243
244    ASSERT(m_isActive);
245
246    Vector<String> paths;
247    {
248        MutexLocker locker(m_databaseMutex);
249        paths = listDirectory(m_storageDirectoryPath, "*.localstorage");
250    }
251
252    // Use a copy of m_originSet to find expired entries and to schedule their
253    // deletions from disk and from m_originSet.
254    OriginSet originSetCopy;
255    {
256        MutexLocker locker(m_originSetMutex);
257        for (OriginSet::const_iterator it = m_originSet.begin(), end = m_originSet.end(); it != end; ++it)
258            originSetCopy.add((*it).isolatedCopy());
259    }
260
261    // Add missing StorageTracker records.
262    OriginSet foundOrigins;
263    String fileExtension = ASCIILiteral(".localstorage");
264
265    for (Vector<String>::const_iterator it = paths.begin(), end = paths.end(); it != end; ++it) {
266        const String& path = *it;
267
268        if (path.length() > fileExtension.length() && path.endsWith(fileExtension, true)) {
269            String file = pathGetFileName(path);
270            String originIdentifier = file.substring(0, file.length() - fileExtension.length());
271            if (!originSetCopy.contains(originIdentifier))
272                syncSetOriginDetails(originIdentifier, path);
273
274            foundOrigins.add(originIdentifier);
275        }
276    }
277
278    // Delete stale StorageTracker records.
279    for (OriginSet::const_iterator it = originSetCopy.begin(), end = originSetCopy.end(); it != end; ++it) {
280        const String& originIdentifier = *it;
281        if (foundOrigins.contains(originIdentifier))
282            continue;
283
284        callOnMainThread(bind(&StorageTracker::deleteOriginWithIdentifier, this, originIdentifier.isolatedCopy()));
285    }
286}
287
288void StorageTracker::setOriginDetails(const String& originIdentifier, const String& databaseFile)
289{
290    if (!m_isActive)
291        return;
292
293    {
294        MutexLocker locker(m_originSetMutex);
295
296        if (m_originSet.contains(originIdentifier))
297            return;
298
299        m_originSet.add(originIdentifier);
300    }
301
302    Function<void ()> function = bind(&StorageTracker::syncSetOriginDetails, this, originIdentifier.isolatedCopy(), databaseFile.isolatedCopy());
303
304    if (isMainThread()) {
305        ASSERT(m_thread);
306        m_thread->dispatch(function);
307    } else {
308        // FIXME: This weird ping-ponging was done to fix a deadlock. We should figure out a cleaner way to avoid it instead.
309        callOnMainThread(bind(&StorageThread::dispatch, m_thread.get(), function));
310    }
311}
312
313void StorageTracker::syncSetOriginDetails(const String& originIdentifier, const String& databaseFile)
314{
315    ASSERT(!isMainThread());
316
317    SQLiteTransactionInProgressAutoCounter transactionCounter;
318
319    MutexLocker locker(m_databaseMutex);
320
321    openTrackerDatabase(true);
322
323    if (!m_database.isOpen())
324        return;
325
326    SQLiteStatement statement(m_database, "INSERT INTO Origins VALUES (?, ?)");
327    if (statement.prepare() != SQLResultOk) {
328        LOG_ERROR("Unable to establish origin '%s' in the tracker", originIdentifier.ascii().data());
329        return;
330    }
331
332    statement.bindText(1, originIdentifier);
333    statement.bindText(2, databaseFile);
334
335    if (statement.step() != SQLResultDone)
336        LOG_ERROR("Unable to establish origin '%s' in the tracker", originIdentifier.ascii().data());
337
338    {
339        MutexLocker locker(m_originSetMutex);
340        if (!m_originSet.contains(originIdentifier))
341            m_originSet.add(originIdentifier);
342    }
343
344    {
345        MutexLocker locker(m_clientMutex);
346        if (m_client)
347            m_client->dispatchDidModifyOrigin(originIdentifier);
348    }
349}
350
351void StorageTracker::origins(Vector<RefPtr<SecurityOrigin>>& result)
352{
353    ASSERT(m_isActive);
354
355    if (!m_isActive)
356        return;
357
358    MutexLocker locker(m_originSetMutex);
359
360    for (OriginSet::const_iterator it = m_originSet.begin(), end = m_originSet.end(); it != end; ++it)
361        result.append(SecurityOrigin::createFromDatabaseIdentifier(*it));
362}
363
364void StorageTracker::deleteAllOrigins()
365{
366    ASSERT(m_isActive);
367    ASSERT(isMainThread());
368    ASSERT(m_thread);
369
370    if (!m_isActive)
371        return;
372
373    {
374        MutexLocker locker(m_originSetMutex);
375        willDeleteAllOrigins();
376        m_originSet.clear();
377    }
378
379    PageGroup::clearLocalStorageForAllOrigins();
380
381    m_thread->dispatch(bind(&StorageTracker::syncDeleteAllOrigins, this));
382}
383
384void StorageTracker::syncDeleteAllOrigins()
385{
386    ASSERT(!isMainThread());
387
388    SQLiteTransactionInProgressAutoCounter transactionCounter;
389
390    MutexLocker locker(m_databaseMutex);
391
392    openTrackerDatabase(false);
393    if (!m_database.isOpen())
394        return;
395
396    SQLiteStatement statement(m_database, "SELECT origin, path FROM Origins");
397    if (statement.prepare() != SQLResultOk) {
398        LOG_ERROR("Failed to prepare statement.");
399        return;
400    }
401
402    int result;
403    while ((result = statement.step()) == SQLResultRow) {
404        if (!canDeleteOrigin(statement.getColumnText(0)))
405            continue;
406
407        SQLiteFileSystem::deleteDatabaseFile(statement.getColumnText(1));
408
409        {
410            MutexLocker locker(m_clientMutex);
411            if (m_client)
412                m_client->dispatchDidModifyOrigin(statement.getColumnText(0));
413        }
414    }
415
416    if (result != SQLResultDone)
417        LOG_ERROR("Failed to read in all origins from the database.");
418
419    if (m_database.isOpen()) {
420#if PLATFORM(IOS)
421        SQLiteFileSystem::truncateDatabaseFile(m_database.sqlite3Handle());
422#endif
423        m_database.close();
424    }
425
426#if !PLATFORM(IOS)
427    if (!SQLiteFileSystem::deleteDatabaseFile(trackerDatabasePath())) {
428        // In the case where it is not possible to delete the database file (e.g some other program
429        // like a virus scanner is accessing it), make sure to remove all entries.
430        openTrackerDatabase(false);
431        if (!m_database.isOpen())
432            return;
433        SQLiteStatement deleteStatement(m_database, "DELETE FROM Origins");
434        if (deleteStatement.prepare() != SQLResultOk) {
435            LOG_ERROR("Unable to prepare deletion of all origins");
436            return;
437        }
438        if (!deleteStatement.executeCommand()) {
439            LOG_ERROR("Unable to execute deletion of all origins");
440            return;
441        }
442    }
443    SQLiteFileSystem::deleteEmptyDatabaseDirectory(m_storageDirectoryPath);
444#endif
445}
446
447void StorageTracker::deleteOriginWithIdentifier(const String& originIdentifier)
448{
449    deleteOrigin(SecurityOrigin::createFromDatabaseIdentifier(originIdentifier).get());
450}
451
452void StorageTracker::deleteOrigin(SecurityOrigin* origin)
453{
454    ASSERT(m_isActive);
455    ASSERT(isMainThread());
456    ASSERT(m_thread);
457
458    if (!m_isActive)
459        return;
460
461    // Before deleting database, we need to clear in-memory local storage data
462    // in StorageArea, and to close the StorageArea db. It's possible for an
463    // item to be added immediately after closing the db and cause StorageAreaSync
464    // to reopen the db before the db is deleted by a StorageTracker thread.
465    // In this case, reopening the db in StorageAreaSync will cancel a pending
466    // StorageTracker db deletion.
467    PageGroup::clearLocalStorageForOrigin(origin);
468
469    String originId = origin->databaseIdentifier();
470
471    {
472        MutexLocker locker(m_originSetMutex);
473        willDeleteOrigin(originId);
474        m_originSet.remove(originId);
475    }
476
477    m_thread->dispatch(bind(&StorageTracker::syncDeleteOrigin, this, originId.isolatedCopy()));
478}
479
480void StorageTracker::syncDeleteOrigin(const String& originIdentifier)
481{
482    ASSERT(!isMainThread());
483
484    SQLiteTransactionInProgressAutoCounter transactionCounter;
485
486    MutexLocker locker(m_databaseMutex);
487
488    if (!canDeleteOrigin(originIdentifier)) {
489        LOG_ERROR("Attempted to delete origin '%s' while it was being created\n", originIdentifier.ascii().data());
490        return;
491    }
492
493    openTrackerDatabase(false);
494    if (!m_database.isOpen())
495        return;
496
497    String path = databasePathForOrigin(originIdentifier);
498    if (path.isEmpty()) {
499        // It is possible to get a request from the API to delete the storage for an origin that
500        // has no such storage.
501        return;
502    }
503
504    SQLiteStatement deleteStatement(m_database, "DELETE FROM Origins where origin=?");
505    if (deleteStatement.prepare() != SQLResultOk) {
506        LOG_ERROR("Unable to prepare deletion of origin '%s'", originIdentifier.ascii().data());
507        return;
508    }
509    deleteStatement.bindText(1, originIdentifier);
510    if (!deleteStatement.executeCommand()) {
511        LOG_ERROR("Unable to execute deletion of origin '%s'", originIdentifier.ascii().data());
512        return;
513    }
514
515    SQLiteFileSystem::deleteDatabaseFile(path);
516
517    bool shouldDeleteTrackerFiles = false;
518    {
519        MutexLocker locker(m_originSetMutex);
520        m_originSet.remove(originIdentifier);
521        shouldDeleteTrackerFiles = m_originSet.isEmpty();
522    }
523
524    if (shouldDeleteTrackerFiles) {
525#if PLATFORM(IOS)
526        SQLiteFileSystem::truncateDatabaseFile(m_database.sqlite3Handle());
527#endif
528        m_database.close();
529#if !PLATFORM(IOS)
530        SQLiteFileSystem::deleteDatabaseFile(trackerDatabasePath());
531        SQLiteFileSystem::deleteEmptyDatabaseDirectory(m_storageDirectoryPath);
532#endif
533    }
534
535    {
536        MutexLocker locker(m_clientMutex);
537        if (m_client)
538            m_client->dispatchDidModifyOrigin(originIdentifier);
539    }
540}
541
542void StorageTracker::willDeleteAllOrigins()
543{
544    ASSERT(!m_originSetMutex.tryLock());
545
546    OriginSet::const_iterator end = m_originSet.end();
547    for (OriginSet::const_iterator it = m_originSet.begin(); it != end; ++it)
548        m_originsBeingDeleted.add((*it).isolatedCopy());
549}
550
551void StorageTracker::willDeleteOrigin(const String& originIdentifier)
552{
553    ASSERT(isMainThread());
554    ASSERT(!m_originSetMutex.tryLock());
555
556    m_originsBeingDeleted.add(originIdentifier);
557}
558
559bool StorageTracker::canDeleteOrigin(const String& originIdentifier)
560{
561    ASSERT(!m_databaseMutex.tryLock());
562    MutexLocker locker(m_originSetMutex);
563    return m_originsBeingDeleted.contains(originIdentifier);
564}
565
566void StorageTracker::cancelDeletingOrigin(const String& originIdentifier)
567{
568    if (!m_isActive)
569        return;
570
571    MutexLocker locker(m_databaseMutex);
572    {
573        MutexLocker locker(m_originSetMutex);
574        if (!m_originsBeingDeleted.isEmpty())
575            m_originsBeingDeleted.remove(originIdentifier);
576    }
577}
578
579bool StorageTracker::isActive()
580{
581    return m_isActive;
582}
583
584void StorageTracker::setIsActive(bool flag)
585{
586    m_isActive = flag;
587}
588
589String StorageTracker::databasePathForOrigin(const String& originIdentifier)
590{
591    ASSERT(!m_databaseMutex.tryLock());
592    ASSERT(m_isActive);
593
594    if (!m_database.isOpen())
595        return String();
596
597    SQLiteTransactionInProgressAutoCounter transactionCounter;
598
599    SQLiteStatement pathStatement(m_database, "SELECT path FROM Origins WHERE origin=?");
600    if (pathStatement.prepare() != SQLResultOk) {
601        LOG_ERROR("Unable to prepare selection of path for origin '%s'", originIdentifier.ascii().data());
602        return String();
603    }
604    pathStatement.bindText(1, originIdentifier);
605    int result = pathStatement.step();
606    if (result != SQLResultRow)
607        return String();
608
609    return pathStatement.getColumnText(0);
610}
611
612long long StorageTracker::diskUsageForOrigin(SecurityOrigin* origin)
613{
614    if (!m_isActive)
615        return 0;
616
617    MutexLocker locker(m_databaseMutex);
618
619    String path = databasePathForOrigin(origin->databaseIdentifier());
620    if (path.isEmpty())
621        return 0;
622
623    return SQLiteFileSystem::getDatabaseFileSize(path);
624}
625
626} // namespace WebCore
627