WebCore:
[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 the new database will fit as-is, allow its creation
135     unsigned long long usage = usageForOrigin(originData);
136     if (usage + estimatedSize < quotaForOrigin(originData))
137         return true;
138     
139     // Otherwise, ask the UI Delegate for a new quota
140     Page* page;
141     if (!(page = document->page()))
142         return false;
143     
144     unsigned long long newQuota = page->chrome()->requestQuotaIncreaseForNewDatabase(document->frame(), originData, displayName, estimatedSize);
145     setQuota(originData, newQuota);
146     
147     return usage + estimatedSize < newQuota;
148 }
149
150 bool DatabaseTracker::hasEntryForOrigin(const SecurityOriginData& origin)
151 {
152     populateOrigins();
153     return m_originQuotaMap->contains(origin);
154 }
155
156 void DatabaseTracker::establishEntryForOrigin(const SecurityOriginData& origin)
157 {
158     ASSERT(!hasEntryForOrigin(origin));
159     
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());
163         return;
164     }
165         
166     statement.bindText(1, origin.stringIdentifier());
167     statement.bindInt64(2, m_defaultQuota);
168     
169     if (statement.step() != SQLResultDone) {
170         LOG_ERROR("Unable to establish origin %s in the tracker", origin.stringIdentifier().ascii().data());
171         return;
172     }
173
174     populateOrigins();
175     m_originQuotaMap->set(origin, m_defaultQuota);
176     
177     if (m_client)
178         m_client->dispatchDidModifyOrigin(origin);
179 }
180
181 String DatabaseTracker::fullPathForDatabase(const SecurityOriginData& origin, const String& name, bool createIfNotExists)
182 {
183     String originIdentifier = origin.stringIdentifier();
184     String originPath = pathByAppendingComponent(m_databasePath, originIdentifier);
185     
186     // Make sure the path for this SecurityOrigin exists
187     if (createIfNotExists && !makeAllDirectories(originPath))
188         return "";
189     
190     // See if we have a path for this database yet
191     SQLiteStatement statement(m_database, "SELECT path FROM Databases WHERE origin=? AND name=?;");
192
193     if (statement.prepare() != SQLResultOk)
194         return "";
195
196     statement.bindText(1, originIdentifier);
197     statement.bindText(2, name);
198
199     int result = statement.step();
200
201     if (result == SQLResultRow)
202         return pathByAppendingComponent(originPath, statement.getColumnText16(0));
203     if (!createIfNotExists)
204         return "";
205         
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());
208         return "";
209     }
210     statement.finalize();
211     
212     SQLiteStatement sequenceStatement(m_database, "SELECT seq FROM sqlite_sequence WHERE name='Databases';");
213
214     // FIXME: More informative error handling here, even though these steps should never fail
215     if (sequenceStatement.prepare() != SQLResultOk)
216         return "";
217     result = sequenceStatement.step();
218
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
221     int64_t seq = 0;
222     if (result == SQLResultRow) {
223         seq = sequenceStatement.getColumnInt64(0);
224     } else if (result != SQLResultDone)
225         return "";
226     sequenceStatement.finalize();
227
228     String filename;
229     do {
230         ++seq;
231         filename = pathByAppendingComponent(originPath, String::format("%016llx.db", seq));
232     } while (fileExists(filename));
233
234     if (!addDatabase(origin, name, String::format("%016llx.db", seq)))
235         return "";
236
237     return filename;
238 }
239
240 void DatabaseTracker::populateOrigins()
241 {
242     if (m_originQuotaMap)
243         return;
244
245     m_originQuotaMap.set(new HashMap<SecurityOriginData, unsigned long long, SecurityOriginDataHash, SecurityOriginDataTraits>);
246
247     if (!m_database.isOpen())
248         return;
249
250     SQLiteStatement statement(m_database, "SELECT origin, quota FROM Origins");
251
252     if (statement.prepare() != SQLResultOk)
253         return;
254
255     int result;
256     while ((result = statement.step()) == SQLResultRow)
257         m_originQuotaMap->set(statement.getColumnText16(0), statement.getColumnInt64(1));
258
259     if (result != SQLResultDone)
260         LOG_ERROR("Failed to read in all origins from the database");
261
262     return;
263 }
264
265 void DatabaseTracker::origins(Vector<SecurityOriginData>& result)
266 {
267     if (!m_originQuotaMap)
268         populateOrigins();
269
270     copyKeysToVector(*(m_originQuotaMap.get()), result);
271 }
272
273 bool DatabaseTracker::databaseNamesForOrigin(const SecurityOriginData& origin, Vector<String>& resultVector)
274 {
275     if (!m_database.isOpen())
276         return false;
277
278     SQLiteStatement statement(m_database, "SELECT name FROM Databases where origin=?;");
279
280     if (statement.prepare() != SQLResultOk)
281         return false;
282
283     statement.bindText(1, origin.stringIdentifier());
284
285     int result;
286     while ((result = statement.step()) == SQLResultRow)
287         resultVector.append(statement.getColumnText16(0));
288
289     if (result != SQLResultDone) {
290         LOG_ERROR("Failed to retrieve all database names for origin %s", origin.stringIdentifier().ascii().data());
291         return false;
292     }
293
294     return true;
295 }
296
297 DatabaseDetails DatabaseTracker::detailsForNameAndOrigin(const String& name, const SecurityOriginData& origin)
298 {
299     String originIdentifier = origin.stringIdentifier();
300         
301     SQLiteStatement statement(m_database, "SELECT displayName, estimatedSize FROM Databases WHERE origin=? AND name=?");
302     if (statement.prepare() != SQLResultOk)
303         return DatabaseDetails();
304    
305     statement.bindText(1, originIdentifier);
306     statement.bindText(2, name);
307     
308     int result = statement.step();
309     if (result == SQLResultDone)
310         return DatabaseDetails();
311     
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();
315     }
316     
317     return DatabaseDetails(name, statement.getColumnText(0), statement.getColumnInt64(1), usageForDatabase(name, origin));
318 }
319
320 void DatabaseTracker::setDatabaseDetails(const SecurityOriginData& origin, const String& name, const String& displayName, unsigned long estimatedSize)
321 {
322     String originIdentifier = origin.stringIdentifier();
323     int64_t guid = 0;
324     
325     SQLiteStatement statement(m_database, "SELECT guid FROM Databases WHERE origin=? AND name=?");
326     if (statement.prepare() != SQLResultOk)
327         return;
328         
329     statement.bindText(1, originIdentifier);
330     statement.bindText(2, name);
331     
332     int result = statement.step();
333     if (result == SQLResultRow)
334         guid = statement.getColumnInt64(0);
335     statement.finalize();
336
337     if (guid == 0) {
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());
340         else {
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());
346         }
347         return;
348     }
349     
350     SQLiteStatement updateStatement(m_database, "UPDATE Databases SET displayName=?, estimatedSize=? WHERE guid=?");
351     if (updateStatement.prepare() != SQLResultOk)
352         return;
353     
354     updateStatement.bindText(1, displayName);
355     updateStatement.bindInt64(2, estimatedSize);
356     updateStatement.bindInt64(3, guid);
357     
358     if (updateStatement.step() != SQLResultDone) {
359         LOG_ERROR("Failed to update details for database %s in origin %s", name.ascii().data(), originIdentifier.ascii().data());
360         return;  
361     }
362     
363     if (m_client)
364         m_client->dispatchDidModifyDatabase(origin, name);
365 }
366
367 unsigned long long DatabaseTracker::usageForDatabase(const String& name, const SecurityOriginData& origin)
368 {
369     String path = fullPathForDatabase(origin, name, false);
370     if (path.isEmpty())
371         return 0;
372         
373     long long size;
374     return fileSize(path, size) ? size : 0;
375 }
376
377 unsigned long long DatabaseTracker::usageForOrigin(const SecurityOriginData& origin)
378 {
379     Vector<String> names;
380     databaseNamesForOrigin(origin, names);
381     
382     unsigned long long result = 0;
383     for (unsigned i = 0; i < names.size(); ++i)
384         result += usageForDatabase(names[i], origin);
385         
386     return result;
387 }
388
389 unsigned long long DatabaseTracker::quotaForOrigin(const SecurityOriginData& origin)
390 {
391     populateOrigins();
392     return m_originQuotaMap->get(origin);
393 }
394
395 void DatabaseTracker::setQuota(const SecurityOriginData& origin, unsigned long long quota)
396 {
397     populateOrigins();
398     if (!m_originQuotaMap->contains(origin))
399         establishEntryForOrigin(origin);
400     
401     m_originQuotaMap->set(origin, quota);
402     
403     SQLiteStatement statement(m_database, "UPDATE Origins SET quota=? WHERE origin=?");
404     
405     bool error = statement.prepare() != SQLResultOk;
406     if (!error) {
407         statement.bindInt64(1, quota);
408         statement.bindText(2, origin.stringIdentifier());
409         
410         error = !statement.executeCommand();
411     }
412         
413     if (error)
414         LOG_ERROR("Failed to set quota %llu in tracker database for origin %s", quota, origin.stringIdentifier().ascii().data());
415     
416     if (m_client)
417         m_client->dispatchDidModifyOrigin(origin);
418 }
419     
420 bool DatabaseTracker::addDatabase(const SecurityOriginData& origin, const String& name, const String& path)
421 {
422     if (!m_database.isOpen())
423         return false;
424         
425     // New database should never be added until the origin has been established
426     ASSERT(hasEntryForOrigin(origin));
427
428     SQLiteStatement statement(m_database, "INSERT INTO Databases (origin, name, path) VALUES (?, ?, ?);");
429
430     if (statement.prepare() != SQLResultOk)
431         return false;
432
433     statement.bindText(1, origin.stringIdentifier());
434     statement.bindText(2, name);
435     statement.bindText(3, path);
436
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());
439         return false;
440     }
441     
442     if (m_client)
443         m_client->dispatchDidModifyOrigin(origin);
444     
445     return true;
446 }
447
448 void DatabaseTracker::deleteAllDatabases()
449 {
450     populateOrigins();
451     
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();
454
455     for (; iter != end; ++iter)
456         deleteDatabasesWithOrigin(iter->first);
457 }
458
459 void DatabaseTracker::deleteDatabasesWithOrigin(const SecurityOriginData& origin)
460 {
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());
464         return;
465     }
466     
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());
470             return;
471         }
472     }
473     
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());
477         return;
478     }
479         
480     statement.bindText(1, origin.stringIdentifier());
481     
482     if (!statement.executeCommand()) {
483         LOG_ERROR("Unable to execute deletion of databases from origin %s from tracker", origin.stringIdentifier().ascii().data());
484         return;
485     }
486     
487     if (m_client) {
488         m_client->dispatchDidModifyOrigin(origin);
489         for (unsigned i = 0; i < databaseNames.size(); ++i)
490             m_client->dispatchDidModifyDatabase(origin, databaseNames[i]);
491     }
492 }
493
494 void DatabaseTracker::deleteDatabase(const SecurityOriginData& origin, const String& name)
495 {
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());
498         return;
499     }
500     
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());
504         return;
505     }
506         
507     statement.bindText(1, origin.stringIdentifier());
508     statement.bindText(2, name);
509     
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());
512         return;
513     }
514     
515     if (m_client) {
516         m_client->dispatchDidModifyOrigin(origin);
517         m_client->dispatchDidModifyDatabase(origin, name);
518     }
519 }
520
521 bool DatabaseTracker::deleteDatabaseFile(const SecurityOriginData& origin, const String& name)
522 {
523     String fullPath = fullPathForDatabase(origin, name, false);
524     if (fullPath.isEmpty())
525         return true;
526         
527     return deleteFile(fullPath);
528 }
529
530 void DatabaseTracker::setClient(DatabaseTrackerClient* client)
531 {
532     m_client = client;
533 }
534
535 void DatabaseTracker::setDefaultOriginQuota(unsigned long long quota)
536 {
537     m_defaultQuota = quota;
538 }
539
540 unsigned long long DatabaseTracker::defaultOriginQuota() const
541 {
542     return m_defaultQuota;
543 }
544
545 } // namespace WebCore