Reviewed by Brady Eidson.
[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::markAsDeletedAndClose()
271 {
272     if (m_deleted)
273         return;
274
275     LOG(StorageAPI, "Marking %s (%p) as deleted", stringIdentifier().ascii().data(), this);
276     m_deleted = true;
277
278     document()->databaseThread()->unscheduleDatabaseTasks(this);
279
280     RefPtr<DatabaseCloseTask> task = new DatabaseCloseTask(this);
281
282     task->lockForSynchronousScheduling();
283     m_document->databaseThread()->scheduleImmediateTask(task.get());
284     task->waitForSynchronousCompletion();
285 }
286
287 void Database::close()
288 {
289     m_sqliteDatabase.close();
290 }
291
292 unsigned long long Database::databaseSize() const
293 {
294     long long size;
295     if (!getFileSize(m_filename, size))
296         size = 0;
297     return size;
298 }
299
300 unsigned long long Database::maximumSize() const
301 {
302     // The maximum size for this database is the full quota for this origin, minus the current usage within this origin,
303     // except for the current usage of this database
304     
305     OriginQuotaManager& manager(DatabaseTracker::tracker().originQuotaManager());
306     Locker<OriginQuotaManager> locker(manager);
307     
308     return DatabaseTracker::tracker().quotaForOrigin(m_securityOrigin.get()) - manager.diskUsage(m_securityOrigin.get()) + databaseSize();
309 }
310
311 void Database::disableAuthorizer()
312 {
313     ASSERT(m_databaseAuthorizer);
314     m_databaseAuthorizer->disable();
315 }
316
317 void Database::enableAuthorizer()
318 {
319     ASSERT(m_databaseAuthorizer);
320     m_databaseAuthorizer->enable();
321 }
322
323 static int guidForOriginAndName(const String& origin, const String& name)
324 {
325     static int currentNewGUID = 1;
326     static Mutex stringIdentifierMutex;
327     static HashMap<String, int> stringIdentifierToGUIDMap;
328
329     String stringID;
330     if (origin.endsWith("/"))
331         stringID = origin + name;
332     else
333         stringID = origin + "/" + name;
334
335     MutexLocker locker(stringIdentifierMutex);
336     int guid = stringIdentifierToGUIDMap.get(stringID);
337     if (!guid) {
338         guid = currentNewGUID++;
339         stringIdentifierToGUIDMap.set(stringID, guid);
340     }
341
342     return guid;
343 }
344
345 void Database::resetAuthorizer()
346 {
347     if (m_databaseAuthorizer)
348         m_databaseAuthorizer->reset();
349 }
350
351 void Database::performPolicyChecks()
352 {
353     // FIXME: Code similar to the following will need to be run to enforce the per-origin size limit the spec mandates.
354     // Additionally, we might need a way to pause the database thread while the UA prompts the user for permission to
355     // increase the size limit
356
357     /*
358     if (m_databaseAuthorizer->lastActionIncreasedSize())
359         DatabaseTracker::scheduleFileSizeCheckOnMainThread(this);
360     */
361
362     notImplemented();
363 }
364
365 bool Database::performOpenAndVerify(ExceptionCode& e)
366 {
367     if (!m_sqliteDatabase.open(m_filename)) {
368         LOG_ERROR("Unable to open database at path %s", m_filename.ascii().data());
369         e = INVALID_STATE_ERR;
370         return false;
371     }
372
373     ASSERT(m_databaseAuthorizer);
374     m_sqliteDatabase.setAuthorizer(m_databaseAuthorizer);
375
376     if (!m_sqliteDatabase.tableExists(databaseInfoTableName())) {
377         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);")) {
378             LOG_ERROR("Unable to create table %s in database %s", databaseInfoTableName().ascii().data(), databaseDebugName().ascii().data());
379             e = INVALID_STATE_ERR;
380             return false;
381         }
382     }
383
384
385     String currentVersion;
386     {
387         MutexLocker locker(guidMutex());
388         currentVersion = guidToVersionMap().get(m_guid);
389
390         if (currentVersion.isNull())
391             LOG(StorageAPI, "Current cached version for guid %i is null", m_guid);
392         else
393             LOG(StorageAPI, "Current cached version for guid %i is %s", m_guid, currentVersion.ascii().data());
394
395         if (currentVersion.isNull()) {
396             if (!getVersionFromDatabase(currentVersion)) {
397                 LOG_ERROR("Failed to get current version from database %s", databaseDebugName().ascii().data());
398                 e = INVALID_STATE_ERR;
399                 return false;
400             }
401             if (currentVersion.length()) {
402                 LOG(StorageAPI, "Retrieved current version %s from database %s", currentVersion.ascii().data(), databaseDebugName().ascii().data());
403             } else {
404                 LOG(StorageAPI, "Setting version %s in database %s that was just created", m_expectedVersion.ascii().data(), databaseDebugName().ascii().data());
405                 if (!setVersionInDatabase(m_expectedVersion)) {
406                     LOG_ERROR("Failed to set version %s in database %s", m_expectedVersion.ascii().data(), databaseDebugName().ascii().data());
407                     e = INVALID_STATE_ERR;
408                     return false;
409                 }
410
411                 currentVersion = m_expectedVersion;
412             }
413
414             guidToVersionMap().set(m_guid, currentVersion.copy());
415         }
416     }
417
418     if (currentVersion.isNull()) {
419         LOG(StorageAPI, "Database %s does not have its version set", databaseDebugName().ascii().data());
420         currentVersion = "";
421     }
422
423     // 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
424     // changed, and here's where we would change it
425     if (m_expectedVersion.length()) {
426         if (currentVersion.length() && m_expectedVersion != currentVersion) {
427             LOG(StorageAPI, "page expects version %s from database %s, which actually has version name %s - openDatabase() call will fail", m_expectedVersion.ascii().data(),
428                 databaseDebugName().ascii().data(), currentVersion.ascii().data());
429             e = INVALID_STATE_ERR;
430             return false;
431         }
432     }
433
434     return true;
435 }
436
437 void Database::changeVersion(const String& oldVersion, const String& newVersion, 
438                              PassRefPtr<SQLTransactionCallback> callback, PassRefPtr<SQLTransactionErrorCallback> errorCallback,
439                              PassRefPtr<VoidCallback> successCallback)
440 {
441     m_transactionQueue.append(new SQLTransaction(this, callback, errorCallback, successCallback, new ChangeVersionWrapper(oldVersion, newVersion)));
442     MutexLocker locker(m_transactionInProgressMutex);
443     if (!m_transactionInProgress)
444         scheduleTransaction();
445 }
446
447 void Database::transaction(PassRefPtr<SQLTransactionCallback> callback, PassRefPtr<SQLTransactionErrorCallback> errorCallback,
448                            PassRefPtr<VoidCallback> successCallback)
449 {
450     m_transactionQueue.append(new SQLTransaction(this, callback, errorCallback, successCallback, 0));
451     MutexLocker locker(m_transactionInProgressMutex);
452     if (!m_transactionInProgress)
453         scheduleTransaction();
454 }
455
456 void Database::scheduleTransaction()
457 {
458     ASSERT(!m_transactionInProgressMutex.tryLock()); // Locked by caller.
459     RefPtr<SQLTransaction> transaction;
460     if (m_transactionQueue.tryGetMessage(transaction) && m_document->databaseThread()) {
461         DatabaseTransactionTask* task = new DatabaseTransactionTask(transaction);
462         LOG(StorageAPI, "Scheduling DatabaseTransactionTask %p for transaction %p\n", task, task->transaction());
463         m_transactionInProgress = true;
464         m_document->databaseThread()->scheduleTask(task);
465     } else
466         m_transactionInProgress = false;
467 }
468
469 void Database::scheduleTransactionStep(SQLTransaction* transaction)
470 {
471     if (m_document->databaseThread()) {
472         DatabaseTransactionTask* task = new DatabaseTransactionTask(transaction);
473         LOG(StorageAPI, "Scheduling DatabaseTransactionTask %p for the transaction step\n", task);
474         m_document->databaseThread()->scheduleTask(task);
475     }
476 }
477
478 void Database::scheduleTransactionCallback(SQLTransaction* transaction)
479 {
480     transaction->ref();
481     callOnMainThread(deliverPendingCallback, transaction);
482 }
483
484 Vector<String> Database::performGetTableNames()
485 {
486     disableAuthorizer();
487
488     SQLiteStatement statement(m_sqliteDatabase, "SELECT name FROM sqlite_master WHERE type='table';");
489     if (statement.prepare() != SQLResultOk) {
490         LOG_ERROR("Unable to retrieve list of tables for database %s", databaseDebugName().ascii().data());
491         enableAuthorizer();
492         return Vector<String>();
493     }
494
495     Vector<String> tableNames;
496     int result;
497     while ((result = statement.step()) == SQLResultRow) {
498         String name = statement.getColumnText(0);
499         if (name != databaseInfoTableName())
500             tableNames.append(name);
501     }
502
503     enableAuthorizer();
504
505     if (result != SQLResultDone) {
506         LOG_ERROR("Error getting tables for database %s", databaseDebugName().ascii().data());
507         return Vector<String>();
508     }
509
510     return tableNames;
511 }
512
513 String Database::version() const
514 {
515     if (m_deleted)
516         return String();
517     MutexLocker locker(guidMutex());
518     return guidToVersionMap().get(m_guid).copy();
519 }
520
521 void Database::deliverPendingCallback(void* context)
522 {
523     SQLTransaction* transaction = static_cast<SQLTransaction*>(context);
524     transaction->performPendingCallback();
525     transaction->deref(); // Was ref'd in scheduleTransactionCallback().
526 }
527
528 Vector<String> Database::tableNames()
529 {
530     RefPtr<DatabaseTableNamesTask> task = new DatabaseTableNamesTask(this);
531
532     task->lockForSynchronousScheduling();
533     m_document->databaseThread()->scheduleImmediateTask(task.get());
534     task->waitForSynchronousCompletion();
535
536     return task->tableNames();
537 }
538
539 void Database::setExpectedVersion(const String& version)
540 {
541     m_expectedVersion = version.copy();
542 }
543
544 PassRefPtr<SecurityOrigin> Database::securityOriginCopy() const
545 {
546     return m_securityOrigin->copy();
547 }
548
549 String Database::stringIdentifier() const
550 {
551     // Return a deep copy for ref counting thread safety
552     return m_name.copy();
553 }
554
555 }