2 * Copyright (C) 2007 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
29 #include "DatabaseTracker.h"
32 #include "DatabaseTrackerClient.h"
34 #include "FileSystem.h"
36 #include "SecurityOriginData.h"
37 #include "SQLiteStatement.h"
41 // HTML5 SQL Storage spec suggests 5MB as the default quota per origin
42 static const unsigned DefaultOriginQuota = 5242880;
44 struct SecurityOriginDataHash {
45 static unsigned hash(const SecurityOriginData& data)
47 unsigned hashCodes[3] = {
48 data.protocol().impl() ? data.protocol().impl()->hash() : 0,
49 data.host().impl() ? data.host().impl()->hash() : 0,
52 return StringImpl::computeHash(reinterpret_cast<UChar*>(hashCodes), 3 * sizeof(unsigned) / sizeof(UChar));
55 static bool equal(const SecurityOriginData& a, const SecurityOriginData& b)
60 static const bool safeToCompareToEmptyOrDeleted = true;
63 struct SecurityOriginDataTraits : WTF::GenericHashTraits<SecurityOriginData> {
64 static const SecurityOriginData& deletedValue()
66 // Okay deleted value because file: protocols should always have port 0
67 static SecurityOriginData key("file", "", 1);
70 static const SecurityOriginData& emptyValue()
72 // Okay empty value because file: protocols should always have port 0
73 static SecurityOriginData key("file", "", 2);
78 DatabaseTracker& DatabaseTracker::tracker()
80 static DatabaseTracker tracker;
85 DatabaseTracker::DatabaseTracker()
86 : m_defaultQuota(DefaultOriginQuota)
91 void DatabaseTracker::setDatabasePath(const String& path)
93 m_databasePath = path;
94 openTrackerDatabase();
97 const String& DatabaseTracker::databasePath()
99 return m_databasePath;
102 void DatabaseTracker::openTrackerDatabase()
104 ASSERT(!m_database.isOpen());
106 makeAllDirectories(m_databasePath);
107 String databasePath = pathByAppendingComponent(m_databasePath, "Databases.db");
109 if (!m_database.open(databasePath)) {
110 // FIXME: What do do here?
113 if (!m_database.tableExists("Origins")) {
114 if (!m_database.executeCommand("CREATE TABLE Origins (origin TEXT UNIQUE ON CONFLICT REPLACE, quota INTEGER NOT NULL ON CONFLICT FAIL);")) {
119 if (!m_database.tableExists("Databases")) {
120 if (!m_database.executeCommand("CREATE TABLE Databases (guid INTEGER PRIMARY KEY AUTOINCREMENT, origin TEXT, name TEXT, displayName TEXT, estimatedSize INTEGER, path TEXT);")) {
126 bool DatabaseTracker::canEstablishDatabase(Document* document, const String& name, const String& displayName, unsigned long estimatedSize)
128 SecurityOriginData originData = document->securityOrigin().securityOriginData();
130 // If this origin has no databases yet, establish an entry in the tracker database with the default quota
131 if (!hasEntryForOrigin(originData))
132 establishEntryForOrigin(originData);
134 // If the new database will fit as-is, allow its creation
135 unsigned long long usage = usageForOrigin(originData);
136 if (usage + estimatedSize < quotaForOrigin(originData))
139 // Otherwise, ask the UI Delegate for a new quota
141 if (!(page = document->page()))
144 unsigned long long newQuota = page->chrome()->requestQuotaIncreaseForNewDatabase(document->frame(), originData, displayName, estimatedSize);
145 setQuota(originData, newQuota);
147 return usage + estimatedSize < newQuota;
150 bool DatabaseTracker::hasEntryForOrigin(const SecurityOriginData& origin)
153 return m_originQuotaMap->contains(origin);
156 void DatabaseTracker::establishEntryForOrigin(const SecurityOriginData& origin)
158 ASSERT(!hasEntryForOrigin(origin));
160 SQLiteStatement statement(m_database, "INSERT INTO Origins VALUES (?, ?)");
161 if (statement.prepare() != SQLResultOk) {
162 LOG_ERROR("Unable to establish origin %s in the tracker", origin.stringIdentifier().ascii().data());
166 statement.bindText(1, origin.stringIdentifier());
167 statement.bindInt64(2, m_defaultQuota);
169 if (statement.step() != SQLResultDone) {
170 LOG_ERROR("Unable to establish origin %s in the tracker", origin.stringIdentifier().ascii().data());
175 m_originQuotaMap->set(origin, m_defaultQuota);
178 m_client->dispatchDidModifyOrigin(origin);
181 String DatabaseTracker::fullPathForDatabase(const SecurityOriginData& origin, const String& name, bool createIfNotExists)
183 String originIdentifier = origin.stringIdentifier();
184 String originPath = pathByAppendingComponent(m_databasePath, originIdentifier);
186 // Make sure the path for this SecurityOrigin exists
187 if (createIfNotExists && !makeAllDirectories(originPath))
190 // See if we have a path for this database yet
191 SQLiteStatement statement(m_database, "SELECT path FROM Databases WHERE origin=? AND name=?;");
193 if (statement.prepare() != SQLResultOk)
196 statement.bindText(1, originIdentifier);
197 statement.bindText(2, name);
199 int result = statement.step();
201 if (result == SQLResultRow)
202 return pathByAppendingComponent(originPath, statement.getColumnText16(0));
203 if (!createIfNotExists)
206 if (result != SQLResultDone) {
207 LOG_ERROR("Failed to retrieve filename from Database Tracker for origin %s, name %s", origin.stringIdentifier().ascii().data(), name.ascii().data());
210 statement.finalize();
212 SQLiteStatement sequenceStatement(m_database, "SELECT seq FROM sqlite_sequence WHERE name='Databases';");
214 // FIXME: More informative error handling here, even though these steps should never fail
215 if (sequenceStatement.prepare() != SQLResultOk)
217 result = sequenceStatement.step();
219 // This has a range of 2^63 and starts at 0 for every time a user resets Safari -
220 // I can't imagine it'd over overflow
222 if (result == SQLResultRow) {
223 seq = sequenceStatement.getColumnInt64(0);
224 } else if (result != SQLResultDone)
226 sequenceStatement.finalize();
231 filename = pathByAppendingComponent(originPath, String::format("%016llx.db", seq));
232 } while (fileExists(filename));
234 if (!addDatabase(origin, name, String::format("%016llx.db", seq)))
240 void DatabaseTracker::populateOrigins()
242 if (m_originQuotaMap)
245 m_originQuotaMap.set(new HashMap<SecurityOriginData, unsigned long long, SecurityOriginDataHash, SecurityOriginDataTraits>);
247 if (!m_database.isOpen())
250 SQLiteStatement statement(m_database, "SELECT origin, quota FROM Origins");
252 if (statement.prepare() != SQLResultOk)
256 while ((result = statement.step()) == SQLResultRow)
257 m_originQuotaMap->set(statement.getColumnText16(0), statement.getColumnInt64(1));
259 if (result != SQLResultDone)
260 LOG_ERROR("Failed to read in all origins from the database");
265 void DatabaseTracker::origins(Vector<SecurityOriginData>& result)
267 if (!m_originQuotaMap)
270 copyKeysToVector(*(m_originQuotaMap.get()), result);
273 bool DatabaseTracker::databaseNamesForOrigin(const SecurityOriginData& origin, Vector<String>& resultVector)
275 if (!m_database.isOpen())
278 SQLiteStatement statement(m_database, "SELECT name FROM Databases where origin=?;");
280 if (statement.prepare() != SQLResultOk)
283 statement.bindText(1, origin.stringIdentifier());
286 while ((result = statement.step()) == SQLResultRow)
287 resultVector.append(statement.getColumnText16(0));
289 if (result != SQLResultDone) {
290 LOG_ERROR("Failed to retrieve all database names for origin %s", origin.stringIdentifier().ascii().data());
297 DatabaseDetails DatabaseTracker::detailsForNameAndOrigin(const String& name, const SecurityOriginData& origin)
299 String originIdentifier = origin.stringIdentifier();
301 SQLiteStatement statement(m_database, "SELECT displayName, estimatedSize FROM Databases WHERE origin=? AND name=?");
302 if (statement.prepare() != SQLResultOk)
303 return DatabaseDetails();
305 statement.bindText(1, originIdentifier);
306 statement.bindText(2, name);
308 int result = statement.step();
309 if (result == SQLResultDone)
310 return DatabaseDetails();
312 if (result != SQLResultRow) {
313 LOG_ERROR("Error retrieving details for database %s in origin %s from tracker database", name.ascii().data(), originIdentifier.ascii().data());
314 return DatabaseDetails();
317 return DatabaseDetails(name, statement.getColumnText(0), statement.getColumnInt64(1), usageForDatabase(name, origin));
320 void DatabaseTracker::setDatabaseDetails(const SecurityOriginData& origin, const String& name, const String& displayName, unsigned long estimatedSize)
322 String originIdentifier = origin.stringIdentifier();
325 SQLiteStatement statement(m_database, "SELECT guid FROM Databases WHERE origin=? AND name=?");
326 if (statement.prepare() != SQLResultOk)
329 statement.bindText(1, originIdentifier);
330 statement.bindText(2, name);
332 int result = statement.step();
333 if (result == SQLResultRow)
334 guid = statement.getColumnInt64(0);
335 statement.finalize();
338 if (result != SQLResultDone)
339 LOG_ERROR("Error to determing existence of database %s in origin %s in tracker database", name.ascii().data(), originIdentifier.ascii().data());
341 // This case should never occur - we should never be setting database details for a database that doesn't already exist in the tracker
342 // But since the tracker file is an external resource not under complete control of our code, it's somewhat invalid to make this an ASSERT case
343 // So we'll print an error instead
344 LOG_ERROR("Could not retrieve guid for database %s in origin %s from the tracker database - it is invalid to set database details on a database that doesn't already exist in the tracker",
345 name.ascii().data(), originIdentifier.ascii().data());
350 SQLiteStatement updateStatement(m_database, "UPDATE Databases SET displayName=?, estimatedSize=? WHERE guid=?");
351 if (updateStatement.prepare() != SQLResultOk)
354 updateStatement.bindText(1, displayName);
355 updateStatement.bindInt64(2, estimatedSize);
356 updateStatement.bindInt64(3, guid);
358 if (updateStatement.step() != SQLResultDone) {
359 LOG_ERROR("Failed to update details for database %s in origin %s", name.ascii().data(), originIdentifier.ascii().data());
364 m_client->dispatchDidModifyDatabase(origin, name);
367 unsigned long long DatabaseTracker::usageForDatabase(const String& name, const SecurityOriginData& origin)
369 String path = fullPathForDatabase(origin, name, false);
374 return fileSize(path, size) ? size : 0;
377 unsigned long long DatabaseTracker::usageForOrigin(const SecurityOriginData& origin)
379 Vector<String> names;
380 databaseNamesForOrigin(origin, names);
382 unsigned long long result = 0;
383 for (unsigned i = 0; i < names.size(); ++i)
384 result += usageForDatabase(names[i], origin);
389 unsigned long long DatabaseTracker::quotaForOrigin(const SecurityOriginData& origin)
392 return m_originQuotaMap->get(origin);
395 void DatabaseTracker::setQuota(const SecurityOriginData& origin, unsigned long long quota)
398 if (!m_originQuotaMap->contains(origin))
399 establishEntryForOrigin(origin);
401 m_originQuotaMap->set(origin, quota);
403 SQLiteStatement statement(m_database, "UPDATE Origins SET quota=? WHERE origin=?");
405 bool error = statement.prepare() != SQLResultOk;
407 statement.bindInt64(1, quota);
408 statement.bindText(2, origin.stringIdentifier());
410 error = !statement.executeCommand();
414 LOG_ERROR("Failed to set quota %llu in tracker database for origin %s", quota, origin.stringIdentifier().ascii().data());
417 m_client->dispatchDidModifyOrigin(origin);
420 bool DatabaseTracker::addDatabase(const SecurityOriginData& origin, const String& name, const String& path)
422 if (!m_database.isOpen())
425 // New database should never be added until the origin has been established
426 ASSERT(hasEntryForOrigin(origin));
428 SQLiteStatement statement(m_database, "INSERT INTO Databases (origin, name, path) VALUES (?, ?, ?);");
430 if (statement.prepare() != SQLResultOk)
433 statement.bindText(1, origin.stringIdentifier());
434 statement.bindText(2, name);
435 statement.bindText(3, path);
437 if (!statement.executeCommand()) {
438 LOG_ERROR("Failed to add database %s to origin %s: %s\n", name.ascii().data(), origin.stringIdentifier().ascii().data(), statement.lastErrorMsg());
443 m_client->dispatchDidModifyOrigin(origin);
448 void DatabaseTracker::deleteAllDatabases()
452 HashMap<SecurityOriginData, unsigned long long, SecurityOriginDataHash, SecurityOriginDataTraits>::const_iterator iter = m_originQuotaMap->begin();
453 HashMap<SecurityOriginData, unsigned long long, SecurityOriginDataHash, SecurityOriginDataTraits>::const_iterator end = m_originQuotaMap->end();
455 for (; iter != end; ++iter)
456 deleteDatabasesWithOrigin(iter->first);
459 void DatabaseTracker::deleteDatabasesWithOrigin(const SecurityOriginData& origin)
461 Vector<String> databaseNames;
462 if (!databaseNamesForOrigin(origin, databaseNames)) {
463 LOG_ERROR("Unable to retrieve list of database names for origin %s", origin.stringIdentifier().ascii().data());
467 for (unsigned i = 0; i < databaseNames.size(); ++i) {
468 if (!deleteDatabaseFile(origin, databaseNames[i])) {
469 LOG_ERROR("Unable to delete file for database %s in origin %s", databaseNames[i].ascii().data(), origin.stringIdentifier().ascii().data());
474 SQLiteStatement statement(m_database, "DELETE FROM Databases WHERE origin=?");
475 if (statement.prepare() != SQLResultOk) {
476 LOG_ERROR("Unable to prepare deletion of databases from origin %s from tracker", origin.stringIdentifier().ascii().data());
480 statement.bindText(1, origin.stringIdentifier());
482 if (!statement.executeCommand()) {
483 LOG_ERROR("Unable to execute deletion of databases from origin %s from tracker", origin.stringIdentifier().ascii().data());
488 m_client->dispatchDidModifyOrigin(origin);
489 for (unsigned i = 0; i < databaseNames.size(); ++i)
490 m_client->dispatchDidModifyDatabase(origin, databaseNames[i]);
494 void DatabaseTracker::deleteDatabase(const SecurityOriginData& origin, const String& name)
496 if (!deleteDatabaseFile(origin, name)) {
497 LOG_ERROR("Unable to delete file for database %s in origin %s", name.ascii().data(), origin.stringIdentifier().ascii().data());
501 SQLiteStatement statement(m_database, "DELETE FROM Databases WHERE origin=? AND name=?");
502 if (statement.prepare() != SQLResultOk) {
503 LOG_ERROR("Unable to prepare deletion of database %s from origin %s from tracker", name.ascii().data(), origin.stringIdentifier().ascii().data());
507 statement.bindText(1, origin.stringIdentifier());
508 statement.bindText(2, name);
510 if (!statement.executeCommand()) {
511 LOG_ERROR("Unable to execute deletion of database %s from origin %s from tracker", name.ascii().data(), origin.stringIdentifier().ascii().data());
516 m_client->dispatchDidModifyOrigin(origin);
517 m_client->dispatchDidModifyDatabase(origin, name);
521 bool DatabaseTracker::deleteDatabaseFile(const SecurityOriginData& origin, const String& name)
523 String fullPath = fullPathForDatabase(origin, name, false);
524 if (fullPath.isEmpty())
527 return deleteFile(fullPath);
530 void DatabaseTracker::setClient(DatabaseTrackerClient* client)
535 void DatabaseTracker::setDefaultOriginQuota(unsigned long long quota)
537 m_defaultQuota = quota;
540 unsigned long long DatabaseTracker::defaultOriginQuota() const
542 return m_defaultQuota;
545 } // namespace WebCore