Use SecurityOriginData more consistently in Service Worker code
[WebKit-https.git] / Source / WebCore / Modules / webdatabase / DatabaseManager.cpp
1 /*
2  * Copyright (C) 2012 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. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "DatabaseManager.h"
28
29 #include "Database.h"
30 #include "DatabaseCallback.h"
31 #include "DatabaseContext.h"
32 #include "DatabaseTask.h"
33 #include "DatabaseTracker.h"
34 #include "InspectorInstrumentation.h"
35 #include "Logging.h"
36 #include "PlatformStrategies.h"
37 #include "ScriptController.h"
38 #include "ScriptExecutionContext.h"
39 #include "SecurityOrigin.h"
40 #include "SecurityOriginData.h"
41 #include <wtf/NeverDestroyed.h>
42
43 namespace WebCore {
44
45 class DatabaseManager::ProposedDatabase {
46 public:
47     ProposedDatabase(DatabaseManager&, SecurityOrigin&, const String& name, const String& displayName, unsigned long estimatedSize);
48     ~ProposedDatabase();
49
50     SecurityOrigin& origin() { return m_origin; }
51     DatabaseDetails& details() { return m_details; }
52
53 private:
54     DatabaseManager& m_manager;
55     Ref<SecurityOrigin> m_origin;
56     DatabaseDetails m_details;
57 };
58
59 DatabaseManager::ProposedDatabase::ProposedDatabase(DatabaseManager& manager, SecurityOrigin& origin, const String& name, const String& displayName, unsigned long estimatedSize)
60     : m_manager(manager)
61     , m_origin(origin.isolatedCopy())
62     , m_details(name.isolatedCopy(), displayName.isolatedCopy(), estimatedSize, 0, 0, 0)
63 {
64     m_manager.addProposedDatabase(*this);
65 }
66
67 inline DatabaseManager::ProposedDatabase::~ProposedDatabase()
68 {
69     m_manager.removeProposedDatabase(*this);
70 }
71
72 DatabaseManager& DatabaseManager::singleton()
73 {
74     static NeverDestroyed<DatabaseManager> instance;
75     return instance;
76 }
77
78 void DatabaseManager::initialize(const String& databasePath)
79 {
80     DatabaseTracker::initializeTracker(databasePath);
81 }
82
83 void DatabaseManager::setClient(DatabaseManagerClient* client)
84 {
85     m_client = client;
86     DatabaseTracker::singleton().setClient(client);
87 }
88
89 bool DatabaseManager::isAvailable()
90 {
91     return m_databaseIsAvailable;
92 }
93
94 void DatabaseManager::setIsAvailable(bool available)
95 {
96     m_databaseIsAvailable = available;
97 }
98
99 Ref<DatabaseContext> DatabaseManager::databaseContext(ScriptExecutionContext& context)
100 {
101     if (auto databaseContext = context.databaseContext())
102         return *databaseContext;
103     return adoptRef(*new DatabaseContext(context));
104 }
105
106 #if LOG_DISABLED
107
108 static inline void logOpenDatabaseError(ScriptExecutionContext&, const String&)
109 {
110 }
111
112 #else
113
114 static void logOpenDatabaseError(ScriptExecutionContext& context, const String& name)
115 {
116     LOG(StorageAPI, "Database %s for origin %s not allowed to be established", name.utf8().data(), context.securityOrigin()->toString().utf8().data());
117 }
118
119 #endif
120
121 ExceptionOr<Ref<Database>> DatabaseManager::openDatabaseBackend(ScriptExecutionContext& context, const String& name, const String& expectedVersion, const String& displayName, unsigned estimatedSize, bool setVersionInNewDatabase)
122 {
123     auto backend = tryToOpenDatabaseBackend(context, name, expectedVersion, displayName, estimatedSize, setVersionInNewDatabase, FirstTryToOpenDatabase);
124
125     if (backend.hasException()) {
126         if (backend.exception().code() == QuotaExceededError) {
127             // Notify the client that we've exceeded the database quota.
128             // The client may want to increase the quota, and we'll give it
129             // one more try after if that is the case.
130             {
131                 // FIXME: What guarantees context.securityOrigin() is non-null?
132                 ProposedDatabase proposedDatabase { *this, *context.securityOrigin(), name, displayName, estimatedSize };
133                 this->databaseContext(context)->databaseExceededQuota(name, proposedDatabase.details());
134             }
135             backend = tryToOpenDatabaseBackend(context, name, expectedVersion, displayName, estimatedSize, setVersionInNewDatabase, RetryOpenDatabase);
136         }
137     }
138
139     if (backend.hasException()) {
140         if (backend.exception().code() == InvalidStateError)
141             logErrorMessage(context, backend.exception().message());
142         else
143             logOpenDatabaseError(context, name);
144     }
145
146     return backend;
147 }
148
149 ExceptionOr<Ref<Database>> DatabaseManager::tryToOpenDatabaseBackend(ScriptExecutionContext& scriptContext, const String& name, const String& expectedVersion, const String& displayName, unsigned estimatedSize, bool setVersionInNewDatabase,
150     OpenAttempt attempt)
151 {
152     if (is<Document>(&scriptContext)) {
153         auto* page = downcast<Document>(scriptContext).page();
154         if (!page || page->usesEphemeralSession())
155             return Exception { SecurityError };
156     }
157
158     if (scriptContext.isWorkerGlobalScope()) {
159         ASSERT_NOT_REACHED();
160         return Exception { SecurityError };
161     }
162
163     auto backendContext = this->databaseContext(scriptContext);
164
165     ExceptionOr<void> preflightResult;
166     switch (attempt) {
167     case FirstTryToOpenDatabase:
168         preflightResult = DatabaseTracker::singleton().canEstablishDatabase(backendContext, name, estimatedSize);
169         break;
170     case RetryOpenDatabase:
171         preflightResult = DatabaseTracker::singleton().retryCanEstablishDatabase(backendContext, name, estimatedSize);
172         break;
173     }
174     if (preflightResult.hasException())
175         return preflightResult.releaseException();
176
177     auto database = adoptRef(*new Database(backendContext, name, expectedVersion, displayName, estimatedSize));
178
179     auto openResult = database->openAndVerifyVersion(setVersionInNewDatabase);
180     if (openResult.hasException())
181         return openResult.releaseException();
182
183     // FIXME: What guarantees backendContext.securityOrigin() is non-null?
184     DatabaseTracker::singleton().setDatabaseDetails(backendContext->securityOrigin(), name, displayName, estimatedSize);
185     return WTFMove(database);
186 }
187
188 void DatabaseManager::addProposedDatabase(ProposedDatabase& database)
189 {
190     std::lock_guard<Lock> lock { m_proposedDatabasesMutex };
191     m_proposedDatabases.add(&database);
192 }
193
194 void DatabaseManager::removeProposedDatabase(ProposedDatabase& database)
195 {
196     std::lock_guard<Lock> lock { m_proposedDatabasesMutex };
197     m_proposedDatabases.remove(&database);
198 }
199
200 ExceptionOr<Ref<Database>> DatabaseManager::openDatabase(ScriptExecutionContext& context, const String& name, const String& expectedVersion, const String& displayName, unsigned estimatedSize, RefPtr<DatabaseCallback>&& creationCallback)
201 {
202     ScriptController::initializeThreading();
203
204     bool setVersionInNewDatabase = !creationCallback;
205     auto openResult = openDatabaseBackend(context, name, expectedVersion, displayName, estimatedSize, setVersionInNewDatabase);
206     if (openResult.hasException())
207         return openResult.releaseException();
208
209     RefPtr<Database> database = openResult.releaseReturnValue();
210
211     auto databaseContext = this->databaseContext(context);
212     databaseContext->setHasOpenDatabases();
213     InspectorInstrumentation::didOpenDatabase(&context, database.copyRef(), context.securityOrigin()->host(), name, expectedVersion);
214
215     if (database->isNew() && creationCallback.get()) {
216         LOG(StorageAPI, "Scheduling DatabaseCreationCallbackTask for database %p\n", database.get());
217         database->setHasPendingCreationEvent(true);
218         database->m_scriptExecutionContext->postTask([creationCallback, database] (ScriptExecutionContext&) {
219             creationCallback->handleEvent(*database);
220             database->setHasPendingCreationEvent(false);
221         });
222     }
223
224     return database.releaseNonNull();
225 }
226
227 bool DatabaseManager::hasOpenDatabases(ScriptExecutionContext& context)
228 {
229     auto databaseContext = context.databaseContext();
230     return databaseContext && databaseContext->hasOpenDatabases();
231 }
232
233 void DatabaseManager::stopDatabases(ScriptExecutionContext& context, DatabaseTaskSynchronizer* synchronizer)
234 {
235     auto databaseContext = context.databaseContext();
236     if (!databaseContext || !databaseContext->stopDatabases(synchronizer)) {
237         if (synchronizer)
238             synchronizer->taskCompleted();
239     }
240 }
241
242 String DatabaseManager::fullPathForDatabase(SecurityOrigin& origin, const String& name, bool createIfDoesNotExist)
243 {
244     {
245         std::lock_guard<Lock> lock { m_proposedDatabasesMutex };
246         for (auto* proposedDatabase : m_proposedDatabases) {
247             if (proposedDatabase->details().name() == name && proposedDatabase->origin().equal(&origin))
248                 return String();
249         }
250     }
251     return DatabaseTracker::singleton().fullPathForDatabase(origin.data(), name, createIfDoesNotExist);
252 }
253
254 DatabaseDetails DatabaseManager::detailsForNameAndOrigin(const String& name, SecurityOrigin& origin)
255 {
256     {
257         std::lock_guard<Lock> lock { m_proposedDatabasesMutex };
258         for (auto* proposedDatabase : m_proposedDatabases) {
259             if (proposedDatabase->details().name() == name && proposedDatabase->origin().equal(&origin)) {
260                 ASSERT(&proposedDatabase->details().thread() == &Thread::current() || isMainThread());
261                 return proposedDatabase->details();
262             }
263         }
264     }
265
266     return DatabaseTracker::singleton().detailsForNameAndOrigin(name, origin.data());
267 }
268
269 void DatabaseManager::logErrorMessage(ScriptExecutionContext& context, const String& message)
270 {
271     context.addConsoleMessage(MessageSource::Storage, MessageLevel::Error, message);
272 }
273
274 } // namespace WebCore