11cc467b28a17d1d8fd58e67ff4213bcb56fe0f8
[WebKit-https.git] / WebCore / storage / Database.cpp
1 /*
2  * Copyright (C) 2007, 2008 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  *
8  * 1.  Redistributions of source code must retain the above copyright
9  *     notice, this list of conditions and the following disclaimer.
10  * 2.  Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution.
13  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14  *     its contributors may be used to endorse or promote products derived
15  *     from this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 #include "config.h"
30 #include "Database.h"
31
32 #include "ChangeVersionWrapper.h"
33 #include "CString.h"
34 #include "DatabaseAuthorizer.h"
35 #include "DatabaseTask.h"
36 #include "DatabaseThread.h"
37 #include "DatabaseTracker.h"
38 #include "Document.h"
39 #include "ExceptionCode.h"
40 #include "FileSystem.h"
41 #include "Frame.h"
42 #include "InspectorController.h"
43 #include "Logging.h"
44 #include "NotImplemented.h"
45 #include "Page.h"
46 #include "OriginQuotaManager.h"
47 #include "SQLiteDatabase.h"
48 #include "SQLiteStatement.h"
49 #include "SQLResultSet.h"
50
51 namespace WebCore {
52
53 static Mutex& guidMutex()
54 {
55     // FIXME: this is not a thread-safe way to initialize a shared global.
56     static Mutex mutex;
57     return mutex;
58 }
59
60 static HashMap<int, String>& guidToVersionMap()
61 {
62     static HashMap<int, String> map;
63     return map;
64 }
65
66 static HashMap<int, HashSet<Database*>*>& guidToDatabaseMap()
67 {
68     static HashMap<int, HashSet<Database*>*> map;
69     return map;
70 }
71
72 const String& Database::databaseInfoTableName()
73 {
74     static String name = "__WebKitDatabaseInfoTable__";
75     return name;
76 }
77
78 static const String& databaseVersionKey()
79 {
80     static String key = "WebKitDatabaseVersionKey";
81     return key;
82 }
83
84 static int guidForOriginAndName(const String& origin, const String& name);
85
86 PassRefPtr<Database> Database::openDatabase(Document* document, const String& name, const String& expectedVersion, const String& displayName, unsigned long estimatedSize, ExceptionCode& e)
87 {
88     if (!DatabaseTracker::tracker().canEstablishDatabase(document, name, displayName, estimatedSize)) {
89         // FIXME: There should be an exception raised here in addition to returning a null Database object.  The question has been raised with the WHATWG.
90         LOG(StorageAPI, "Database %s for origin %s not allowed to be established", name.ascii().data(), document->securityOrigin()->toString().ascii().data());
91         return 0;
92     }
93     
94     RefPtr<Database> database = new Database(document, name, expectedVersion);
95
96     if (!database->openAndVerifyVersion(e)) {
97        LOG(StorageAPI, "Failed to open and verify version (expected %s) of database %s", expectedVersion.ascii().data(), database->databaseDebugName().ascii().data());
98        return 0;
99     }
100     
101     DatabaseTracker::tracker().setDatabaseDetails(document->securityOrigin(), name, displayName, estimatedSize);
102
103     document->setHasOpenDatabases();
104
105     if (Page* page = document->frame()->page())
106         page->inspectorController()->didOpenDatabase(database.get(), document->domain(), name, expectedVersion);
107
108     return database;
109 }
110
111 Database::Database(Document* document, const String& name, const String& expectedVersion)
112     : m_transactionInProgress(false)
113     , m_document(document)
114     , m_name(name.copy())
115     , m_guid(0)
116     , m_expectedVersion(expectedVersion)
117     , m_deleted(0)
118 {
119     ASSERT(document);
120     m_securityOrigin = document->securityOrigin();
121
122     if (m_name.isNull())
123         m_name = "";
124
125     initializeThreading();
126
127     m_guid = guidForOriginAndName(m_securityOrigin->toString(), name);
128
129     {
130         MutexLocker locker(guidMutex());
131
132         HashSet<Database*>* hashSet = guidToDatabaseMap().get(m_guid);
133         if (!hashSet) {
134             hashSet = new HashSet<Database*>;
135             guidToDatabaseMap().set(m_guid, hashSet);
136         }
137
138         hashSet->add(this);
139     }
140
141     ASSERT(m_document->databaseThread());
142
143     m_filename = DatabaseTracker::tracker().fullPathForDatabase(m_securityOrigin.get(), m_name);
144
145     DatabaseTracker::tracker().addOpenDatabase(this);
146 }
147
148 Database::~Database()
149 {
150     {
151         MutexLocker locker(guidMutex());
152
153         HashSet<Database*>* hashSet = guidToDatabaseMap().get(m_guid);
154         ASSERT(hashSet);
155         ASSERT(hashSet->contains(this));
156         hashSet->remove(this);
157         if (hashSet->isEmpty()) {
158             guidToDatabaseMap().remove(m_guid);
159             delete hashSet;
160             guidToVersionMap().remove(m_guid);
161         }
162     }
163
164     if (m_document->databaseThread())
165         m_document->databaseThread()->unscheduleDatabaseTasks(this);
166
167     DatabaseTracker::tracker().removeOpenDatabase(this);
168 }
169
170 bool Database::openAndVerifyVersion(ExceptionCode& e)
171 {
172     m_databaseAuthorizer = new DatabaseAuthorizer();
173
174     RefPtr<DatabaseOpenTask> task = new DatabaseOpenTask(this);
175
176     task->lockForSynchronousScheduling();
177     m_document->databaseThread()->scheduleImmediateTask(task.get());
178     task->waitForSynchronousCompletion();
179
180     ASSERT(task->isComplete());
181     e = task->exceptionCode();
182     return task->openSuccessful();
183 }
184
185
186 static bool retrieveTextResultFromDatabase(SQLiteDatabase& db, const String& query, String& resultString)
187 {
188     SQLiteStatement statement(db, query);
189     int result = statement.prepare();
190
191     if (result != SQLResultOk) {
192         LOG_ERROR("Error (%i) preparing statement to read text result from database (%s)", result, query.ascii().data());
193         return false;
194     }
195
196     result = statement.step();
197     if (result == SQLResultRow) {
198         resultString = statement.getColumnText(0);
199         return true;
200     } else if (result == SQLResultDone) {
201         resultString = String();
202         return true;
203     } else {
204         LOG_ERROR("Error (%i) reading text result from database (%s)", result, query.ascii().data());
205         return false;
206     }
207 }
208
209 bool Database::getVersionFromDatabase(String& version)
210 {
211     static String getVersionQuery = "SELECT value FROM " + databaseInfoTableName() + " WHERE key = '" + databaseVersionKey() + "';";
212
213     m_databaseAuthorizer->disable();
214
215     bool result = retrieveTextResultFromDatabase(m_sqliteDatabase, getVersionQuery.copy(), version);
216     if (!result)
217         LOG_ERROR("Failed to retrieve version from database %s", databaseDebugName().ascii().data());
218
219     m_databaseAuthorizer->enable();
220
221     return result;
222 }
223
224 static bool setTextValueInDatabase(SQLiteDatabase& db, const String& query, const String& value)
225 {
226     SQLiteStatement statement(db, query);
227     int result = statement.prepare();
228
229     if (result != SQLResultOk) {
230         LOG_ERROR("Failed to prepare statement to set value in database (%s)", query.ascii().data());
231         return false;
232     }
233
234     statement.bindText(1, value);
235
236     result = statement.step();
237     if (result != SQLResultDone) {
238         LOG_ERROR("Failed to step statement to set value in database (%s)", query.ascii().data());
239         return false;
240     }
241
242     return true;
243 }
244
245 bool Database::setVersionInDatabase(const String& version)
246 {
247     static String setVersionQuery = "INSERT INTO " + databaseInfoTableName() + " (key, value) VALUES ('" + databaseVersionKey() + "', ?);";
248
249     m_databaseAuthorizer->disable();
250
251     bool result = setTextValueInDatabase(m_sqliteDatabase, setVersionQuery.copy(), version);
252     if (!result)
253         LOG_ERROR("Failed to set version %s in database (%s)", version.ascii().data(), setVersionQuery.ascii().data());
254
255     m_databaseAuthorizer->enable();
256
257     return result;
258 }
259
260 bool Database::versionMatchesExpected() const
261 {
262     if (!m_expectedVersion.isEmpty()) {
263         MutexLocker locker(guidMutex());
264         return m_expectedVersion == guidToVersionMap().get(m_guid);
265     }
266     
267     return true;
268 }
269
270 void Database::markAsDeleted()
271 {
272     if (m_deleted)
273         return;
274     LOG(StorageAPI, "Marking %s (%p) as deleted", stringIdentifier().ascii().data(), this);
275     m_deleted = true;
276 }
277
278 unsigned long long Database::databaseSize() const
279 {
280     long long size;
281     if (!getFileSize(m_filename, size))
282         size = 0;
283     return size;
284 }
285
286 unsigned long long Database::maximumSize() const
287 {
288     // The maximum size for this database is the full quota for this origin, minus the current usage within this origin,
289     // except for the current usage of this database
290     
291     OriginQuotaManager& manager(DatabaseTracker::tracker().originQuotaManager());
292     Locker<OriginQuotaManager> locker(manager);
293     
294     return DatabaseTracker::tracker().quotaForOrigin(m_securityOrigin.get()) - manager.diskUsage(m_securityOrigin.get()) + databaseSize();
295 }
296
297 void Database::disableAuthorizer()
298 {
299     ASSERT(m_databaseAuthorizer);
300     m_databaseAuthorizer->disable();
301 }
302
303 void Database::enableAuthorizer()
304 {
305     ASSERT(m_databaseAuthorizer);
306     m_databaseAuthorizer->enable();
307 }
308
309 static int guidForOriginAndName(const String& origin, const String& name)
310 {
311     static int currentNewGUID = 1;
312     static Mutex stringIdentifierMutex;
313     static HashMap<String, int> stringIdentifierToGUIDMap;
314
315     String stringID;
316     if (origin.endsWith("/"))
317         stringID = origin + name;
318     else
319         stringID = origin + "/" + name;
320
321     MutexLocker locker(stringIdentifierMutex);
322     int guid = stringIdentifierToGUIDMap.get(stringID);
323     if (!guid) {
324         guid = currentNewGUID++;
325         stringIdentifierToGUIDMap.set(stringID, guid);
326     }
327
328     return guid;
329 }
330
331 void Database::resetAuthorizer()
332 {
333     if (m_databaseAuthorizer)
334         m_databaseAuthorizer->reset();
335 }
336
337 void Database::performPolicyChecks()
338 {
339     // FIXME: Code similar to the following will need to be run to enforce the per-origin size limit the spec mandates.
340     // Additionally, we might need a way to pause the database thread while the UA prompts the user for permission to
341     // increase the size limit
342
343     /*
344     if (m_databaseAuthorizer->lastActionIncreasedSize())
345         DatabaseTracker::scheduleFileSizeCheckOnMainThread(this);
346     */
347
348     notImplemented();
349 }
350
351 bool Database::performOpenAndVerify(ExceptionCode& e)
352 {
353     if (!m_sqliteDatabase.open(m_filename)) {
354         LOG_ERROR("Unable to open database at path %s", m_filename.ascii().data());
355         e = INVALID_STATE_ERR;
356         return false;
357     }
358
359     ASSERT(m_databaseAuthorizer);
360     m_sqliteDatabase.setAuthorizer(m_databaseAuthorizer);
361
362     if (!m_sqliteDatabase.tableExists(databaseInfoTableName())) {
363         if (!m_sqliteDatabase.executeCommand("CREATE TABLE " + databaseInfoTableName() + " (key TEXT NOT NULL ON CONFLICT FAIL UNIQUE ON CONFLICT REPLACE,value TEXT NOT NULL ON CONFLICT FAIL);")) {
364             LOG_ERROR("Unable to create table %s in database %s", databaseInfoTableName().ascii().data(), databaseDebugName().ascii().data());
365             e = INVALID_STATE_ERR;
366             return false;
367         }
368     }
369
370
371     String currentVersion;
372     {
373         MutexLocker locker(guidMutex());
374         currentVersion = guidToVersionMap().get(m_guid);
375
376         if (currentVersion.isNull())
377             LOG(StorageAPI, "Current cached version for guid %i is null", m_guid);
378         else
379             LOG(StorageAPI, "Current cached version for guid %i is %s", m_guid, currentVersion.ascii().data());
380
381         if (currentVersion.isNull()) {
382             if (!getVersionFromDatabase(currentVersion)) {
383                 LOG_ERROR("Failed to get current version from database %s", databaseDebugName().ascii().data());
384                 e = INVALID_STATE_ERR;
385                 return false;
386             }
387             if (currentVersion.length()) {
388                 LOG(StorageAPI, "Retrieved current version %s from database %s", currentVersion.ascii().data(), databaseDebugName().ascii().data());
389             } else {
390                 LOG(StorageAPI, "Setting version %s in database %s that was just created", m_expectedVersion.ascii().data(), databaseDebugName().ascii().data());
391                 if (!setVersionInDatabase(m_expectedVersion)) {
392                     LOG_ERROR("Failed to set version %s in database %s", m_expectedVersion.ascii().data(), databaseDebugName().ascii().data());
393                     e = INVALID_STATE_ERR;
394                     return false;
395                 }
396
397                 currentVersion = m_expectedVersion;
398             }
399
400             guidToVersionMap().set(m_guid, currentVersion.copy());
401         }
402     }
403
404     if (currentVersion.isNull()) {
405         LOG(StorageAPI, "Database %s does not have its version set", databaseDebugName().ascii().data());
406         currentVersion = "";
407     }
408
409     // FIXME: For now, the spec says that if the database has no version, it is valid for any "Expected version" string.  That seems silly and I think it should be
410     // changed, and here's where we would change it
411     if (m_expectedVersion.length()) {
412         if (currentVersion.length() && m_expectedVersion != currentVersion) {
413             LOG(StorageAPI, "page expects version %s from database %s, which actually has version name %s - openDatabase() call will fail", m_expectedVersion.ascii().data(),
414                 databaseDebugName().ascii().data(), currentVersion.ascii().data());
415             e = INVALID_STATE_ERR;
416             return false;
417         }
418     }
419
420     return true;
421 }
422
423 void Database::changeVersion(const String& oldVersion, const String& newVersion, 
424                              PassRefPtr<SQLTransactionCallback> callback, PassRefPtr<SQLTransactionErrorCallback> errorCallback,
425                              PassRefPtr<VoidCallback> successCallback)
426 {
427     m_transactionQueue.append(new SQLTransaction(this, callback, errorCallback, successCallback, new ChangeVersionWrapper(oldVersion, newVersion)));
428     MutexLocker locker(m_transactionInProgressMutex);
429     if (!m_transactionInProgress)
430         scheduleTransaction();
431 }
432
433 void Database::transaction(PassRefPtr<SQLTransactionCallback> callback, PassRefPtr<SQLTransactionErrorCallback> errorCallback,
434                            PassRefPtr<VoidCallback> successCallback)
435 {
436     m_transactionQueue.append(new SQLTransaction(this, callback, errorCallback, successCallback, 0));
437     MutexLocker locker(m_transactionInProgressMutex);
438     if (!m_transactionInProgress)
439         scheduleTransaction();
440 }
441
442 void Database::scheduleTransaction()
443 {
444     ASSERT(!m_transactionInProgressMutex.tryLock()); // Locked by caller.
445     RefPtr<SQLTransaction> transaction;
446     if (m_transactionQueue.tryGetMessage(transaction) && m_document->databaseThread()) {
447         DatabaseTransactionTask* task = new DatabaseTransactionTask(transaction);
448         LOG(StorageAPI, "Scheduling DatabaseTransactionTask %p for transaction %p\n", task, task->transaction());
449         m_transactionInProgress = true;
450         m_document->databaseThread()->scheduleTask(task);
451     } else
452         m_transactionInProgress = false;
453 }
454
455 void Database::scheduleTransactionStep(SQLTransaction* transaction)
456 {
457     if (m_document->databaseThread()) {
458         DatabaseTransactionTask* task = new DatabaseTransactionTask(transaction);
459         LOG(StorageAPI, "Scheduling DatabaseTransactionTask %p for the transaction step\n", task);
460         m_document->databaseThread()->scheduleTask(task);
461     }
462 }
463
464 void Database::scheduleTransactionCallback(SQLTransaction* transaction)
465 {
466     transaction->ref();
467     callOnMainThread(deliverPendingCallback, transaction);
468 }
469
470 Vector<String> Database::performGetTableNames()
471 {
472     disableAuthorizer();
473
474     SQLiteStatement statement(m_sqliteDatabase, "SELECT name FROM sqlite_master WHERE type='table';");
475     if (statement.prepare() != SQLResultOk) {
476         LOG_ERROR("Unable to retrieve list of tables for database %s", databaseDebugName().ascii().data());
477         enableAuthorizer();
478         return Vector<String>();
479     }
480
481     Vector<String> tableNames;
482     int result;
483     while ((result = statement.step()) == SQLResultRow) {
484         String name = statement.getColumnText(0);
485         if (name != databaseInfoTableName())
486             tableNames.append(name);
487     }
488
489     enableAuthorizer();
490
491     if (result != SQLResultDone) {
492         LOG_ERROR("Error getting tables for database %s", databaseDebugName().ascii().data());
493         return Vector<String>();
494     }
495
496     return tableNames;
497 }
498
499 String Database::version() const
500 {
501     if (m_deleted)
502         return String();
503     MutexLocker locker(guidMutex());
504     return guidToVersionMap().get(m_guid).copy();
505 }
506
507 void Database::deliverPendingCallback(void* context)
508 {
509     SQLTransaction* transaction = static_cast<SQLTransaction*>(context);
510     transaction->performPendingCallback();
511     transaction->deref(); // Was ref'd in scheduleTransactionCallback().
512 }
513
514 Vector<String> Database::tableNames()
515 {
516     RefPtr<DatabaseTableNamesTask> task = new DatabaseTableNamesTask(this);
517
518     task->lockForSynchronousScheduling();
519     m_document->databaseThread()->scheduleImmediateTask(task.get());
520     task->waitForSynchronousCompletion();
521
522     return task->tableNames();
523 }
524
525 void Database::setExpectedVersion(const String& version)
526 {
527     m_expectedVersion = version.copy();
528 }
529
530 PassRefPtr<SecurityOrigin> Database::securityOriginCopy() const
531 {
532     return m_securityOrigin->copy();
533 }
534
535 String Database::stringIdentifier() const
536 {
537     // Return a deep copy for ref counting thread safety
538     return m_name.copy();
539 }
540
541 }