Reviewed by Brady
[WebKit-https.git] / WebCore / storage / DatabaseTracker.cpp
1 /*
2  * Copyright (C) 2007 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 #include "config.h"
29 #include "DatabaseTracker.h"
30
31 #include "Database.h"
32 #include "DatabaseTrackerClient.h"
33 #include "Document.h"
34 #include "FileSystem.h"
35 #include "Page.h"
36 #include "SecurityOriginData.h"
37 #include "SQLiteStatement.h"
38
39 namespace WebCore {
40
41 // HTML5 SQL Storage spec suggests 5MB as the default quota per origin
42 static const unsigned DefaultOriginQuota = 5242880;
43
44 struct SecurityOriginDataHash {
45     static unsigned hash(const SecurityOriginData& data)
46     {
47         unsigned hashCodes[3] = {
48             data.protocol().impl() ? data.protocol().impl()->hash() : 0,
49             data.host().impl() ? data.host().impl()->hash() : 0,
50             data.port()
51         };
52         return StringImpl::computeHash(reinterpret_cast<UChar*>(hashCodes), 3 * sizeof(unsigned) / sizeof(UChar));
53     }
54          
55     static bool equal(const SecurityOriginData& a, const SecurityOriginData& b)
56     {
57         return a == b;
58     }
59
60     static const bool safeToCompareToEmptyOrDeleted = true;
61 };
62
63 struct SecurityOriginDataTraits : WTF::GenericHashTraits<SecurityOriginData> {
64     static const SecurityOriginData& deletedValue()
65     {
66         // Okay deleted value because file: protocols should always have port 0
67         static SecurityOriginData key("file", "", 1);
68         return key;
69     }
70     static const SecurityOriginData& emptyValue()
71     {
72         // Okay empty value because file: protocols should always have port 0
73         static SecurityOriginData key("file", "", 2);
74         return key;
75     }
76 };
77
78 DatabaseTracker& DatabaseTracker::tracker()
79 {
80     static DatabaseTracker tracker;
81
82     return tracker;
83 }
84
85 DatabaseTracker::DatabaseTracker()
86     : m_defaultQuota(DefaultOriginQuota)
87     , m_client(0)
88 {
89 }
90
91 void DatabaseTracker::setDatabasePath(const String& path)
92 {        
93     m_databasePath = path;
94     openTrackerDatabase();
95 }
96
97 const String& DatabaseTracker::databasePath()
98 {
99     return m_databasePath;
100 }
101
102 void DatabaseTracker::openTrackerDatabase()
103 {
104     ASSERT(!m_database.isOpen());
105     
106     makeAllDirectories(m_databasePath);
107     String databasePath = pathByAppendingComponent(m_databasePath, "Databases.db");
108
109     if (!m_database.open(databasePath)) {
110         // FIXME: What do do here?
111         return;
112     }
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);")) {
115             // FIXME: and here
116         }
117     }
118
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);")) {
121             // FIXME: and here
122         }
123     }
124 }
125
126 bool DatabaseTracker::canEstablishDatabase(Document* document, const String& name, const String& displayName, unsigned long estimatedSize)
127 {
128     SecurityOriginData originData = document->securityOrigin().securityOriginData();
129     
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);
133     
134     // If a database already exists, you can always establish a handle to it
135     if (hasEntryForDatabase(originData, name))
136         return true;
137         
138     // If the new database will fit as-is, allow its creation
139     unsigned long long usage = usageForOrigin(originData);
140     if (usage + estimatedSize < quotaForOrigin(originData))
141         return true;
142     
143     // Otherwise, ask the UI Delegate for a new quota
144     Page* page;
145     if (!(page = document->page()))
146         return false;
147     
148     // If no displayName was specified, pass the standard (required) name instead
149     unsigned long long newQuota = page->chrome()->requestQuotaIncreaseForNewDatabase(document->frame(), originData, displayName.length() > 0 ? displayName : name, estimatedSize);
150     setQuota(originData, newQuota);
151     
152     return usage + estimatedSize <= newQuota;
153 }
154
155 bool DatabaseTracker::hasEntryForOrigin(const SecurityOriginData& origin)
156 {
157     populateOrigins();
158     return m_originQuotaMap->contains(origin);
159 }
160
161 bool DatabaseTracker::hasEntryForDatabase(const SecurityOriginData& origin, const String& databaseIdentifier)
162 {
163     SQLiteStatement statement(m_database, "SELECT guid FROM Databases WHERE origin=? AND name=?;");
164
165     if (statement.prepare() != SQLResultOk)
166         return false;
167
168     statement.bindText(1, origin.stringIdentifier());
169     statement.bindText(2, databaseIdentifier);
170
171     return statement.step() == SQLResultRow;
172 }
173
174 void DatabaseTracker::establishEntryForOrigin(const SecurityOriginData& origin)
175 {
176     ASSERT(!hasEntryForOrigin(origin));
177     
178     SQLiteStatement statement(m_database, "INSERT INTO Origins VALUES (?, ?)");
179     if (statement.prepare() != SQLResultOk) {
180         LOG_ERROR("Unable to establish origin %s in the tracker", origin.stringIdentifier().ascii().data());
181         return;
182     }
183         
184     statement.bindText(1, origin.stringIdentifier());
185     statement.bindInt64(2, m_defaultQuota);
186     
187     if (statement.step() != SQLResultDone) {
188         LOG_ERROR("Unable to establish origin %s in the tracker", origin.stringIdentifier().ascii().data());
189         return;
190     }
191
192     populateOrigins();
193     m_originQuotaMap->set(origin, m_defaultQuota);
194     
195     if (m_client)
196         m_client->dispatchDidModifyOrigin(origin);
197 }
198
199 String DatabaseTracker::fullPathForDatabase(const SecurityOriginData& origin, const String& name, bool createIfNotExists)
200 {
201     String originIdentifier = origin.stringIdentifier();
202     String originPath = pathByAppendingComponent(m_databasePath, originIdentifier);
203     
204     // Make sure the path for this SecurityOrigin exists
205     if (createIfNotExists && !makeAllDirectories(originPath))
206         return "";
207     
208     // See if we have a path for this database yet
209     SQLiteStatement statement(m_database, "SELECT path FROM Databases WHERE origin=? AND name=?;");
210
211     if (statement.prepare() != SQLResultOk)
212         return "";
213
214     statement.bindText(1, originIdentifier);
215     statement.bindText(2, name);
216
217     int result = statement.step();
218
219     if (result == SQLResultRow)
220         return pathByAppendingComponent(originPath, statement.getColumnText16(0));
221     if (!createIfNotExists)
222         return "";
223         
224     if (result != SQLResultDone) {
225         LOG_ERROR("Failed to retrieve filename from Database Tracker for origin %s, name %s", origin.stringIdentifier().ascii().data(), name.ascii().data());
226         return "";
227     }
228     statement.finalize();
229     
230     SQLiteStatement sequenceStatement(m_database, "SELECT seq FROM sqlite_sequence WHERE name='Databases';");
231
232     // FIXME: More informative error handling here, even though these steps should never fail
233     if (sequenceStatement.prepare() != SQLResultOk)
234         return "";
235     result = sequenceStatement.step();
236
237     // This has a range of 2^63 and starts at 0 for every time a user resets Safari -
238     // I can't imagine it'd over overflow
239     int64_t seq = 0;
240     if (result == SQLResultRow) {
241         seq = sequenceStatement.getColumnInt64(0);
242     } else if (result != SQLResultDone)
243         return "";
244     sequenceStatement.finalize();
245
246     String filename;
247     do {
248         ++seq;
249         filename = pathByAppendingComponent(originPath, String::format("%016llx.db", seq));
250     } while (fileExists(filename));
251
252     if (!addDatabase(origin, name, String::format("%016llx.db", seq)))
253         return "";
254
255     return filename;
256 }
257
258 void DatabaseTracker::populateOrigins()
259 {
260     if (m_originQuotaMap)
261         return;
262
263     m_originQuotaMap.set(new HashMap<SecurityOriginData, unsigned long long, SecurityOriginDataHash, SecurityOriginDataTraits>);
264
265     if (!m_database.isOpen())
266         return;
267
268     SQLiteStatement statement(m_database, "SELECT origin, quota FROM Origins");
269
270     if (statement.prepare() != SQLResultOk)
271         return;
272
273     int result;
274     while ((result = statement.step()) == SQLResultRow)
275         m_originQuotaMap->set(statement.getColumnText16(0), statement.getColumnInt64(1));
276
277     if (result != SQLResultDone)
278         LOG_ERROR("Failed to read in all origins from the database");
279
280     return;
281 }
282
283 void DatabaseTracker::origins(Vector<SecurityOriginData>& result)
284 {
285     if (!m_originQuotaMap)
286         populateOrigins();
287
288     copyKeysToVector(*(m_originQuotaMap.get()), result);
289 }
290
291 bool DatabaseTracker::databaseNamesForOrigin(const SecurityOriginData& origin, Vector<String>& resultVector)
292 {
293     if (!m_database.isOpen())
294         return false;
295
296     SQLiteStatement statement(m_database, "SELECT name FROM Databases where origin=?;");
297
298     if (statement.prepare() != SQLResultOk)
299         return false;
300
301     statement.bindText(1, origin.stringIdentifier());
302
303     int result;
304     while ((result = statement.step()) == SQLResultRow)
305         resultVector.append(statement.getColumnText16(0));
306
307     if (result != SQLResultDone) {
308         LOG_ERROR("Failed to retrieve all database names for origin %s", origin.stringIdentifier().ascii().data());
309         return false;
310     }
311
312     return true;
313 }
314
315 DatabaseDetails DatabaseTracker::detailsForNameAndOrigin(const String& name, const SecurityOriginData& origin)
316 {
317     String originIdentifier = origin.stringIdentifier();
318         
319     SQLiteStatement statement(m_database, "SELECT displayName, estimatedSize FROM Databases WHERE origin=? AND name=?");
320     if (statement.prepare() != SQLResultOk)
321         return DatabaseDetails();
322    
323     statement.bindText(1, originIdentifier);
324     statement.bindText(2, name);
325     
326     int result = statement.step();
327     if (result == SQLResultDone)
328         return DatabaseDetails();
329     
330     if (result != SQLResultRow) {
331         LOG_ERROR("Error retrieving details for database %s in origin %s from tracker database", name.ascii().data(), originIdentifier.ascii().data());
332         return DatabaseDetails();
333     }
334     
335     return DatabaseDetails(name, statement.getColumnText(0), statement.getColumnInt64(1), usageForDatabase(name, origin));
336 }
337
338 void DatabaseTracker::setDatabaseDetails(const SecurityOriginData& origin, const String& name, const String& displayName, unsigned long estimatedSize)
339 {
340     String originIdentifier = origin.stringIdentifier();
341     int64_t guid = 0;
342     
343     SQLiteStatement statement(m_database, "SELECT guid FROM Databases WHERE origin=? AND name=?");
344     if (statement.prepare() != SQLResultOk)
345         return;
346         
347     statement.bindText(1, originIdentifier);
348     statement.bindText(2, name);
349     
350     int result = statement.step();
351     if (result == SQLResultRow)
352         guid = statement.getColumnInt64(0);
353     statement.finalize();
354
355     if (guid == 0) {
356         if (result != SQLResultDone)
357             LOG_ERROR("Error to determing existence of database %s in origin %s in tracker database", name.ascii().data(), originIdentifier.ascii().data());
358         else {
359             // This case should never occur - we should never be setting database details for a database that doesn't already exist in the tracker
360             // 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
361             // So we'll print an error instead
362             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",
363                        name.ascii().data(), originIdentifier.ascii().data());
364         }
365         return;
366     }
367     
368     SQLiteStatement updateStatement(m_database, "UPDATE Databases SET displayName=?, estimatedSize=? WHERE guid=?");
369     if (updateStatement.prepare() != SQLResultOk)
370         return;
371     
372     updateStatement.bindText(1, displayName);
373     updateStatement.bindInt64(2, estimatedSize);
374     updateStatement.bindInt64(3, guid);
375     
376     if (updateStatement.step() != SQLResultDone) {
377         LOG_ERROR("Failed to update details for database %s in origin %s", name.ascii().data(), originIdentifier.ascii().data());
378         return;  
379     }
380     
381     if (m_client)
382         m_client->dispatchDidModifyDatabase(origin, name);
383 }
384
385 unsigned long long DatabaseTracker::usageForDatabase(const String& name, const SecurityOriginData& origin)
386 {
387     String path = fullPathForDatabase(origin, name, false);
388     if (path.isEmpty())
389         return 0;
390         
391     long long size;
392     return fileSize(path, size) ? size : 0;
393 }
394
395 unsigned long long DatabaseTracker::usageForOrigin(const SecurityOriginData& origin)
396 {
397     Vector<String> names;
398     databaseNamesForOrigin(origin, names);
399     
400     unsigned long long result = 0;
401     for (unsigned i = 0; i < names.size(); ++i)
402         result += usageForDatabase(names[i], origin);
403         
404     return result;
405 }
406
407 unsigned long long DatabaseTracker::quotaForOrigin(const SecurityOriginData& origin)
408 {
409     populateOrigins();
410     return m_originQuotaMap->get(origin);
411 }
412
413 void DatabaseTracker::setQuota(const SecurityOriginData& origin, unsigned long long quota)
414 {
415     populateOrigins();
416     if (!m_originQuotaMap->contains(origin))
417         establishEntryForOrigin(origin);
418     
419     m_originQuotaMap->set(origin, quota);
420     
421     SQLiteStatement statement(m_database, "UPDATE Origins SET quota=? WHERE origin=?");
422     
423     bool error = statement.prepare() != SQLResultOk;
424     if (!error) {
425         statement.bindInt64(1, quota);
426         statement.bindText(2, origin.stringIdentifier());
427         
428         error = !statement.executeCommand();
429     }
430         
431     if (error)
432         LOG_ERROR("Failed to set quota %llu in tracker database for origin %s", quota, origin.stringIdentifier().ascii().data());
433     
434     if (m_client)
435         m_client->dispatchDidModifyOrigin(origin);
436 }
437     
438 bool DatabaseTracker::addDatabase(const SecurityOriginData& origin, const String& name, const String& path)
439 {
440     if (!m_database.isOpen())
441         return false;
442         
443     // New database should never be added until the origin has been established
444     ASSERT(hasEntryForOrigin(origin));
445
446     SQLiteStatement statement(m_database, "INSERT INTO Databases (origin, name, path) VALUES (?, ?, ?);");
447
448     if (statement.prepare() != SQLResultOk)
449         return false;
450
451     statement.bindText(1, origin.stringIdentifier());
452     statement.bindText(2, name);
453     statement.bindText(3, path);
454
455     if (!statement.executeCommand()) {
456         LOG_ERROR("Failed to add database %s to origin %s: %s\n", name.ascii().data(), origin.stringIdentifier().ascii().data(), statement.lastErrorMsg());
457         return false;
458     }
459     
460     if (m_client)
461         m_client->dispatchDidModifyOrigin(origin);
462     
463     return true;
464 }
465
466 void DatabaseTracker::deleteAllDatabases()
467 {
468     populateOrigins();
469     
470     HashMap<SecurityOriginData, unsigned long long, SecurityOriginDataHash, SecurityOriginDataTraits>::const_iterator iter = m_originQuotaMap->begin();
471     HashMap<SecurityOriginData, unsigned long long, SecurityOriginDataHash, SecurityOriginDataTraits>::const_iterator end = m_originQuotaMap->end();
472
473     for (; iter != end; ++iter)
474         deleteDatabasesWithOrigin(iter->first);
475 }
476
477 void DatabaseTracker::deleteDatabasesWithOrigin(const SecurityOriginData& origin)
478 {
479     Vector<String> databaseNames;
480     if (!databaseNamesForOrigin(origin, databaseNames)) {
481         LOG_ERROR("Unable to retrieve list of database names for origin %s", origin.stringIdentifier().ascii().data());
482         return;
483     }
484     
485     for (unsigned i = 0; i < databaseNames.size(); ++i) {
486         if (!deleteDatabaseFile(origin, databaseNames[i])) {
487             LOG_ERROR("Unable to delete file for database %s in origin %s", databaseNames[i].ascii().data(), origin.stringIdentifier().ascii().data());
488             return;
489         }
490     }
491     
492     SQLiteStatement statement(m_database, "DELETE FROM Databases WHERE origin=?");
493     if (statement.prepare() != SQLResultOk) {
494         LOG_ERROR("Unable to prepare deletion of databases from origin %s from tracker", origin.stringIdentifier().ascii().data());
495         return;
496     }
497         
498     statement.bindText(1, origin.stringIdentifier());
499     
500     if (!statement.executeCommand()) {
501         LOG_ERROR("Unable to execute deletion of databases from origin %s from tracker", origin.stringIdentifier().ascii().data());
502         return;
503     }
504     
505     if (m_client) {
506         m_client->dispatchDidModifyOrigin(origin);
507         for (unsigned i = 0; i < databaseNames.size(); ++i)
508             m_client->dispatchDidModifyDatabase(origin, databaseNames[i]);
509     }
510 }
511
512 void DatabaseTracker::deleteDatabase(const SecurityOriginData& origin, const String& name)
513 {
514     if (!deleteDatabaseFile(origin, name)) {
515         LOG_ERROR("Unable to delete file for database %s in origin %s", name.ascii().data(), origin.stringIdentifier().ascii().data());
516         return;
517     }
518     
519     SQLiteStatement statement(m_database, "DELETE FROM Databases WHERE origin=? AND name=?");
520     if (statement.prepare() != SQLResultOk) {
521         LOG_ERROR("Unable to prepare deletion of database %s from origin %s from tracker", name.ascii().data(), origin.stringIdentifier().ascii().data());
522         return;
523     }
524         
525     statement.bindText(1, origin.stringIdentifier());
526     statement.bindText(2, name);
527     
528     if (!statement.executeCommand()) {
529         LOG_ERROR("Unable to execute deletion of database %s from origin %s from tracker", name.ascii().data(), origin.stringIdentifier().ascii().data());
530         return;
531     }
532     
533     if (m_client) {
534         m_client->dispatchDidModifyOrigin(origin);
535         m_client->dispatchDidModifyDatabase(origin, name);
536     }
537 }
538
539 bool DatabaseTracker::deleteDatabaseFile(const SecurityOriginData& origin, const String& name)
540 {
541     String fullPath = fullPathForDatabase(origin, name, false);
542     if (fullPath.isEmpty())
543         return true;
544         
545     return deleteFile(fullPath);
546 }
547
548 void DatabaseTracker::setClient(DatabaseTrackerClient* client)
549 {
550     m_client = client;
551 }
552
553 void DatabaseTracker::setDefaultOriginQuota(unsigned long long quota)
554 {
555     m_defaultQuota = quota;
556 }
557
558 unsigned long long DatabaseTracker::defaultOriginQuota() const
559 {
560     return m_defaultQuota;
561 }
562
563 static Mutex& notificationMutex()
564 {
565     static Mutex mutex;
566     return mutex;
567 }
568
569 static Vector<pair<SecurityOriginData, String> >& notificationQueue()
570 {
571     static Vector<pair<SecurityOriginData, String> > queue;
572     return queue;
573 }
574
575 void DatabaseTracker::scheduleNotifyDatabaseChanged(const SecurityOriginData& origin, const String& name)
576 {
577     MutexLocker locker(notificationMutex());
578
579     notificationQueue().append(pair<SecurityOriginData, String>(origin.copy(), name.copy()));
580     scheduleForNotification();
581 }
582
583 static bool notificationScheduled = false;
584
585 void DatabaseTracker::scheduleForNotification()
586 {
587     ASSERT(!notificationMutex().tryLock());
588
589     if (!notificationScheduled) {
590         callOnMainThread(DatabaseTracker::notifyDatabasesChanged);
591         notificationScheduled = true;
592     }
593 }
594
595 void DatabaseTracker::notifyDatabasesChanged()
596 {
597     // Note that if DatabaseTracker ever becomes non-singleton, we'll have to ammend this notification
598     // mechanism to inclue which tracker the notification goes out on, as well
599     DatabaseTracker& theTracker(tracker());
600
601     Vector<pair<SecurityOriginData, String> > notifications;
602     {
603         MutexLocker locker(notificationMutex());
604
605         notifications.swap(notificationQueue());
606
607         notificationScheduled = false;
608     }
609
610     if (!theTracker.m_client)
611         return;
612
613     for (unsigned i = 0; i < notifications.size(); ++i)
614         theTracker.m_client->dispatchDidModifyDatabase(notifications[i].first, notifications[i].second);
615 }
616
617
618 } // namespace WebCore