6af1dfc475c0494f53ad5be6f0264f0ffcb65903
[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     unsigned long long newQuota = page->chrome()->requestQuotaIncreaseForNewDatabase(document->frame(), originData, displayName, estimatedSize);
149     setQuota(originData, newQuota);
150     
151     return usage + estimatedSize <= newQuota;
152 }
153
154 bool DatabaseTracker::hasEntryForOrigin(const SecurityOriginData& origin)
155 {
156     populateOrigins();
157     return m_originQuotaMap->contains(origin);
158 }
159
160 bool DatabaseTracker::hasEntryForDatabase(const SecurityOriginData& origin, const String& databaseIdentifier)
161 {
162     SQLiteStatement statement(m_database, "SELECT guid FROM Databases WHERE origin=? AND name=?;");
163
164     if (statement.prepare() != SQLResultOk)
165         return false;
166
167     statement.bindText(1, origin.stringIdentifier());
168     statement.bindText(2, databaseIdentifier);
169
170     return statement.step() == SQLResultRow;
171 }
172
173 void DatabaseTracker::establishEntryForOrigin(const SecurityOriginData& origin)
174 {
175     ASSERT(!hasEntryForOrigin(origin));
176     
177     SQLiteStatement statement(m_database, "INSERT INTO Origins VALUES (?, ?)");
178     if (statement.prepare() != SQLResultOk) {
179         LOG_ERROR("Unable to establish origin %s in the tracker", origin.stringIdentifier().ascii().data());
180         return;
181     }
182         
183     statement.bindText(1, origin.stringIdentifier());
184     statement.bindInt64(2, m_defaultQuota);
185     
186     if (statement.step() != SQLResultDone) {
187         LOG_ERROR("Unable to establish origin %s in the tracker", origin.stringIdentifier().ascii().data());
188         return;
189     }
190
191     populateOrigins();
192     m_originQuotaMap->set(origin, m_defaultQuota);
193     
194     if (m_client)
195         m_client->dispatchDidModifyOrigin(origin);
196 }
197
198 String DatabaseTracker::fullPathForDatabase(const SecurityOriginData& origin, const String& name, bool createIfNotExists)
199 {
200     String originIdentifier = origin.stringIdentifier();
201     String originPath = pathByAppendingComponent(m_databasePath, originIdentifier);
202     
203     // Make sure the path for this SecurityOrigin exists
204     if (createIfNotExists && !makeAllDirectories(originPath))
205         return "";
206     
207     // See if we have a path for this database yet
208     SQLiteStatement statement(m_database, "SELECT path FROM Databases WHERE origin=? AND name=?;");
209
210     if (statement.prepare() != SQLResultOk)
211         return "";
212
213     statement.bindText(1, originIdentifier);
214     statement.bindText(2, name);
215
216     int result = statement.step();
217
218     if (result == SQLResultRow)
219         return pathByAppendingComponent(originPath, statement.getColumnText16(0));
220     if (!createIfNotExists)
221         return "";
222         
223     if (result != SQLResultDone) {
224         LOG_ERROR("Failed to retrieve filename from Database Tracker for origin %s, name %s", origin.stringIdentifier().ascii().data(), name.ascii().data());
225         return "";
226     }
227     statement.finalize();
228     
229     SQLiteStatement sequenceStatement(m_database, "SELECT seq FROM sqlite_sequence WHERE name='Databases';");
230
231     // FIXME: More informative error handling here, even though these steps should never fail
232     if (sequenceStatement.prepare() != SQLResultOk)
233         return "";
234     result = sequenceStatement.step();
235
236     // This has a range of 2^63 and starts at 0 for every time a user resets Safari -
237     // I can't imagine it'd over overflow
238     int64_t seq = 0;
239     if (result == SQLResultRow) {
240         seq = sequenceStatement.getColumnInt64(0);
241     } else if (result != SQLResultDone)
242         return "";
243     sequenceStatement.finalize();
244
245     String filename;
246     do {
247         ++seq;
248         filename = pathByAppendingComponent(originPath, String::format("%016llx.db", seq));
249     } while (fileExists(filename));
250
251     if (!addDatabase(origin, name, String::format("%016llx.db", seq)))
252         return "";
253
254     return filename;
255 }
256
257 void DatabaseTracker::populateOrigins()
258 {
259     if (m_originQuotaMap)
260         return;
261
262     m_originQuotaMap.set(new HashMap<SecurityOriginData, unsigned long long, SecurityOriginDataHash, SecurityOriginDataTraits>);
263
264     if (!m_database.isOpen())
265         return;
266
267     SQLiteStatement statement(m_database, "SELECT origin, quota FROM Origins");
268
269     if (statement.prepare() != SQLResultOk)
270         return;
271
272     int result;
273     while ((result = statement.step()) == SQLResultRow)
274         m_originQuotaMap->set(statement.getColumnText16(0), statement.getColumnInt64(1));
275
276     if (result != SQLResultDone)
277         LOG_ERROR("Failed to read in all origins from the database");
278
279     return;
280 }
281
282 void DatabaseTracker::origins(Vector<SecurityOriginData>& result)
283 {
284     if (!m_originQuotaMap)
285         populateOrigins();
286
287     copyKeysToVector(*(m_originQuotaMap.get()), result);
288 }
289
290 bool DatabaseTracker::databaseNamesForOrigin(const SecurityOriginData& origin, Vector<String>& resultVector)
291 {
292     if (!m_database.isOpen())
293         return false;
294
295     SQLiteStatement statement(m_database, "SELECT name FROM Databases where origin=?;");
296
297     if (statement.prepare() != SQLResultOk)
298         return false;
299
300     statement.bindText(1, origin.stringIdentifier());
301
302     int result;
303     while ((result = statement.step()) == SQLResultRow)
304         resultVector.append(statement.getColumnText16(0));
305
306     if (result != SQLResultDone) {
307         LOG_ERROR("Failed to retrieve all database names for origin %s", origin.stringIdentifier().ascii().data());
308         return false;
309     }
310
311     return true;
312 }
313
314 DatabaseDetails DatabaseTracker::detailsForNameAndOrigin(const String& name, const SecurityOriginData& origin)
315 {
316     String originIdentifier = origin.stringIdentifier();
317         
318     SQLiteStatement statement(m_database, "SELECT displayName, estimatedSize FROM Databases WHERE origin=? AND name=?");
319     if (statement.prepare() != SQLResultOk)
320         return DatabaseDetails();
321    
322     statement.bindText(1, originIdentifier);
323     statement.bindText(2, name);
324     
325     int result = statement.step();
326     if (result == SQLResultDone)
327         return DatabaseDetails();
328     
329     if (result != SQLResultRow) {
330         LOG_ERROR("Error retrieving details for database %s in origin %s from tracker database", name.ascii().data(), originIdentifier.ascii().data());
331         return DatabaseDetails();
332     }
333     
334     return DatabaseDetails(name, statement.getColumnText(0), statement.getColumnInt64(1), usageForDatabase(name, origin));
335 }
336
337 void DatabaseTracker::setDatabaseDetails(const SecurityOriginData& origin, const String& name, const String& displayName, unsigned long estimatedSize)
338 {
339     String originIdentifier = origin.stringIdentifier();
340     int64_t guid = 0;
341     
342     SQLiteStatement statement(m_database, "SELECT guid FROM Databases WHERE origin=? AND name=?");
343     if (statement.prepare() != SQLResultOk)
344         return;
345         
346     statement.bindText(1, originIdentifier);
347     statement.bindText(2, name);
348     
349     int result = statement.step();
350     if (result == SQLResultRow)
351         guid = statement.getColumnInt64(0);
352     statement.finalize();
353
354     if (guid == 0) {
355         if (result != SQLResultDone)
356             LOG_ERROR("Error to determing existence of database %s in origin %s in tracker database", name.ascii().data(), originIdentifier.ascii().data());
357         else {
358             // This case should never occur - we should never be setting database details for a database that doesn't already exist in the tracker
359             // 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
360             // So we'll print an error instead
361             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",
362                        name.ascii().data(), originIdentifier.ascii().data());
363         }
364         return;
365     }
366     
367     SQLiteStatement updateStatement(m_database, "UPDATE Databases SET displayName=?, estimatedSize=? WHERE guid=?");
368     if (updateStatement.prepare() != SQLResultOk)
369         return;
370     
371     updateStatement.bindText(1, displayName);
372     updateStatement.bindInt64(2, estimatedSize);
373     updateStatement.bindInt64(3, guid);
374     
375     if (updateStatement.step() != SQLResultDone) {
376         LOG_ERROR("Failed to update details for database %s in origin %s", name.ascii().data(), originIdentifier.ascii().data());
377         return;  
378     }
379     
380     if (m_client)
381         m_client->dispatchDidModifyDatabase(origin, name);
382 }
383
384 unsigned long long DatabaseTracker::usageForDatabase(const String& name, const SecurityOriginData& origin)
385 {
386     String path = fullPathForDatabase(origin, name, false);
387     if (path.isEmpty())
388         return 0;
389         
390     long long size;
391     return fileSize(path, size) ? size : 0;
392 }
393
394 unsigned long long DatabaseTracker::usageForOrigin(const SecurityOriginData& origin)
395 {
396     Vector<String> names;
397     databaseNamesForOrigin(origin, names);
398     
399     unsigned long long result = 0;
400     for (unsigned i = 0; i < names.size(); ++i)
401         result += usageForDatabase(names[i], origin);
402         
403     return result;
404 }
405
406 unsigned long long DatabaseTracker::quotaForOrigin(const SecurityOriginData& origin)
407 {
408     populateOrigins();
409     return m_originQuotaMap->get(origin);
410 }
411
412 void DatabaseTracker::setQuota(const SecurityOriginData& origin, unsigned long long quota)
413 {
414     populateOrigins();
415     if (!m_originQuotaMap->contains(origin))
416         establishEntryForOrigin(origin);
417     
418     m_originQuotaMap->set(origin, quota);
419     
420     SQLiteStatement statement(m_database, "UPDATE Origins SET quota=? WHERE origin=?");
421     
422     bool error = statement.prepare() != SQLResultOk;
423     if (!error) {
424         statement.bindInt64(1, quota);
425         statement.bindText(2, origin.stringIdentifier());
426         
427         error = !statement.executeCommand();
428     }
429         
430     if (error)
431         LOG_ERROR("Failed to set quota %llu in tracker database for origin %s", quota, origin.stringIdentifier().ascii().data());
432     
433     if (m_client)
434         m_client->dispatchDidModifyOrigin(origin);
435 }
436     
437 bool DatabaseTracker::addDatabase(const SecurityOriginData& origin, const String& name, const String& path)
438 {
439     if (!m_database.isOpen())
440         return false;
441         
442     // New database should never be added until the origin has been established
443     ASSERT(hasEntryForOrigin(origin));
444
445     SQLiteStatement statement(m_database, "INSERT INTO Databases (origin, name, path) VALUES (?, ?, ?);");
446
447     if (statement.prepare() != SQLResultOk)
448         return false;
449
450     statement.bindText(1, origin.stringIdentifier());
451     statement.bindText(2, name);
452     statement.bindText(3, path);
453
454     if (!statement.executeCommand()) {
455         LOG_ERROR("Failed to add database %s to origin %s: %s\n", name.ascii().data(), origin.stringIdentifier().ascii().data(), statement.lastErrorMsg());
456         return false;
457     }
458     
459     if (m_client)
460         m_client->dispatchDidModifyOrigin(origin);
461     
462     return true;
463 }
464
465 void DatabaseTracker::deleteAllDatabases()
466 {
467     populateOrigins();
468     
469     HashMap<SecurityOriginData, unsigned long long, SecurityOriginDataHash, SecurityOriginDataTraits>::const_iterator iter = m_originQuotaMap->begin();
470     HashMap<SecurityOriginData, unsigned long long, SecurityOriginDataHash, SecurityOriginDataTraits>::const_iterator end = m_originQuotaMap->end();
471
472     for (; iter != end; ++iter)
473         deleteDatabasesWithOrigin(iter->first);
474 }
475
476 void DatabaseTracker::deleteDatabasesWithOrigin(const SecurityOriginData& origin)
477 {
478     Vector<String> databaseNames;
479     if (!databaseNamesForOrigin(origin, databaseNames)) {
480         LOG_ERROR("Unable to retrieve list of database names for origin %s", origin.stringIdentifier().ascii().data());
481         return;
482     }
483     
484     for (unsigned i = 0; i < databaseNames.size(); ++i) {
485         if (!deleteDatabaseFile(origin, databaseNames[i])) {
486             LOG_ERROR("Unable to delete file for database %s in origin %s", databaseNames[i].ascii().data(), origin.stringIdentifier().ascii().data());
487             return;
488         }
489     }
490     
491     SQLiteStatement statement(m_database, "DELETE FROM Databases WHERE origin=?");
492     if (statement.prepare() != SQLResultOk) {
493         LOG_ERROR("Unable to prepare deletion of databases from origin %s from tracker", origin.stringIdentifier().ascii().data());
494         return;
495     }
496         
497     statement.bindText(1, origin.stringIdentifier());
498     
499     if (!statement.executeCommand()) {
500         LOG_ERROR("Unable to execute deletion of databases from origin %s from tracker", origin.stringIdentifier().ascii().data());
501         return;
502     }
503     
504     if (m_client) {
505         m_client->dispatchDidModifyOrigin(origin);
506         for (unsigned i = 0; i < databaseNames.size(); ++i)
507             m_client->dispatchDidModifyDatabase(origin, databaseNames[i]);
508     }
509 }
510
511 void DatabaseTracker::deleteDatabase(const SecurityOriginData& origin, const String& name)
512 {
513     if (!deleteDatabaseFile(origin, name)) {
514         LOG_ERROR("Unable to delete file for database %s in origin %s", name.ascii().data(), origin.stringIdentifier().ascii().data());
515         return;
516     }
517     
518     SQLiteStatement statement(m_database, "DELETE FROM Databases WHERE origin=? AND name=?");
519     if (statement.prepare() != SQLResultOk) {
520         LOG_ERROR("Unable to prepare deletion of database %s from origin %s from tracker", name.ascii().data(), origin.stringIdentifier().ascii().data());
521         return;
522     }
523         
524     statement.bindText(1, origin.stringIdentifier());
525     statement.bindText(2, name);
526     
527     if (!statement.executeCommand()) {
528         LOG_ERROR("Unable to execute deletion of database %s from origin %s from tracker", name.ascii().data(), origin.stringIdentifier().ascii().data());
529         return;
530     }
531     
532     if (m_client) {
533         m_client->dispatchDidModifyOrigin(origin);
534         m_client->dispatchDidModifyDatabase(origin, name);
535     }
536 }
537
538 bool DatabaseTracker::deleteDatabaseFile(const SecurityOriginData& origin, const String& name)
539 {
540     String fullPath = fullPathForDatabase(origin, name, false);
541     if (fullPath.isEmpty())
542         return true;
543         
544     return deleteFile(fullPath);
545 }
546
547 void DatabaseTracker::setClient(DatabaseTrackerClient* client)
548 {
549     m_client = client;
550 }
551
552 void DatabaseTracker::setDefaultOriginQuota(unsigned long long quota)
553 {
554     m_defaultQuota = quota;
555 }
556
557 unsigned long long DatabaseTracker::defaultOriginQuota() const
558 {
559     return m_defaultQuota;
560 }
561
562 static Mutex& notificationMutex()
563 {
564     static Mutex mutex;
565     return mutex;
566 }
567
568 static Vector<pair<SecurityOriginData, String> >& notificationQueue()
569 {
570     static Vector<pair<SecurityOriginData, String> > queue;
571     return queue;
572 }
573
574 void DatabaseTracker::scheduleNotifyDatabaseChanged(const SecurityOriginData& origin, const String& name)
575 {
576     MutexLocker locker(notificationMutex());
577
578     notificationQueue().append(pair<SecurityOriginData, String>(origin.copy(), name.copy()));
579     scheduleForNotification();
580 }
581
582 static bool notificationScheduled = false;
583
584 void DatabaseTracker::scheduleForNotification()
585 {
586     ASSERT(!notificationMutex().tryLock());
587
588     if (!notificationScheduled) {
589         callOnMainThread(DatabaseTracker::notifyDatabasesChanged);
590         notificationScheduled = true;
591     }
592 }
593
594 void DatabaseTracker::notifyDatabasesChanged()
595 {
596     // Note that if DatabaseTracker ever becomes non-singleton, we'll have to ammend this notification
597     // mechanism to inclue which tracker the notification goes out on, as well
598     DatabaseTracker& theTracker(tracker());
599
600     Vector<pair<SecurityOriginData, String> > notifications;
601     {
602         MutexLocker locker(notificationMutex());
603
604         notifications.swap(notificationQueue());
605
606         notificationScheduled = false;
607     }
608
609     if (!theTracker.m_client)
610         return;
611
612     for (unsigned i = 0; i < notifications.size(); ++i)
613         theTracker.m_client->dispatchDidModifyDatabase(notifications[i].first, notifications[i].second);
614 }
615
616
617 } // namespace WebCore