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