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