1/*
2 * Copyright (C) 2010 Google 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 "AbstractDatabase.h"
31
32#if ENABLE(DATABASE)
33#include "DatabaseAuthorizer.h"
34#include "DatabaseTracker.h"
35#include "Logging.h"
36#include "SQLiteStatement.h"
37#include "ScriptExecutionContext.h"
38#include "SecurityOrigin.h"
39#include <wtf/HashMap.h>
40#include <wtf/HashSet.h>
41#include <wtf/PassRefPtr.h>
42#include <wtf/RefPtr.h>
43#include <wtf/StdLibExtras.h>
44#include <wtf/text/CString.h>
45#include <wtf/text/StringHash.h>
46
47namespace WebCore {
48
49static bool retrieveTextResultFromDatabase(SQLiteDatabase& db, const String& query, String& resultString)
50{
51    SQLiteStatement statement(db, query);
52    int result = statement.prepare();
53
54    if (result != SQLResultOk) {
55        LOG_ERROR("Error (%i) preparing statement to read text result from database (%s)", result, query.ascii().data());
56        return false;
57    }
58
59    result = statement.step();
60    if (result == SQLResultRow) {
61        resultString = statement.getColumnText(0);
62        return true;
63    }
64    if (result == SQLResultDone) {
65        resultString = String();
66        return true;
67    }
68
69    LOG_ERROR("Error (%i) reading text result from database (%s)", result, query.ascii().data());
70    return false;
71}
72
73static bool setTextValueInDatabase(SQLiteDatabase& db, const String& query, const String& value)
74{
75    SQLiteStatement statement(db, query);
76    int result = statement.prepare();
77
78    if (result != SQLResultOk) {
79        LOG_ERROR("Failed to prepare statement to set value in database (%s)", query.ascii().data());
80        return false;
81    }
82
83    statement.bindText(1, value);
84
85    result = statement.step();
86    if (result != SQLResultDone) {
87        LOG_ERROR("Failed to step statement to set value in database (%s)", query.ascii().data());
88        return false;
89    }
90
91    return true;
92}
93
94// FIXME: move all guid-related functions to a DatabaseVersionTracker class.
95static Mutex& guidMutex()
96{
97    // Note: We don't have to use AtomicallyInitializedStatic here because
98    // this function is called once in the constructor on the main thread
99    // before any other threads that call this function are used.
100    DEFINE_STATIC_LOCAL(Mutex, mutex, ());
101    return mutex;
102}
103
104typedef HashMap<int, String> GuidVersionMap;
105static GuidVersionMap& guidToVersionMap()
106{
107    DEFINE_STATIC_LOCAL(GuidVersionMap, map, ());
108    return map;
109}
110
111// NOTE: Caller must lock guidMutex().
112static inline void updateGuidVersionMap(int guid, String newVersion)
113{
114    // Ensure the the mutex is locked.
115    ASSERT(!guidMutex().tryLock());
116
117    // Note: It is not safe to put an empty string into the guidToVersionMap() map.
118    // That's because the map is cross-thread, but empty strings are per-thread.
119    // The copy() function makes a version of the string you can use on the current
120    // thread, but we need a string we can keep in a cross-thread data structure.
121    // FIXME: This is a quite-awkward restriction to have to program with.
122
123    // Map null string to empty string (see comment above).
124    guidToVersionMap().set(guid, newVersion.isEmpty() ? String() : newVersion.threadsafeCopy());
125}
126
127typedef HashMap<int, HashSet<AbstractDatabase*>*> GuidDatabaseMap;
128static GuidDatabaseMap& guidToDatabaseMap()
129{
130    DEFINE_STATIC_LOCAL(GuidDatabaseMap, map, ());
131    return map;
132}
133
134static int guidForOriginAndName(const String& origin, const String& name)
135{
136    String stringID = origin + "/" + name;
137
138    // Note: We don't have to use AtomicallyInitializedStatic here because
139    // this function is called once in the constructor on the main thread
140    // before any other threads that call this function are used.
141    DEFINE_STATIC_LOCAL(Mutex, stringIdentifierMutex, ());
142    MutexLocker locker(stringIdentifierMutex);
143    typedef HashMap<String, int> IDGuidMap;
144    DEFINE_STATIC_LOCAL(IDGuidMap, stringIdentifierToGUIDMap, ());
145    int guid = stringIdentifierToGUIDMap.get(stringID);
146    if (!guid) {
147        static int currentNewGUID = 1;
148        guid = currentNewGUID++;
149        stringIdentifierToGUIDMap.set(stringID, guid);
150    }
151
152    return guid;
153}
154
155static bool isDatabaseAvailable = true;
156
157bool AbstractDatabase::isAvailable()
158{
159    return isDatabaseAvailable;
160}
161
162void AbstractDatabase::setIsAvailable(bool available)
163{
164    isDatabaseAvailable = available;
165}
166
167// static
168const String& AbstractDatabase::databaseInfoTableName()
169{
170    DEFINE_STATIC_LOCAL(String, name, ("__WebKitDatabaseInfoTable__"));
171    return name;
172}
173
174AbstractDatabase::AbstractDatabase(ScriptExecutionContext* context, const String& name, const String& expectedVersion,
175                                   const String& displayName, unsigned long estimatedSize)
176    : m_scriptExecutionContext(context)
177    , m_name(name.crossThreadString())
178    , m_expectedVersion(expectedVersion.crossThreadString())
179    , m_displayName(displayName.crossThreadString())
180    , m_estimatedSize(estimatedSize)
181    , m_guid(0)
182    , m_opened(false)
183    , m_new(false)
184{
185    ASSERT(context->isContextThread());
186    m_contextThreadSecurityOrigin = m_scriptExecutionContext->securityOrigin();
187
188    m_databaseAuthorizer = DatabaseAuthorizer::create(databaseInfoTableName());
189
190    if (m_name.isNull())
191        m_name = "";
192
193    m_guid = guidForOriginAndName(securityOrigin()->toString(), name);
194    {
195        MutexLocker locker(guidMutex());
196
197        HashSet<AbstractDatabase*>* hashSet = guidToDatabaseMap().get(m_guid);
198        if (!hashSet) {
199            hashSet = new HashSet<AbstractDatabase*>;
200            guidToDatabaseMap().set(m_guid, hashSet);
201        }
202
203        hashSet->add(this);
204    }
205
206    m_filename = DatabaseTracker::tracker().fullPathForDatabase(securityOrigin(), m_name);
207    DatabaseTracker::tracker().addOpenDatabase(this);
208}
209
210AbstractDatabase::~AbstractDatabase()
211{
212}
213
214void AbstractDatabase::closeDatabase()
215{
216    if (!m_opened)
217        return;
218
219    m_sqliteDatabase.close();
220    m_opened = false;
221    {
222        MutexLocker locker(guidMutex());
223
224        HashSet<AbstractDatabase*>* hashSet = guidToDatabaseMap().get(m_guid);
225        ASSERT(hashSet);
226        ASSERT(hashSet->contains(this));
227        hashSet->remove(this);
228        if (hashSet->isEmpty()) {
229            guidToDatabaseMap().remove(m_guid);
230            delete hashSet;
231            guidToVersionMap().remove(m_guid);
232        }
233    }
234}
235
236String AbstractDatabase::version() const
237{
238    MutexLocker locker(guidMutex());
239    return guidToVersionMap().get(m_guid).threadsafeCopy();
240}
241
242static const int maxSqliteBusyWaitTime = 30000;
243bool AbstractDatabase::performOpenAndVerify(bool shouldSetVersionInNewDatabase, ExceptionCode& ec)
244{
245    if (!m_sqliteDatabase.open(m_filename, true)) {
246        LOG_ERROR("Unable to open database at path %s", m_filename.ascii().data());
247        ec = INVALID_STATE_ERR;
248        return false;
249    }
250    if (!m_sqliteDatabase.turnOnIncrementalAutoVacuum())
251        LOG_ERROR("Unable to turn on incremental auto-vacuum for database %s", m_filename.ascii().data());
252
253    ASSERT(m_databaseAuthorizer);
254    m_sqliteDatabase.setAuthorizer(m_databaseAuthorizer);
255    m_sqliteDatabase.setBusyTimeout(maxSqliteBusyWaitTime);
256
257    String currentVersion;
258    {
259        MutexLocker locker(guidMutex());
260
261        GuidVersionMap::iterator entry = guidToVersionMap().find(m_guid);
262        if (entry != guidToVersionMap().end()) {
263            // Map null string to empty string (see updateGuidVersionMap()).
264            currentVersion = entry->second.isNull() ? String("") : entry->second;
265            LOG(StorageAPI, "Current cached version for guid %i is %s", m_guid, currentVersion.ascii().data());
266        } else {
267            LOG(StorageAPI, "No cached version for guid %i", m_guid);
268
269            if (!m_sqliteDatabase.tableExists(databaseInfoTableName())) {
270                m_new = true;
271
272                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);")) {
273                    LOG_ERROR("Unable to create table %s in database %s", databaseInfoTableName().ascii().data(), databaseDebugName().ascii().data());
274                    ec = INVALID_STATE_ERR;
275                    // Close the handle to the database file.
276                    m_sqliteDatabase.close();
277                    return false;
278                }
279            }
280
281            if (!getVersionFromDatabase(currentVersion)) {
282                LOG_ERROR("Failed to get current version from database %s", databaseDebugName().ascii().data());
283                ec = INVALID_STATE_ERR;
284                // Close the handle to the database file.
285                m_sqliteDatabase.close();
286                return false;
287            }
288            if (currentVersion.length()) {
289                LOG(StorageAPI, "Retrieved current version %s from database %s", currentVersion.ascii().data(), databaseDebugName().ascii().data());
290            } else if (!m_new || shouldSetVersionInNewDatabase) {
291                LOG(StorageAPI, "Setting version %s in database %s that was just created", m_expectedVersion.ascii().data(), databaseDebugName().ascii().data());
292                if (!setVersionInDatabase(m_expectedVersion)) {
293                    LOG_ERROR("Failed to set version %s in database %s", m_expectedVersion.ascii().data(), databaseDebugName().ascii().data());
294                    ec = INVALID_STATE_ERR;
295                    // Close the handle to the database file.
296                    m_sqliteDatabase.close();
297                    return false;
298                }
299                currentVersion = m_expectedVersion;
300            }
301
302            updateGuidVersionMap(m_guid, currentVersion);
303        }
304    }
305
306    if (currentVersion.isNull()) {
307        LOG(StorageAPI, "Database %s does not have its version set", databaseDebugName().ascii().data());
308        currentVersion = "";
309    }
310
311    // If the expected version isn't the empty string, ensure that the current database version we have matches that version. Otherwise, set an exception.
312    // If the expected version is the empty string, then we always return with whatever version of the database we have.
313    if ((!m_new || shouldSetVersionInNewDatabase) && m_expectedVersion.length() && m_expectedVersion != currentVersion) {
314        LOG(StorageAPI, "page expects version %s from database %s, which actually has version name %s - openDatabase() call will fail", m_expectedVersion.ascii().data(),
315            databaseDebugName().ascii().data(), currentVersion.ascii().data());
316        ec = INVALID_STATE_ERR;
317        // Close the handle to the database file.
318        m_sqliteDatabase.close();
319        return false;
320    }
321
322    m_opened = true;
323
324    return true;
325}
326
327ScriptExecutionContext* AbstractDatabase::scriptExecutionContext() const
328{
329    return m_scriptExecutionContext.get();
330}
331
332SecurityOrigin* AbstractDatabase::securityOrigin() const
333{
334    return m_contextThreadSecurityOrigin.get();
335}
336
337String AbstractDatabase::stringIdentifier() const
338{
339    // Return a deep copy for ref counting thread safety
340    return m_name.threadsafeCopy();
341}
342
343String AbstractDatabase::displayName() const
344{
345    // Return a deep copy for ref counting thread safety
346    return m_displayName.threadsafeCopy();
347}
348
349unsigned long AbstractDatabase::estimatedSize() const
350{
351    return m_estimatedSize;
352}
353
354String AbstractDatabase::fileName() const
355{
356    // Return a deep copy for ref counting thread safety
357    return m_filename.threadsafeCopy();
358}
359
360// static
361const String& AbstractDatabase::databaseVersionKey()
362{
363    DEFINE_STATIC_LOCAL(String, key, ("WebKitDatabaseVersionKey"));
364    return key;
365}
366
367bool AbstractDatabase::getVersionFromDatabase(String& version)
368{
369    DEFINE_STATIC_LOCAL(String, getVersionQuery, ("SELECT value FROM " + databaseInfoTableName() + " WHERE key = '" + databaseVersionKey() + "';"));
370
371    m_databaseAuthorizer->disable();
372
373    bool result = retrieveTextResultFromDatabase(m_sqliteDatabase, getVersionQuery.threadsafeCopy(), version);
374    if (!result)
375        LOG_ERROR("Failed to retrieve version from database %s", databaseDebugName().ascii().data());
376
377    m_databaseAuthorizer->enable();
378
379    return result;
380}
381
382bool AbstractDatabase::setVersionInDatabase(const String& version)
383{
384    // The INSERT will replace an existing entry for the database with the new version number, due to the UNIQUE ON CONFLICT REPLACE
385    // clause in the CREATE statement (see Database::performOpenAndVerify()).
386    DEFINE_STATIC_LOCAL(String, setVersionQuery, ("INSERT INTO " + databaseInfoTableName() + " (key, value) VALUES ('" + databaseVersionKey() + "', ?);"));
387
388    m_databaseAuthorizer->disable();
389
390    bool result = setTextValueInDatabase(m_sqliteDatabase, setVersionQuery.threadsafeCopy(), version);
391    if (!result)
392        LOG_ERROR("Failed to set version %s in database (%s)", version.ascii().data(), setVersionQuery.ascii().data());
393
394    m_databaseAuthorizer->enable();
395
396    return result;
397}
398
399bool AbstractDatabase::versionMatchesExpected() const
400{
401    if (!m_expectedVersion.isEmpty()) {
402        MutexLocker locker(guidMutex());
403        return m_expectedVersion == guidToVersionMap().get(m_guid);
404    }
405
406    return true;
407}
408
409void AbstractDatabase::setExpectedVersion(const String& version)
410{
411    m_expectedVersion = version.threadsafeCopy();
412    // Update the in memory database version map.
413    MutexLocker locker(guidMutex());
414    updateGuidVersionMap(m_guid, version);
415}
416
417void AbstractDatabase::disableAuthorizer()
418{
419    ASSERT(m_databaseAuthorizer);
420    m_databaseAuthorizer->disable();
421}
422
423void AbstractDatabase::enableAuthorizer()
424{
425    ASSERT(m_databaseAuthorizer);
426    m_databaseAuthorizer->enable();
427}
428
429void AbstractDatabase::setAuthorizerReadOnly()
430{
431    ASSERT(m_databaseAuthorizer);
432    m_databaseAuthorizer->setReadOnly();
433}
434
435void AbstractDatabase::setAuthorizerPermissions(int permissions)
436{
437    ASSERT(m_databaseAuthorizer);
438    m_databaseAuthorizer->setPermissions(permissions);
439}
440
441bool AbstractDatabase::lastActionChangedDatabase()
442{
443    ASSERT(m_databaseAuthorizer);
444    return m_databaseAuthorizer->lastActionChangedDatabase();
445}
446
447bool AbstractDatabase::lastActionWasInsert()
448{
449    ASSERT(m_databaseAuthorizer);
450    return m_databaseAuthorizer->lastActionWasInsert();
451}
452
453void AbstractDatabase::resetDeletes()
454{
455    ASSERT(m_databaseAuthorizer);
456    m_databaseAuthorizer->resetDeletes();
457}
458
459bool AbstractDatabase::hadDeletes()
460{
461    ASSERT(m_databaseAuthorizer);
462    return m_databaseAuthorizer->hadDeletes();
463}
464
465void AbstractDatabase::resetAuthorizer()
466{
467    if (m_databaseAuthorizer)
468        m_databaseAuthorizer->reset();
469}
470
471unsigned long long AbstractDatabase::maximumSize() const
472{
473    return DatabaseTracker::tracker().getMaxSizeForDatabase(this);
474}
475
476void AbstractDatabase::incrementalVacuumIfNeeded()
477{
478    int64_t freeSpaceSize = m_sqliteDatabase.freeSpaceSize();
479    int64_t totalSize = m_sqliteDatabase.totalSize();
480    if (totalSize <= 10 * freeSpaceSize)
481        m_sqliteDatabase.runIncrementalVacuumCommand();
482}
483
484void AbstractDatabase::interrupt()
485{
486    m_sqliteDatabase.interrupt();
487}
488
489bool AbstractDatabase::isInterrupted()
490{
491    MutexLocker locker(m_sqliteDatabase.databaseMutex());
492    return m_sqliteDatabase.isInterrupted();
493}
494
495} // namespace WebCore
496
497#endif // ENABLE(DATABASE)
498