41e80554339575ec3150296df69271be151f9cfa
[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     m_workQueue->dispatch([protectedThis = makeRef(*this), task = WTFMove(task)]() mutable {
122         task();
123     });
124 }
125
126 void RegistrationDatabase::openSQLiteDatabase(const String& fullFilename)
127 {
128     ASSERT(!isMainThread());
129     ASSERT(!m_database);
130
131     cleanOldDatabases(m_databaseDirectory);
132
133     LOG(ServiceWorker, "ServiceWorker RegistrationDatabase opening file %s", fullFilename.utf8().data());
134
135     String errorMessage;
136     auto scopeExit = makeScopeExit([this, protectedThis = makeRef(*this), errorMessage = &errorMessage] {
137         ASSERT_UNUSED(errorMessage, !errorMessage->isNull());
138
139 #if RELEASE_LOG_DISABLED
140         LOG_ERROR("Failed to open Service Worker registration database: %s", errorMessage->utf8().data());
141 #else
142         RELEASE_LOG_ERROR(ServiceWorker, "Failed to open Service Worker registration database: %{public}s", errorMessage->utf8().data());
143 #endif
144
145         m_database = nullptr;
146         callOnMainThread([protectedThis = protectedThis.copyRef()] {
147             protectedThis->databaseFailedToOpen();
148         });
149     });
150
151     SQLiteFileSystem::ensureDatabaseDirectoryExists(m_databaseDirectory);
152
153     m_database = std::make_unique<SQLiteDatabase>();
154     if (!m_database->open(fullFilename)) {
155         errorMessage = "Failed to open registration database";
156         return;
157     }
158
159     // Disable threading checks. We always access the database from our serial WorkQueue. Such accesses
160     // are safe since work queue tasks are guaranteed to run one after another. However, tasks will not
161     // necessary run on the same thread every time (as per GCD documentation).
162     m_database->disableThreadingChecks();
163     
164     errorMessage = ensureValidRecordsTable();
165     if (!errorMessage.isNull())
166         return;
167     
168     errorMessage = importRecords();
169     if (!errorMessage.isNull())
170         return;
171
172     scopeExit.release();
173 }
174
175 void RegistrationDatabase::importRecordsIfNecessary()
176 {
177     ASSERT(!isMainThread());
178
179     if (FileSystem::fileExists(m_databaseFilePath))
180         openSQLiteDatabase(m_databaseFilePath);
181
182     callOnMainThread([protectedThis = makeRef(*this)] {
183         protectedThis->databaseOpenedAndRecordsImported();
184     });
185 }
186
187 String RegistrationDatabase::ensureValidRecordsTable()
188 {
189     ASSERT(!isMainThread());
190     ASSERT(m_database);
191     ASSERT(m_database->isOpen());
192
193     String currentSchema;
194     {
195         // Fetch the schema for an existing records table.
196         SQLiteStatement statement(*m_database, "SELECT type, sql FROM sqlite_master WHERE tbl_name='Records'");
197         if (statement.prepare() != SQLITE_OK)
198             return "Unable to prepare statement to fetch schema for the Records table.";
199
200         int sqliteResult = statement.step();
201
202         // If there is no Records table at all, create it and then bail.
203         if (sqliteResult == SQLITE_DONE) {
204             if (!m_database->executeCommand(recordsTableSchema()))
205                 return makeString("Could not create Records table in database (", m_database->lastError(), ") - ", m_database->lastErrorMsg());
206             return { };
207         }
208
209         if (sqliteResult != SQLITE_ROW)
210             return "Error executing statement to fetch schema for the Records table.";
211
212         currentSchema = statement.getColumnText(1);
213     }
214
215     ASSERT(!currentSchema.isEmpty());
216     
217     if (currentSchema == recordsTableSchema() || currentSchema == recordsTableSchemaAlternate())
218         return { };
219
220     // This database has a Records table but it is not a schema we expect.
221     // Trying to recover by deleting the data contained within is dangerous so
222     // we should consider this an unrecoverable error.
223     RELEASE_ASSERT_NOT_REACHED();
224 }
225
226 static String updateViaCacheToString(ServiceWorkerUpdateViaCache update)
227 {
228     switch (update) {
229     case ServiceWorkerUpdateViaCache::Imports:
230         return "Imports";
231     case ServiceWorkerUpdateViaCache::All:
232         return "All";
233     case ServiceWorkerUpdateViaCache::None:
234         return "None";
235     }
236
237     RELEASE_ASSERT_NOT_REACHED();
238 }
239
240 static Optional<ServiceWorkerUpdateViaCache> stringToUpdateViaCache(const String& update)
241 {
242     if (update == "Imports")
243         return ServiceWorkerUpdateViaCache::Imports;
244     if (update == "All")
245         return ServiceWorkerUpdateViaCache::All;
246     if (update == "None")
247         return ServiceWorkerUpdateViaCache::None;
248
249     return WTF::nullopt;
250 }
251
252 static String workerTypeToString(WorkerType workerType)
253 {
254     switch (workerType) {
255     case WorkerType::Classic:
256         return "Classic";
257     case WorkerType::Module:
258         return "Module";
259     }
260
261     RELEASE_ASSERT_NOT_REACHED();
262 }
263
264 static Optional<WorkerType> stringToWorkerType(const String& type)
265 {
266     if (type == "Classic")
267         return WorkerType::Classic;
268     if (type == "Module")
269         return WorkerType::Module;
270
271     return WTF::nullopt;
272 }
273
274 void RegistrationDatabase::pushChanges(Vector<ServiceWorkerContextData>&& datas, CompletionHandler<void()>&& completionHandler)
275 {
276     postTaskToWorkQueue([this, datas = crossThreadCopy(datas), completionHandler = WTFMove(completionHandler)]() mutable {
277         doPushChanges(WTFMove(datas));
278
279         if (!completionHandler)
280             return;
281
282         callOnMainThread(WTFMove(completionHandler));
283     });
284 }
285
286 void RegistrationDatabase::clearAll(CompletionHandler<void()>&& completionHandler)
287 {
288     postTaskToWorkQueue([this, completionHandler = WTFMove(completionHandler)]() mutable {
289         m_database = nullptr;
290
291         SQLiteFileSystem::deleteDatabaseFile(m_databaseFilePath);
292         SQLiteFileSystem::deleteEmptyDatabaseDirectory(m_databaseDirectory);
293
294         callOnMainThread(WTFMove(completionHandler));
295     });
296 }
297
298 void RegistrationDatabase::doPushChanges(Vector<ServiceWorkerContextData>&& datas)
299 {
300     if (!m_database) {
301         openSQLiteDatabase(m_databaseFilePath);
302         if (!m_database)
303             return;
304     }
305
306     SQLiteTransaction transaction(*m_database);
307     transaction.begin();
308
309     SQLiteStatement sql(*m_database, "INSERT INTO Records VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"_s);
310     if (sql.prepare() != SQLITE_OK) {
311         RELEASE_LOG_ERROR(ServiceWorker, "Failed to prepare statement to store registration data into records table (%i) - %s", m_database->lastError(), m_database->lastErrorMsg());
312         return;
313     }
314
315     for (auto& data : datas) {
316         if (data.registration.identifier == ServiceWorkerRegistrationIdentifier()) {
317             SQLiteStatement sql(*m_database, "DELETE FROM Records WHERE key = ?");
318             if (sql.prepare() != SQLITE_OK
319                 || sql.bindText(1, data.registration.key.toDatabaseKey()) != SQLITE_OK
320                 || sql.step() != SQLITE_DONE) {
321                 RELEASE_LOG_ERROR(ServiceWorker, "Failed to remove registration data from records table (%i) - %s", m_database->lastError(), m_database->lastErrorMsg());
322                 return;
323             }
324
325             continue;
326         }
327
328         WTF::Persistence::Encoder cspEncoder;
329         data.contentSecurityPolicy.encode(cspEncoder);
330
331         WTF::Persistence::Encoder scriptResourceMapEncoder;
332         scriptResourceMapEncoder.encode(data.scriptResourceMap);
333
334         if (sql.bindText(1, data.registration.key.toDatabaseKey()) != SQLITE_OK
335             || sql.bindText(2, data.registration.scopeURL.protocolHostAndPort()) != SQLITE_OK
336             || sql.bindText(3, data.registration.scopeURL.path()) != SQLITE_OK
337             || sql.bindText(4, data.registration.key.topOrigin().databaseIdentifier()) != SQLITE_OK
338             || sql.bindDouble(5, data.registration.lastUpdateTime.secondsSinceEpoch().value()) != SQLITE_OK
339             || sql.bindText(6, updateViaCacheToString(data.registration.updateViaCache)) != SQLITE_OK
340             || sql.bindText(7, data.scriptURL.string()) != SQLITE_OK
341             || sql.bindText(8, data.script) != SQLITE_OK
342             || sql.bindText(9, workerTypeToString(data.workerType)) != SQLITE_OK
343             || sql.bindBlob(10, cspEncoder.buffer(), cspEncoder.bufferSize()) != SQLITE_OK
344             || sql.bindText(11, data.referrerPolicy) != SQLITE_OK
345             || sql.bindBlob(12, scriptResourceMapEncoder.buffer(), scriptResourceMapEncoder.bufferSize()) != SQLITE_OK
346             || sql.step() != SQLITE_DONE) {
347             RELEASE_LOG_ERROR(ServiceWorker, "Failed to store registration data into records table (%i) - %s", m_database->lastError(), m_database->lastErrorMsg());
348             return;
349         }
350     }
351
352     transaction.commit();
353
354     LOG(ServiceWorker, "Pushed %zu changes to ServiceWorker registration database", datas.size());
355 }
356
357 String RegistrationDatabase::importRecords()
358 {
359     ASSERT(!isMainThread());
360
361     SQLiteStatement sql(*m_database, "SELECT * FROM Records;"_s);
362     if (sql.prepare() != SQLITE_OK)
363         return makeString("Failed to prepare statement to retrieve registrations from records table (", m_database->lastError(), ") - ", m_database->lastErrorMsg());
364
365     int result = sql.step();
366
367     for (; result == SQLITE_ROW; result = sql.step()) {
368         auto key = ServiceWorkerRegistrationKey::fromDatabaseKey(sql.getColumnText(0));
369         auto originURL = URL { URL(), sql.getColumnText(1) };
370         auto scopePath = sql.getColumnText(2);
371         auto topOrigin = SecurityOriginData::fromDatabaseIdentifier(sql.getColumnText(3));
372         auto lastUpdateCheckTime = WallTime::fromRawSeconds(sql.getColumnDouble(4));
373         auto updateViaCache = stringToUpdateViaCache(sql.getColumnText(5));
374         auto scriptURL = URL { URL(), sql.getColumnText(6) };
375         auto script = sql.getColumnText(7);
376         auto workerType = stringToWorkerType(sql.getColumnText(8));
377
378         Vector<uint8_t> contentSecurityPolicyData;
379         sql.getColumnBlobAsVector(9, contentSecurityPolicyData);
380         WTF::Persistence::Decoder cspDecoder(contentSecurityPolicyData.data(), contentSecurityPolicyData.size());
381         ContentSecurityPolicyResponseHeaders contentSecurityPolicy;
382         if (contentSecurityPolicyData.size() && !ContentSecurityPolicyResponseHeaders::decode(cspDecoder, contentSecurityPolicy))
383             continue;
384
385         auto referrerPolicy = sql.getColumnText(10);
386
387         Vector<uint8_t> scriptResourceMapData;
388         sql.getColumnBlobAsVector(11, scriptResourceMapData);
389         HashMap<URL, ServiceWorkerContextData::ImportedScript> scriptResourceMap;
390
391         WTF::Persistence::Decoder scriptResourceMapDecoder(scriptResourceMapData.data(), scriptResourceMapData.size());
392         if (scriptResourceMapData.size()) {
393             if (!scriptResourceMapDecoder.decode(scriptResourceMap))
394                 continue;
395         }
396
397         // Validate the input for this registration.
398         // If any part of this input is invalid, let's skip this registration.
399         // FIXME: Should we return an error skipping *all* registrations?
400         if (!key || !originURL.isValid() || !topOrigin || !updateViaCache || !scriptURL.isValid() || !workerType)
401             continue;
402
403         auto workerIdentifier = ServiceWorkerIdentifier::generate();
404         auto registrationIdentifier = ServiceWorkerRegistrationIdentifier::generate();
405         auto serviceWorkerData = ServiceWorkerData { workerIdentifier, scriptURL, ServiceWorkerState::Activated, *workerType, registrationIdentifier };
406         auto registration = ServiceWorkerRegistrationData { WTFMove(*key), registrationIdentifier, URL(originURL, scopePath), *updateViaCache, lastUpdateCheckTime, WTF::nullopt, WTF::nullopt, WTFMove(serviceWorkerData) };
407         auto contextData = ServiceWorkerContextData { WTF::nullopt, WTFMove(registration), workerIdentifier, WTFMove(script), WTFMove(contentSecurityPolicy), WTFMove(referrerPolicy), WTFMove(scriptURL), *workerType, m_sessionID, true, WTFMove(scriptResourceMap) };
408
409         callOnMainThread([protectedThis = makeRef(*this), contextData = contextData.isolatedCopy()]() mutable {
410             protectedThis->addRegistrationToStore(WTFMove(contextData));
411         });
412     }
413
414     if (result != SQLITE_DONE)
415         return makeString("Failed to import at least one registration from records table (", m_database->lastError(), ") - ", m_database->lastErrorMsg());
416
417     return { };
418 }
419
420 void RegistrationDatabase::addRegistrationToStore(ServiceWorkerContextData&& context)
421 {
422     if (m_store)
423         m_store->addRegistrationFromDatabase(WTFMove(context));
424 }
425
426 void RegistrationDatabase::databaseFailedToOpen()
427 {
428     if (m_store)
429         m_store->databaseFailedToOpen();
430 }
431
432 void RegistrationDatabase::databaseOpenedAndRecordsImported()
433 {
434     if (m_store)
435         m_store->databaseOpenedAndRecordsImported();
436 }
437
438 } // namespace WebCore
439
440 #endif // ENABLE(SERVICE_WORKER)