Remove std::chrono completely
[WebKit.git] / Source / WebKit / UIProcess / WebStorage / LocalStorageDatabaseTracker.cpp
1 /*
2  * Copyright (C) 2011, 2013 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 "LocalStorageDatabaseTracker.h"
28
29 #include <WebCore/FileSystem.h>
30 #include <WebCore/SQLiteFileSystem.h>
31 #include <WebCore/SQLiteStatement.h>
32 #include <WebCore/SecurityOrigin.h>
33 #include <WebCore/SecurityOriginData.h>
34 #include <WebCore/TextEncoding.h>
35 #include <wtf/MainThread.h>
36 #include <wtf/RunLoop.h>
37 #include <wtf/WorkQueue.h>
38 #include <wtf/text/CString.h>
39
40 using namespace WebCore;
41
42 namespace WebKit {
43
44 Ref<LocalStorageDatabaseTracker> LocalStorageDatabaseTracker::create(Ref<WorkQueue>&& queue, const String& localStorageDirectory)
45 {
46     return adoptRef(*new LocalStorageDatabaseTracker(WTFMove(queue), localStorageDirectory));
47 }
48
49 LocalStorageDatabaseTracker::LocalStorageDatabaseTracker(Ref<WorkQueue>&& queue, const String& localStorageDirectory)
50     : m_queue(WTFMove(queue))
51     , m_localStorageDirectory(localStorageDirectory.isolatedCopy())
52 {
53     ASSERT(!m_localStorageDirectory.isEmpty());
54
55     // Make sure the encoding is initialized before we start dispatching things to the queue.
56     UTF8Encoding();
57
58     m_queue->dispatch([protectedThis = makeRef(*this)]() mutable {
59         protectedThis->importOriginIdentifiers();
60     });
61 }
62
63 LocalStorageDatabaseTracker::~LocalStorageDatabaseTracker()
64 {
65 }
66
67 String LocalStorageDatabaseTracker::databasePath(const SecurityOriginData& securityOrigin) const
68 {
69     return databasePath(securityOrigin.databaseIdentifier() + ".localstorage");
70 }
71
72 void LocalStorageDatabaseTracker::didOpenDatabaseWithOrigin(const SecurityOriginData& securityOrigin)
73 {
74     addDatabaseWithOriginIdentifier(securityOrigin.databaseIdentifier(), databasePath(securityOrigin));
75 }
76
77 void LocalStorageDatabaseTracker::deleteDatabaseWithOrigin(const SecurityOriginData& securityOrigin)
78 {
79     removeDatabaseWithOriginIdentifier(securityOrigin.databaseIdentifier());
80 }
81
82 void LocalStorageDatabaseTracker::deleteAllDatabases()
83 {
84     m_origins.clear();
85
86     openTrackerDatabase(SkipIfNonExistent);
87     if (!m_database.isOpen())
88         return;
89
90     SQLiteStatement statement(m_database, "SELECT origin, path FROM Origins");
91     if (statement.prepare() != SQLITE_OK) {
92         LOG_ERROR("Failed to prepare statement.");
93         return;
94     }
95
96     int result;
97     while ((result = statement.step()) == SQLITE_ROW) {
98         FileSystem::deleteFile(statement.getColumnText(1));
99
100         // FIXME: Call out to the client.
101     }
102
103     if (result != SQLITE_DONE)
104         LOG_ERROR("Failed to read in all origins from the database.");
105
106     if (m_database.isOpen())
107         m_database.close();
108
109     if (!FileSystem::deleteFile(trackerDatabasePath())) {
110         // In the case where it is not possible to delete the database file (e.g some other program
111         // like a virus scanner is accessing it), make sure to remove all entries.
112         openTrackerDatabase(SkipIfNonExistent);
113         if (!m_database.isOpen())
114             return;
115
116         SQLiteStatement deleteStatement(m_database, "DELETE FROM Origins");
117         if (deleteStatement.prepare() != SQLITE_OK) {
118             LOG_ERROR("Unable to prepare deletion of all origins");
119             return;
120         }
121         if (!deleteStatement.executeCommand()) {
122             LOG_ERROR("Unable to execute deletion of all origins");
123             return;
124         }
125     }
126
127     FileSystem::deleteEmptyDirectory(m_localStorageDirectory);
128 }
129
130 static std::optional<time_t> fileCreationTime(const String& filePath)
131 {
132     time_t time;
133     return FileSystem::getFileCreationTime(filePath, time) ? time : std::optional<time_t>(std::nullopt);
134 }
135
136 static std::optional<time_t> fileModificationTime(const String& filePath)
137 {
138     time_t time;
139     if (!FileSystem::getFileModificationTime(filePath, time))
140         return std::nullopt;
141
142     return time;
143 }
144
145 Vector<SecurityOriginData> LocalStorageDatabaseTracker::deleteDatabasesModifiedSince(WallTime time)
146 {
147     ASSERT(!RunLoop::isMain());
148     importOriginIdentifiers();
149     Vector<String> originIdentifiersToDelete;
150
151     for (const String& origin : m_origins) {
152         String filePath = pathForDatabaseWithOriginIdentifier(origin);
153
154         auto modificationTime = FileSystem::getFileModificationTime(filePath);
155         if (!modificationTime)
156             continue;
157
158         if (modificationTime.value() >= time)
159             originIdentifiersToDelete.append(origin);
160     }
161
162     Vector<SecurityOriginData> deletedDatabaseOrigins;
163     deletedDatabaseOrigins.reserveInitialCapacity(originIdentifiersToDelete.size());
164
165     for (const auto& originIdentifier : originIdentifiersToDelete) {
166         removeDatabaseWithOriginIdentifier(originIdentifier);
167
168         if (auto origin = SecurityOriginData::fromDatabaseIdentifier(originIdentifier))
169             deletedDatabaseOrigins.uncheckedAppend(*origin);
170         else
171             ASSERT_NOT_REACHED();
172     }
173
174     return deletedDatabaseOrigins;
175 }
176
177 Vector<SecurityOriginData> LocalStorageDatabaseTracker::origins() const
178 {
179     Vector<SecurityOriginData> origins;
180     origins.reserveInitialCapacity(m_origins.size());
181
182     for (const String& originIdentifier : m_origins) {
183         if (auto origin = SecurityOriginData::fromDatabaseIdentifier(originIdentifier))
184             origins.uncheckedAppend(*origin);
185         else
186             ASSERT_NOT_REACHED();
187     }
188
189     return origins;
190 }
191
192 Vector<LocalStorageDatabaseTracker::OriginDetails> LocalStorageDatabaseTracker::originDetails()
193 {
194     Vector<OriginDetails> result;
195     result.reserveInitialCapacity(m_origins.size());
196
197     for (const String& origin : m_origins) {
198         String filePath = pathForDatabaseWithOriginIdentifier(origin);
199
200         OriginDetails details;
201         details.originIdentifier = origin.isolatedCopy();
202         details.creationTime = fileCreationTime(filePath);
203         details.modificationTime = fileModificationTime(filePath);
204         result.uncheckedAppend(details);
205     }
206
207     return result;
208 }
209
210 String LocalStorageDatabaseTracker::databasePath(const String& filename) const
211 {
212     if (!FileSystem::makeAllDirectories(m_localStorageDirectory)) {
213         LOG_ERROR("Unable to create LocalStorage database path %s", m_localStorageDirectory.utf8().data());
214         return String();
215     }
216
217 #if PLATFORM(IOS)
218     platformMaybeExcludeFromBackup();
219 #endif
220
221     return FileSystem::pathByAppendingComponent(m_localStorageDirectory, filename);
222 }
223
224 String LocalStorageDatabaseTracker::trackerDatabasePath() const
225 {
226     return databasePath("StorageTracker.db");
227 }
228
229 void LocalStorageDatabaseTracker::openTrackerDatabase(DatabaseOpeningStrategy openingStrategy)
230 {
231     if (m_database.isOpen())
232         return;
233
234     String databasePath = trackerDatabasePath();
235
236     if (!FileSystem::fileExists(databasePath) && openingStrategy == SkipIfNonExistent)
237         return;
238
239     if (!m_database.open(databasePath)) {
240         LOG_ERROR("Failed to open databasePath %s.", databasePath.ascii().data());
241         return;
242     }
243
244     // Since a WorkQueue isn't bound to a specific thread, we have to disable threading checks
245     // even though we never access the database from different threads simultaneously.
246     m_database.disableThreadingChecks();
247
248     if (m_database.tableExists("Origins"))
249         return;
250
251     if (!m_database.executeCommand("CREATE TABLE Origins (origin TEXT UNIQUE ON CONFLICT REPLACE, path TEXT);"))
252         LOG_ERROR("Failed to create Origins table.");
253 }
254
255 void LocalStorageDatabaseTracker::importOriginIdentifiers()
256 {
257     openTrackerDatabase(SkipIfNonExistent);
258
259     if (m_database.isOpen()) {
260         SQLiteStatement statement(m_database, "SELECT origin FROM Origins");
261         if (statement.prepare() != SQLITE_OK) {
262             LOG_ERROR("Failed to prepare statement.");
263             return;
264         }
265
266         int result;
267
268         while ((result = statement.step()) == SQLITE_ROW)
269             m_origins.add(statement.getColumnText(0));
270
271         if (result != SQLITE_DONE) {
272             LOG_ERROR("Failed to read in all origins from the database.");
273             return;
274         }
275     }
276
277     updateTrackerDatabaseFromLocalStorageDatabaseFiles();
278 }
279
280 void LocalStorageDatabaseTracker::updateTrackerDatabaseFromLocalStorageDatabaseFiles()
281 {
282     Vector<String> paths = FileSystem::listDirectory(m_localStorageDirectory, "*.localstorage");
283
284     HashSet<String> origins(m_origins);
285     HashSet<String> originsFromLocalStorageDatabaseFiles;
286
287     for (size_t i = 0; i < paths.size(); ++i) {
288         const String& path = paths[i];
289
290         if (!path.endsWith(".localstorage"))
291             continue;
292
293         String filename = FileSystem::pathGetFileName(path);
294
295         String originIdentifier = filename.substring(0, filename.length() - strlen(".localstorage"));
296
297         if (!m_origins.contains(originIdentifier))
298             addDatabaseWithOriginIdentifier(originIdentifier, path);
299
300         originsFromLocalStorageDatabaseFiles.add(originIdentifier);
301     }
302
303     for (auto it = origins.begin(), end = origins.end(); it != end; ++it) {
304         const String& originIdentifier = *it;
305         if (origins.contains(originIdentifier))
306             continue;
307
308         removeDatabaseWithOriginIdentifier(originIdentifier);
309     }
310 }
311
312 void LocalStorageDatabaseTracker::addDatabaseWithOriginIdentifier(const String& originIdentifier, const String& databasePath)
313 {
314     openTrackerDatabase(CreateIfNonExistent);
315     if (!m_database.isOpen())
316         return;
317
318     SQLiteStatement statement(m_database, "INSERT INTO Origins VALUES (?, ?)");
319     if (statement.prepare() != SQLITE_OK) {
320         LOG_ERROR("Unable to establish origin '%s' in the tracker", originIdentifier.utf8().data());
321         return;
322     }
323
324     statement.bindText(1, originIdentifier);
325     statement.bindText(2, databasePath);
326
327     if (statement.step() != SQLITE_DONE)
328         LOG_ERROR("Unable to establish origin '%s' in the tracker", originIdentifier.utf8().data());
329
330     m_origins.add(originIdentifier);
331
332     // FIXME: Tell clients that the origin was added.
333 }
334
335 void LocalStorageDatabaseTracker::removeDatabaseWithOriginIdentifier(const String& originIdentifier)
336 {
337     openTrackerDatabase(SkipIfNonExistent);
338     if (!m_database.isOpen())
339         return;
340
341     String path = pathForDatabaseWithOriginIdentifier(originIdentifier);
342     if (path.isEmpty())
343         return;
344
345     SQLiteStatement deleteStatement(m_database, "DELETE FROM Origins where origin=?");
346     if (deleteStatement.prepare() != SQLITE_OK) {
347         LOG_ERROR("Unable to prepare deletion of origin '%s'", originIdentifier.ascii().data());
348         return;
349     }
350     deleteStatement.bindText(1, originIdentifier);
351     if (!deleteStatement.executeCommand()) {
352         LOG_ERROR("Unable to execute deletion of origin '%s'", originIdentifier.ascii().data());
353         return;
354     }
355
356     SQLiteFileSystem::deleteDatabaseFile(path);
357
358     m_origins.remove(originIdentifier);
359     if (m_origins.isEmpty()) {
360         // There are no origins left; delete the tracker database.
361         m_database.close();
362         SQLiteFileSystem::deleteDatabaseFile(trackerDatabasePath());
363         FileSystem::deleteEmptyDirectory(m_localStorageDirectory);
364     }
365
366     // FIXME: Tell clients that the origin was removed.
367 }
368
369 String LocalStorageDatabaseTracker::pathForDatabaseWithOriginIdentifier(const String& originIdentifier)
370 {
371     if (!m_database.isOpen())
372         return String();
373
374     SQLiteStatement pathStatement(m_database, "SELECT path FROM Origins WHERE origin=?");
375     if (pathStatement.prepare() != SQLITE_OK) {
376         LOG_ERROR("Unable to prepare selection of path for origin '%s'", originIdentifier.utf8().data());
377         return String();
378     }
379
380     pathStatement.bindText(1, originIdentifier);
381
382     int result = pathStatement.step();
383     if (result != SQLITE_ROW)
384         return String();
385
386     return pathStatement.getColumnText(0);
387 }
388
389 } // namespace WebKit