Close service worker database on network process suspension
[WebKit-https.git] / Source / WebCore / workers / service / server / RegistrationDatabase.cpp
1 /*
2  * Copyright (C) 2017 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 "RegistrationDatabase.h"
28
29 #if ENABLE(SERVICE_WORKER)
30
31 #include "Logging.h"
32 #include "RegistrationStore.h"
33 #include "SQLiteDatabase.h"
34 #include "SQLiteFileSystem.h"
35 #include "SQLiteStatement.h"
36 #include "SQLiteTransaction.h"
37 #include "SWServer.h"
38 #include "SecurityOrigin.h"
39 #include <wtf/CompletionHandler.h>
40 #include <wtf/CrossThreadCopier.h>
41 #include <wtf/FileSystem.h>
42 #include <wtf/MainThread.h>
43 #include <wtf/NeverDestroyed.h>
44 #include <wtf/Scope.h>
45 #include <wtf/persistence/PersistentCoders.h>
46 #include <wtf/persistence/PersistentDecoder.h>
47 #include <wtf/persistence/PersistentEncoder.h>
48 #include <wtf/text/StringConcatenateNumbers.h>
49
50 namespace WebCore {
51
52 static const uint64_t schemaVersion = 4;
53
54 static const String recordsTableSchema(const String& tableName)
55 {
56     return makeString("CREATE TABLE ", tableName, " (key TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE, origin TEXT NOT NULL ON CONFLICT FAIL, scopeURL TEXT NOT NULL ON CONFLICT FAIL, topOrigin TEXT NOT NULL ON CONFLICT FAIL, lastUpdateCheckTime DOUBLE NOT NULL ON CONFLICT FAIL, updateViaCache TEXT NOT NULL ON CONFLICT FAIL, scriptURL TEXT NOT NULL ON CONFLICT FAIL, script TEXT NOT NULL ON CONFLICT FAIL, workerType TEXT NOT NULL ON CONFLICT FAIL, contentSecurityPolicy BLOB NOT NULL ON CONFLICT FAIL, referrerPolicy TEXT NOT NULL ON CONFLICT FAIL, scriptResourceMap BLOB NOT NULL ON CONFLICT FAIL)");
57 }
58
59 static const String recordsTableSchema()
60 {
61     ASSERT(!isMainThread());
62     static NeverDestroyed<String> schema(recordsTableSchema("Records"));
63     return schema;
64 }
65
66 static const String recordsTableSchemaAlternate()
67 {
68     ASSERT(!isMainThread());
69     static NeverDestroyed<String> schema(recordsTableSchema("\"Records\""));
70     return schema;
71 }
72
73 static inline String databaseFilenameFromVersion(uint64_t version)
74 {
75     return makeString("ServiceWorkerRegistrations-", version, ".sqlite3");
76 }
77
78 static const String& databaseFilename()
79 {
80     ASSERT(isMainThread());
81     static NeverDestroyed<String> filename = databaseFilenameFromVersion(schemaVersion);
82     return filename;
83 }
84
85 String serviceWorkerRegistrationDatabaseFilename(const String& databaseDirectory)
86 {
87     return FileSystem::pathByAppendingComponent(databaseDirectory, databaseFilename());
88 }
89
90 static inline void cleanOldDatabases(const String& databaseDirectory)
91 {
92     for (uint64_t version = 1; version < schemaVersion; ++version)
93         SQLiteFileSystem::deleteDatabaseFile(FileSystem::pathByAppendingComponent(databaseDirectory, databaseFilenameFromVersion(version)));
94 }
95
96 RegistrationDatabase::RegistrationDatabase(RegistrationStore& store, String&& databaseDirectory)
97     : m_workQueue(WorkQueue::create("ServiceWorker I/O Thread", WorkQueue::Type::Serial))
98     , m_store(makeWeakPtr(store))
99     , m_sessionID(m_store->server().sessionID())
100     , m_databaseDirectory(WTFMove(databaseDirectory))
101     , m_databaseFilePath(FileSystem::pathByAppendingComponent(m_databaseDirectory, databaseFilename()))
102 {
103     ASSERT(isMainThread());
104
105     postTaskToWorkQueue([this] {
106         importRecordsIfNecessary();
107     });
108 }
109
110 RegistrationDatabase::~RegistrationDatabase()
111 {
112     ASSERT(isMainThread());
113
114     // The database needs to be destroyed on the background thread.
115     if (m_database)
116         m_workQueue->dispatch([database = WTFMove(m_database)] { });
117 }
118
119 void RegistrationDatabase::postTaskToWorkQueue(Function<void()>&& task)
120 {
121     ASSERT(isMainThread());
122
123     m_workQueue->dispatch([protectedThis = makeRef(*this), task = WTFMove(task)]() mutable {
124         task();
125     });
126 }
127
128 void RegistrationDatabase::openSQLiteDatabase(const String& fullFilename)
129 {
130     ASSERT(!isMainThread());
131     ASSERT(!m_database);
132
133     cleanOldDatabases(m_databaseDirectory);
134
135     LOG(ServiceWorker, "ServiceWorker RegistrationDatabase opening file %s", fullFilename.utf8().data());
136
137     String errorMessage;
138     auto scopeExit = makeScopeExit([this, protectedThis = makeRef(*this), errorMessage = &errorMessage] {
139         ASSERT_UNUSED(errorMessage, !errorMessage->isNull());
140
141 #if RELEASE_LOG_DISABLED
142         LOG_ERROR("Failed to open Service Worker registration database: %s", errorMessage->utf8().data());
143 #else
144         RELEASE_LOG_ERROR(ServiceWorker, "Failed to open Service Worker registration database: %{public}s", errorMessage->utf8().data());
145 #endif
146
147         m_database = nullptr;
148         callOnMainThread([protectedThis = protectedThis.copyRef()] {
149             protectedThis->databaseFailedToOpen();
150         });
151     });
152
153     SQLiteFileSystem::ensureDatabaseDirectoryExists(m_databaseDirectory);
154
155     m_database = std::make_unique<SQLiteDatabase>();
156     if (!m_database->open(fullFilename)) {
157         errorMessage = "Failed to open registration database";
158         return;
159     }
160
161     // Disable threading checks. We always access the database from our serial WorkQueue. Such accesses
162     // are safe since work queue tasks are guaranteed to run one after another. However, tasks will not
163     // necessary run on the same thread every time (as per GCD documentation).
164     m_database->disableThreadingChecks();
165     
166     errorMessage = ensureValidRecordsTable();
167     if (!errorMessage.isNull())
168         return;
169     
170     errorMessage = importRecords();
171     if (!errorMessage.isNull())
172         return;
173
174     scopeExit.release();
175 }
176
177 void RegistrationDatabase::importRecordsIfNecessary()
178 {
179     ASSERT(!isMainThread());
180
181     if (FileSystem::fileExists(m_databaseFilePath))
182         openSQLiteDatabase(m_databaseFilePath);
183
184     callOnMainThread([protectedThis = makeRef(*this)] {
185         protectedThis->databaseOpenedAndRecordsImported();
186     });
187 }
188
189 String RegistrationDatabase::ensureValidRecordsTable()
190 {
191     ASSERT(!isMainThread());
192     ASSERT(m_database);
193     ASSERT(m_database->isOpen());
194
195     String currentSchema;
196     {
197         // Fetch the schema for an existing records table.
198         SQLiteStatement statement(*m_database, "SELECT type, sql FROM sqlite_master WHERE tbl_name='Records'");
199         if (statement.prepare() != SQLITE_OK)
200             return "Unable to prepare statement to fetch schema for the Records table.";
201
202         int sqliteResult = statement.step();
203
204         // If there is no Records table at all, create it and then bail.
205         if (sqliteResult == SQLITE_DONE) {
206             if (!m_database->executeCommand(recordsTableSchema()))
207                 return makeString("Could not create Records table in database (", m_database->lastError(), ") - ", m_database->lastErrorMsg());
208             return { };
209         }
210
211         if (sqliteResult != SQLITE_ROW)
212             return "Error executing statement to fetch schema for the Records table.";
213
214         currentSchema = statement.getColumnText(1);
215     }
216
217     ASSERT(!currentSchema.isEmpty());
218     
219     if (currentSchema == recordsTableSchema() || currentSchema == recordsTableSchemaAlternate())
220         return { };
221
222     // This database has a Records table but it is not a schema we expect.
223     // Trying to recover by deleting the data contained within is dangerous so
224     // we should consider this an unrecoverable error.
225     RELEASE_ASSERT_NOT_REACHED();
226 }
227
228 static String updateViaCacheToString(ServiceWorkerUpdateViaCache update)
229 {
230     switch (update) {
231     case ServiceWorkerUpdateViaCache::Imports:
232         return "Imports";
233     case ServiceWorkerUpdateViaCache::All:
234         return "All";
235     case ServiceWorkerUpdateViaCache::None:
236         return "None";
237     }
238
239     RELEASE_ASSERT_NOT_REACHED();
240 }
241
242 static Optional<ServiceWorkerUpdateViaCache> stringToUpdateViaCache(const String& update)
243 {
244     if (update == "Imports")
245         return ServiceWorkerUpdateViaCache::Imports;
246     if (update == "All")
247         return ServiceWorkerUpdateViaCache::All;
248     if (update == "None")
249         return ServiceWorkerUpdateViaCache::None;
250
251     return WTF::nullopt;
252 }
253
254 static String workerTypeToString(WorkerType workerType)
255 {
256     switch (workerType) {
257     case WorkerType::Classic:
258         return "Classic";
259     case WorkerType::Module:
260         return "Module";
261     }
262
263     RELEASE_ASSERT_NOT_REACHED();
264 }
265
266 static Optional<WorkerType> stringToWorkerType(const String& type)
267 {
268     if (type == "Classic")
269         return WorkerType::Classic;
270     if (type == "Module")
271         return WorkerType::Module;
272
273     return WTF::nullopt;
274 }
275
276 void RegistrationDatabase::pushChanges(Vector<ServiceWorkerContextData>&& datas, CompletionHandler<void()>&& completionHandler)
277 {
278     postTaskToWorkQueue([this, datas = crossThreadCopy(datas), completionHandler = WTFMove(completionHandler)]() mutable {
279         doPushChanges(WTFMove(datas));
280
281         if (!completionHandler)
282             return;
283
284         callOnMainThread(WTFMove(completionHandler));
285     });
286 }
287
288 void RegistrationDatabase::close(CompletionHandler<void()>&& completionHandler)
289 {
290     postTaskToWorkQueue([this, completionHandler = WTFMove(completionHandler)]() mutable {
291         m_database = nullptr;
292         callOnMainThread(WTFMove(completionHandler));
293     });
294 }
295
296 void RegistrationDatabase::clearAll(CompletionHandler<void()>&& completionHandler)
297 {
298     postTaskToWorkQueue([this, completionHandler = WTFMove(completionHandler)]() mutable {
299         m_database = nullptr;
300
301         SQLiteFileSystem::deleteDatabaseFile(m_databaseFilePath);
302         SQLiteFileSystem::deleteEmptyDatabaseDirectory(m_databaseDirectory);
303
304         callOnMainThread(WTFMove(completionHandler));
305     });
306 }
307
308 void RegistrationDatabase::doPushChanges(Vector<ServiceWorkerContextData>&& datas)
309 {
310     if (!m_database) {
311         openSQLiteDatabase(m_databaseFilePath);
312         if (!m_database)
313             return;
314     }
315
316     SQLiteTransaction transaction(*m_database);
317     transaction.begin();
318
319     SQLiteStatement sql(*m_database, "INSERT INTO Records VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"_s);
320     if (sql.prepare() != SQLITE_OK) {
321         RELEASE_LOG_ERROR(ServiceWorker, "Failed to prepare statement to store registration data into records table (%i) - %s", m_database->lastError(), m_database->lastErrorMsg());
322         return;
323     }
324
325     for (auto& data : datas) {
326         if (data.registration.identifier == ServiceWorkerRegistrationIdentifier()) {
327             SQLiteStatement sql(*m_database, "DELETE FROM Records WHERE key = ?");
328             if (sql.prepare() != SQLITE_OK
329                 || sql.bindText(1, data.registration.key.toDatabaseKey()) != SQLITE_OK
330                 || sql.step() != SQLITE_DONE) {
331                 RELEASE_LOG_ERROR(ServiceWorker, "Failed to remove registration data from records table (%i) - %s", m_database->lastError(), m_database->lastErrorMsg());
332                 return;
333             }
334
335             continue;
336         }
337
338         WTF::Persistence::Encoder cspEncoder;
339         data.contentSecurityPolicy.encode(cspEncoder);
340
341         WTF::Persistence::Encoder scriptResourceMapEncoder;
342         scriptResourceMapEncoder.encode(data.scriptResourceMap);
343
344         if (sql.bindText(1, data.registration.key.toDatabaseKey()) != SQLITE_OK
345             || sql.bindText(2, data.registration.scopeURL.protocolHostAndPort()) != SQLITE_OK
346             || sql.bindText(3, data.registration.scopeURL.path()) != SQLITE_OK
347             || sql.bindText(4, data.registration.key.topOrigin().databaseIdentifier()) != SQLITE_OK
348             || sql.bindDouble(5, data.registration.lastUpdateTime.secondsSinceEpoch().value()) != SQLITE_OK
349             || sql.bindText(6, updateViaCacheToString(data.registration.updateViaCache)) != SQLITE_OK
350             || sql.bindText(7, data.scriptURL.string()) != SQLITE_OK
351             || sql.bindText(8, data.script) != SQLITE_OK
352             || sql.bindText(9, workerTypeToString(data.workerType)) != SQLITE_OK
353             || sql.bindBlob(10, cspEncoder.buffer(), cspEncoder.bufferSize()) != SQLITE_OK
354             || sql.bindText(11, data.referrerPolicy) != SQLITE_OK
355             || sql.bindBlob(12, scriptResourceMapEncoder.buffer(), scriptResourceMapEncoder.bufferSize()) != SQLITE_OK
356             || sql.step() != SQLITE_DONE) {
357             RELEASE_LOG_ERROR(ServiceWorker, "Failed to store registration data into records table (%i) - %s", m_database->lastError(), m_database->lastErrorMsg());
358             return;
359         }
360     }
361
362     transaction.commit();
363
364     LOG(ServiceWorker, "Pushed %zu changes to ServiceWorker registration database", datas.size());
365 }
366
367 String RegistrationDatabase::importRecords()
368 {
369     ASSERT(!isMainThread());
370
371     SQLiteStatement sql(*m_database, "SELECT * FROM Records;"_s);
372     if (sql.prepare() != SQLITE_OK)
373         return makeString("Failed to prepare statement to retrieve registrations from records table (", m_database->lastError(), ") - ", m_database->lastErrorMsg());
374
375     int result = sql.step();
376
377     for (; result == SQLITE_ROW; result = sql.step()) {
378         auto key = ServiceWorkerRegistrationKey::fromDatabaseKey(sql.getColumnText(0));
379         auto originURL = URL { URL(), sql.getColumnText(1) };
380         auto scopePath = sql.getColumnText(2);
381         auto topOrigin = SecurityOriginData::fromDatabaseIdentifier(sql.getColumnText(3));
382         auto lastUpdateCheckTime = WallTime::fromRawSeconds(sql.getColumnDouble(4));
383         auto updateViaCache = stringToUpdateViaCache(sql.getColumnText(5));
384         auto scriptURL = URL { URL(), sql.getColumnText(6) };
385         auto script = sql.getColumnText(7);
386         auto workerType = stringToWorkerType(sql.getColumnText(8));
387
388         Vector<uint8_t> contentSecurityPolicyData;
389         sql.getColumnBlobAsVector(9, contentSecurityPolicyData);
390         WTF::Persistence::Decoder cspDecoder(contentSecurityPolicyData.data(), contentSecurityPolicyData.size());
391         ContentSecurityPolicyResponseHeaders contentSecurityPolicy;
392         if (contentSecurityPolicyData.size() && !ContentSecurityPolicyResponseHeaders::decode(cspDecoder, contentSecurityPolicy))
393             continue;
394
395         auto referrerPolicy = sql.getColumnText(10);
396
397         Vector<uint8_t> scriptResourceMapData;
398         sql.getColumnBlobAsVector(11, scriptResourceMapData);
399         HashMap<URL, ServiceWorkerContextData::ImportedScript> scriptResourceMap;
400
401         WTF::Persistence::Decoder scriptResourceMapDecoder(scriptResourceMapData.data(), scriptResourceMapData.size());
402         if (scriptResourceMapData.size()) {
403             if (!scriptResourceMapDecoder.decode(scriptResourceMap))
404                 continue;
405         }
406
407         // Validate the input for this registration.
408         // If any part of this input is invalid, let's skip this registration.
409         // FIXME: Should we return an error skipping *all* registrations?
410         if (!key || !originURL.isValid() || !topOrigin || !updateViaCache || !scriptURL.isValid() || !workerType)
411             continue;
412
413         auto workerIdentifier = ServiceWorkerIdentifier::generate();
414         auto registrationIdentifier = ServiceWorkerRegistrationIdentifier::generate();
415         auto serviceWorkerData = ServiceWorkerData { workerIdentifier, scriptURL, ServiceWorkerState::Activated, *workerType, registrationIdentifier };
416         auto registration = ServiceWorkerRegistrationData { WTFMove(*key), registrationIdentifier, URL(originURL, scopePath), *updateViaCache, lastUpdateCheckTime, WTF::nullopt, WTF::nullopt, WTFMove(serviceWorkerData) };
417         auto contextData = ServiceWorkerContextData { WTF::nullopt, WTFMove(registration), workerIdentifier, WTFMove(script), WTFMove(contentSecurityPolicy), WTFMove(referrerPolicy), WTFMove(scriptURL), *workerType, m_sessionID, true, WTFMove(scriptResourceMap) };
418
419         callOnMainThread([protectedThis = makeRef(*this), contextData = contextData.isolatedCopy()]() mutable {
420             protectedThis->addRegistrationToStore(WTFMove(contextData));
421         });
422     }
423
424     if (result != SQLITE_DONE)
425         return makeString("Failed to import at least one registration from records table (", m_database->lastError(), ") - ", m_database->lastErrorMsg());
426
427     return { };
428 }
429
430 void RegistrationDatabase::addRegistrationToStore(ServiceWorkerContextData&& context)
431 {
432     if (m_store)
433         m_store->addRegistrationFromDatabase(WTFMove(context));
434 }
435
436 void RegistrationDatabase::databaseFailedToOpen()
437 {
438     if (m_store)
439         m_store->databaseFailedToOpen();
440 }
441
442 void RegistrationDatabase::databaseOpenedAndRecordsImported()
443 {
444     if (m_store)
445         m_store->databaseOpenedAndRecordsImported();
446 }
447
448 } // namespace WebCore
449
450 #endif // ENABLE(SERVICE_WORKER)