[WTF] Add makeUnique<T>, which ensures T is fast-allocated, makeUnique / makeUniqueWi...
[WebKit-https.git] / Source / WebKit / NetworkProcess / WebStorage / LocalStorageDatabase.cpp
1 /*
2  * Copyright (C) 2008, 2009, 2010, 2013, 2019 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 "LocalStorageDatabase.h"
28
29 #include "LocalStorageDatabaseTracker.h"
30 #include <WebCore/SQLiteStatement.h>
31 #include <WebCore/SQLiteTransaction.h>
32 #include <WebCore/SecurityOrigin.h>
33 #include <WebCore/StorageMap.h>
34 #include <WebCore/SuddenTermination.h>
35 #include <wtf/FileSystem.h>
36 #include <wtf/RefPtr.h>
37 #include <wtf/WorkQueue.h>
38 #include <wtf/text/StringHash.h>
39 #include <wtf/text/WTFString.h>
40
41 static const auto databaseUpdateInterval = 1_s;
42
43 static const int maximumItemsToUpdate = 100;
44
45 namespace WebKit {
46 using namespace WebCore;
47
48 Ref<LocalStorageDatabase> LocalStorageDatabase::create(Ref<WorkQueue>&& queue, Ref<LocalStorageDatabaseTracker>&& tracker, const SecurityOriginData& securityOrigin)
49 {
50     return adoptRef(*new LocalStorageDatabase(WTFMove(queue), WTFMove(tracker), securityOrigin));
51 }
52
53 LocalStorageDatabase::LocalStorageDatabase(Ref<WorkQueue>&& queue, Ref<LocalStorageDatabaseTracker>&& tracker, const SecurityOriginData& securityOrigin)
54     : m_queue(WTFMove(queue))
55     , m_tracker(WTFMove(tracker))
56     , m_securityOrigin(securityOrigin)
57     , m_databasePath(m_tracker->databasePath(m_securityOrigin))
58 {
59     ASSERT(!RunLoop::isMain());
60 }
61
62 LocalStorageDatabase::~LocalStorageDatabase()
63 {
64     ASSERT(!RunLoop::isMain());
65     ASSERT(m_isClosed);
66 }
67
68 void LocalStorageDatabase::openDatabase(DatabaseOpeningStrategy openingStrategy)
69 {
70     ASSERT(!m_database.isOpen());
71     ASSERT(!m_failedToOpenDatabase);
72
73     if (!tryToOpenDatabase(openingStrategy)) {
74         m_failedToOpenDatabase = true;
75         return;
76     }
77
78     if (m_database.isOpen())
79         m_tracker->didOpenDatabaseWithOrigin(m_securityOrigin);
80 }
81
82 bool LocalStorageDatabase::tryToOpenDatabase(DatabaseOpeningStrategy openingStrategy)
83 {
84     ASSERT(!RunLoop::isMain());
85     if (!FileSystem::fileExists(m_databasePath) && openingStrategy == SkipIfNonExistent)
86         return true;
87
88     if (m_databasePath.isEmpty()) {
89         LOG_ERROR("Filename for local storage database is empty - cannot open for persistent storage");
90         return false;
91     }
92
93     if (!m_database.open(m_databasePath)) {
94         LOG_ERROR("Failed to open database file %s for local storage", m_databasePath.utf8().data());
95         return false;
96     }
97
98     // Since a WorkQueue isn't bound to a specific thread, we have to disable threading checks
99     // even though we never access the database from different threads simultaneously.
100     m_database.disableThreadingChecks();
101
102     if (!migrateItemTableIfNeeded()) {
103         // We failed to migrate the item table. In order to avoid trying to migrate the table over and over,
104         // just delete it and start from scratch.
105         if (!m_database.executeCommand("DROP TABLE ItemTable"))
106             LOG_ERROR("Failed to delete table ItemTable for local storage");
107     }
108
109     if (!m_database.executeCommand("CREATE TABLE IF NOT EXISTS ItemTable (key TEXT UNIQUE ON CONFLICT REPLACE, value BLOB NOT NULL ON CONFLICT FAIL)")) {
110         LOG_ERROR("Failed to create table ItemTable for local storage");
111         return false;
112     }
113
114     return true;
115 }
116
117 bool LocalStorageDatabase::migrateItemTableIfNeeded()
118 {
119     if (!m_database.tableExists("ItemTable"))
120         return true;
121
122     SQLiteStatement query(m_database, "SELECT value FROM ItemTable LIMIT 1");
123
124     // This query isn't ever executed, it's just used to check the column type.
125     if (query.isColumnDeclaredAsBlob(0))
126         return true;
127
128     // Create a new table with the right type, copy all the data over to it and then replace the new table with the old table.
129     static const char* commands[] = {
130         "DROP TABLE IF EXISTS ItemTable2",
131         "CREATE TABLE ItemTable2 (key TEXT UNIQUE ON CONFLICT REPLACE, value BLOB NOT NULL ON CONFLICT FAIL)",
132         "INSERT INTO ItemTable2 SELECT * from ItemTable",
133         "DROP TABLE ItemTable",
134         "ALTER TABLE ItemTable2 RENAME TO ItemTable",
135         0,
136     };
137
138     SQLiteTransaction transaction(m_database, false);
139     transaction.begin();
140
141     for (size_t i = 0; commands[i]; ++i) {
142         if (m_database.executeCommand(commands[i]))
143             continue;
144
145         LOG_ERROR("Failed to migrate table ItemTable for local storage when executing: %s", commands[i]);
146         transaction.rollback();
147
148         return false;
149     }
150
151     transaction.commit();
152     return true;
153 }
154
155 void LocalStorageDatabase::importItems(StorageMap& storageMap)
156 {
157     if (m_didImportItems)
158         return;
159
160     // FIXME: If it can't import, then the default WebKit behavior should be that of private browsing,
161     // not silently ignoring it. https://bugs.webkit.org/show_bug.cgi?id=25894
162
163     // We set this to true even if we don't end up importing any items due to failure because
164     // there's really no good way to recover other than not importing anything.
165     m_didImportItems = true;
166
167     openDatabase(SkipIfNonExistent);
168     if (!m_database.isOpen())
169         return;
170
171     SQLiteStatement query(m_database, "SELECT key, value FROM ItemTable"_str);
172     if (query.prepare() != SQLITE_OK) {
173         LOG_ERROR("Unable to select items from ItemTable for local storage");
174         return;
175     }
176
177     HashMap<String, String> items;
178
179     int result = query.step();
180     while (result == SQLITE_ROW) {
181         String key = query.getColumnText(0);
182         String value = query.getColumnBlobAsString(1);
183         if (!key.isNull() && !value.isNull())
184             items.add(WTFMove(key), WTFMove(value));
185         result = query.step();
186     }
187
188     if (result != SQLITE_DONE) {
189         LOG_ERROR("Error reading items from ItemTable for local storage");
190         return;
191     }
192
193     storageMap.importItems(WTFMove(items));
194 }
195
196 void LocalStorageDatabase::setItem(const String& key, const String& value)
197 {
198     itemDidChange(key, value);
199 }
200
201 void LocalStorageDatabase::removeItem(const String& key)
202 {
203     itemDidChange(key, String());
204 }
205
206 void LocalStorageDatabase::clear()
207 {
208     m_changedItems.clear();
209     m_shouldClearItems = true;
210
211     scheduleDatabaseUpdate();
212 }
213
214 void LocalStorageDatabase::close()
215 {
216     ASSERT(!m_isClosed);
217     m_isClosed = true;
218
219     if (m_didScheduleDatabaseUpdate) {
220         updateDatabaseWithChangedItems(m_changedItems);
221         m_changedItems.clear();
222     }
223
224     bool isEmpty = databaseIsEmpty();
225
226     if (m_database.isOpen())
227         m_database.close();
228
229     if (isEmpty)
230         m_tracker->deleteDatabaseWithOrigin(m_securityOrigin);
231 }
232
233 void LocalStorageDatabase::itemDidChange(const String& key, const String& value)
234 {
235     m_changedItems.set(key, value);
236     scheduleDatabaseUpdate();
237 }
238
239 void LocalStorageDatabase::scheduleDatabaseUpdate()
240 {
241     if (m_didScheduleDatabaseUpdate)
242         return;
243
244     if (!m_disableSuddenTerminationWhileWritingToLocalStorage)
245         m_disableSuddenTerminationWhileWritingToLocalStorage = makeUnique<SuddenTerminationDisabler>();
246
247     m_didScheduleDatabaseUpdate = true;
248
249     m_queue->dispatch([protectedThis = makeRef(*this)] {
250         protectedThis->updateDatabase();
251     });
252 }
253
254 void LocalStorageDatabase::updateDatabase()
255 {
256     if (m_isClosed)
257         return;
258
259     m_didScheduleDatabaseUpdate = false;
260
261     HashMap<String, String> changedItems;
262     if (m_changedItems.size() <= maximumItemsToUpdate) {
263         // There are few enough changed items that we can just always write all of them.
264         m_changedItems.swap(changedItems);
265         updateDatabaseWithChangedItems(changedItems);
266         m_disableSuddenTerminationWhileWritingToLocalStorage = nullptr;
267     } else {
268         for (int i = 0; i < maximumItemsToUpdate; ++i) {
269             auto it = m_changedItems.begin();
270             changedItems.add(it->key, it->value);
271
272             m_changedItems.remove(it);
273         }
274
275         ASSERT(changedItems.size() <= maximumItemsToUpdate);
276
277         // Reschedule the update for the remaining items.
278         scheduleDatabaseUpdate();
279         updateDatabaseWithChangedItems(changedItems);
280     }
281 }
282
283 void LocalStorageDatabase::updateDatabaseWithChangedItems(const HashMap<String, String>& changedItems)
284 {
285     if (!m_database.isOpen())
286         openDatabase(CreateIfNonExistent);
287     if (!m_database.isOpen())
288         return;
289
290     if (m_shouldClearItems) {
291         m_shouldClearItems = false;
292
293         SQLiteStatement clearStatement(m_database, "DELETE FROM ItemTable");
294         if (clearStatement.prepare() != SQLITE_OK) {
295             LOG_ERROR("Failed to prepare clear statement - cannot write to local storage database");
296             return;
297         }
298
299         int result = clearStatement.step();
300         if (result != SQLITE_DONE) {
301             LOG_ERROR("Failed to clear all items in the local storage database - %i", result);
302             return;
303         }
304     }
305
306     SQLiteStatement insertStatement(m_database, "INSERT INTO ItemTable VALUES (?, ?)");
307     if (insertStatement.prepare() != SQLITE_OK) {
308         LOG_ERROR("Failed to prepare insert statement - cannot write to local storage database");
309         return;
310     }
311
312     SQLiteStatement deleteStatement(m_database, "DELETE FROM ItemTable WHERE key=?");
313     if (deleteStatement.prepare() != SQLITE_OK) {
314         LOG_ERROR("Failed to prepare delete statement - cannot write to local storage database");
315         return;
316     }
317
318     SQLiteTransaction transaction(m_database);
319     transaction.begin();
320
321     for (auto it = changedItems.begin(), end = changedItems.end(); it != end; ++it) {
322         // A null value means that the key/value pair should be deleted.
323         SQLiteStatement& statement = it->value.isNull() ? deleteStatement : insertStatement;
324
325         statement.bindText(1, it->key);
326
327         // If we're inserting a key/value pair, bind the value as well.
328         if (!it->value.isNull())
329             statement.bindBlob(2, it->value);
330
331         int result = statement.step();
332         if (result != SQLITE_DONE) {
333             LOG_ERROR("Failed to update item in the local storage database - %i", result);
334             break;
335         }
336
337         statement.reset();
338     }
339
340     transaction.commit();
341 }
342
343 bool LocalStorageDatabase::databaseIsEmpty()
344 {
345     if (!m_database.isOpen())
346         return false;
347
348     SQLiteStatement query(m_database, "SELECT COUNT(*) FROM ItemTable");
349     if (query.prepare() != SQLITE_OK) {
350         LOG_ERROR("Unable to count number of rows in ItemTable for local storage");
351         return false;
352     }
353
354     int result = query.step();
355     if (result != SQLITE_ROW) {
356         LOG_ERROR("No results when counting number of rows in ItemTable for local storage");
357         return false;
358     }
359
360     return !query.getColumnInt(0);
361 }
362
363 } // namespace WebKit