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