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