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
47 namespace WebCore {
48
retrieveTextResultFromDatabase(SQLiteDatabase & db,const String & query,String & resultString)49 static 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
setTextValueInDatabase(SQLiteDatabase & db,const String & query,const String & value)73 static 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.
guidMutex()95 static 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
104 typedef HashMap<int, String> GuidVersionMap;
guidToVersionMap()105 static GuidVersionMap& guidToVersionMap()
106 {
107 DEFINE_STATIC_LOCAL(GuidVersionMap, map, ());
108 return map;
109 }
110
111 // NOTE: Caller must lock guidMutex().
updateGuidVersionMap(int guid,String newVersion)112 static 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
127 typedef HashMap<int, HashSet<AbstractDatabase*>*> GuidDatabaseMap;
guidToDatabaseMap()128 static GuidDatabaseMap& guidToDatabaseMap()
129 {
130 DEFINE_STATIC_LOCAL(GuidDatabaseMap, map, ());
131 return map;
132 }
133
guidForOriginAndName(const String & origin,const String & name)134 static 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
155 static bool isDatabaseAvailable = true;
156
isAvailable()157 bool AbstractDatabase::isAvailable()
158 {
159 return isDatabaseAvailable;
160 }
161
setIsAvailable(bool available)162 void AbstractDatabase::setIsAvailable(bool available)
163 {
164 isDatabaseAvailable = available;
165 }
166
167 // static
databaseInfoTableName()168 const String& AbstractDatabase::databaseInfoTableName()
169 {
170 DEFINE_STATIC_LOCAL(String, name, ("__WebKitDatabaseInfoTable__"));
171 return name;
172 }
173
AbstractDatabase(ScriptExecutionContext * context,const String & name,const String & expectedVersion,const String & displayName,unsigned long estimatedSize)174 AbstractDatabase::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
~AbstractDatabase()210 AbstractDatabase::~AbstractDatabase()
211 {
212 }
213
closeDatabase()214 void 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
version() const236 String AbstractDatabase::version() const
237 {
238 MutexLocker locker(guidMutex());
239 return guidToVersionMap().get(m_guid).threadsafeCopy();
240 }
241
242 static const int maxSqliteBusyWaitTime = 30000;
performOpenAndVerify(bool shouldSetVersionInNewDatabase,ExceptionCode & ec)243 bool 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
scriptExecutionContext() const327 ScriptExecutionContext* AbstractDatabase::scriptExecutionContext() const
328 {
329 return m_scriptExecutionContext.get();
330 }
331
securityOrigin() const332 SecurityOrigin* AbstractDatabase::securityOrigin() const
333 {
334 return m_contextThreadSecurityOrigin.get();
335 }
336
stringIdentifier() const337 String AbstractDatabase::stringIdentifier() const
338 {
339 // Return a deep copy for ref counting thread safety
340 return m_name.threadsafeCopy();
341 }
342
displayName() const343 String AbstractDatabase::displayName() const
344 {
345 // Return a deep copy for ref counting thread safety
346 return m_displayName.threadsafeCopy();
347 }
348
estimatedSize() const349 unsigned long AbstractDatabase::estimatedSize() const
350 {
351 return m_estimatedSize;
352 }
353
fileName() const354 String AbstractDatabase::fileName() const
355 {
356 // Return a deep copy for ref counting thread safety
357 return m_filename.threadsafeCopy();
358 }
359
360 // static
databaseVersionKey()361 const String& AbstractDatabase::databaseVersionKey()
362 {
363 DEFINE_STATIC_LOCAL(String, key, ("WebKitDatabaseVersionKey"));
364 return key;
365 }
366
getVersionFromDatabase(String & version)367 bool 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
setVersionInDatabase(const String & version)382 bool 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
versionMatchesExpected() const399 bool 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
setExpectedVersion(const String & version)409 void 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
disableAuthorizer()417 void AbstractDatabase::disableAuthorizer()
418 {
419 ASSERT(m_databaseAuthorizer);
420 m_databaseAuthorizer->disable();
421 }
422
enableAuthorizer()423 void AbstractDatabase::enableAuthorizer()
424 {
425 ASSERT(m_databaseAuthorizer);
426 m_databaseAuthorizer->enable();
427 }
428
setAuthorizerReadOnly()429 void AbstractDatabase::setAuthorizerReadOnly()
430 {
431 ASSERT(m_databaseAuthorizer);
432 m_databaseAuthorizer->setReadOnly();
433 }
434
setAuthorizerPermissions(int permissions)435 void AbstractDatabase::setAuthorizerPermissions(int permissions)
436 {
437 ASSERT(m_databaseAuthorizer);
438 m_databaseAuthorizer->setPermissions(permissions);
439 }
440
lastActionChangedDatabase()441 bool AbstractDatabase::lastActionChangedDatabase()
442 {
443 ASSERT(m_databaseAuthorizer);
444 return m_databaseAuthorizer->lastActionChangedDatabase();
445 }
446
lastActionWasInsert()447 bool AbstractDatabase::lastActionWasInsert()
448 {
449 ASSERT(m_databaseAuthorizer);
450 return m_databaseAuthorizer->lastActionWasInsert();
451 }
452
resetDeletes()453 void AbstractDatabase::resetDeletes()
454 {
455 ASSERT(m_databaseAuthorizer);
456 m_databaseAuthorizer->resetDeletes();
457 }
458
hadDeletes()459 bool AbstractDatabase::hadDeletes()
460 {
461 ASSERT(m_databaseAuthorizer);
462 return m_databaseAuthorizer->hadDeletes();
463 }
464
resetAuthorizer()465 void AbstractDatabase::resetAuthorizer()
466 {
467 if (m_databaseAuthorizer)
468 m_databaseAuthorizer->reset();
469 }
470
maximumSize() const471 unsigned long long AbstractDatabase::maximumSize() const
472 {
473 return DatabaseTracker::tracker().getMaxSizeForDatabase(this);
474 }
475
incrementalVacuumIfNeeded()476 void 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
interrupt()484 void AbstractDatabase::interrupt()
485 {
486 m_sqliteDatabase.interrupt();
487 }
488
isInterrupted()489 bool 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