1/* 2 * Copyright (C) 2011, 2013 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 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' 14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS 17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 23 * THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26#include "config.h" 27#include "LocalStorageDatabaseTracker.h" 28 29#include "LocalStorageDetails.h" 30#include "WorkQueue.h" 31#include <WebCore/FileSystem.h> 32#include <WebCore/SQLiteStatement.h> 33#include <WebCore/SecurityOrigin.h> 34#include <WebCore/TextEncoding.h> 35#include <wtf/text/CString.h> 36 37using namespace WebCore; 38 39namespace WebKit { 40 41PassRefPtr<LocalStorageDatabaseTracker> LocalStorageDatabaseTracker::create(PassRefPtr<WorkQueue> queue, const String& localStorageDirectory) 42{ 43 return adoptRef(new LocalStorageDatabaseTracker(queue, localStorageDirectory)); 44} 45 46LocalStorageDatabaseTracker::LocalStorageDatabaseTracker(PassRefPtr<WorkQueue> queue, const String& localStorageDirectory) 47 : m_queue(queue) 48 , m_localStorageDirectory(localStorageDirectory.isolatedCopy()) 49{ 50 ASSERT(!m_localStorageDirectory.isEmpty()); 51 52 // Make sure the encoding is initialized before we start dispatching things to the queue. 53 UTF8Encoding(); 54 55 RefPtr<LocalStorageDatabaseTracker> localStorageDatabaseTracker(this); 56 m_queue->dispatch([localStorageDatabaseTracker] { 57 localStorageDatabaseTracker->importOriginIdentifiers(); 58 }); 59} 60 61LocalStorageDatabaseTracker::~LocalStorageDatabaseTracker() 62{ 63} 64 65String LocalStorageDatabaseTracker::databasePath(SecurityOrigin* securityOrigin) const 66{ 67 return databasePath(securityOrigin->databaseIdentifier() + ".localstorage"); 68} 69 70void LocalStorageDatabaseTracker::didOpenDatabaseWithOrigin(SecurityOrigin* securityOrigin) 71{ 72 addDatabaseWithOriginIdentifier(securityOrigin->databaseIdentifier(), databasePath(securityOrigin)); 73} 74 75void LocalStorageDatabaseTracker::deleteDatabaseWithOrigin(SecurityOrigin* securityOrigin) 76{ 77 removeDatabaseWithOriginIdentifier(securityOrigin->databaseIdentifier()); 78} 79 80void LocalStorageDatabaseTracker::deleteAllDatabases() 81{ 82 m_origins.clear(); 83 84 openTrackerDatabase(SkipIfNonExistent); 85 if (!m_database.isOpen()) 86 return; 87 88 SQLiteStatement statement(m_database, "SELECT origin, path FROM Origins"); 89 if (statement.prepare() != SQLResultOk) { 90 LOG_ERROR("Failed to prepare statement."); 91 return; 92 } 93 94 int result; 95 while ((result = statement.step()) == SQLResultRow) { 96 deleteFile(statement.getColumnText(1)); 97 98 // FIXME: Call out to the client. 99 } 100 101 if (result != SQLResultDone) 102 LOG_ERROR("Failed to read in all origins from the database."); 103 104 if (m_database.isOpen()) 105 m_database.close(); 106 107 if (!deleteFile(trackerDatabasePath())) { 108 // In the case where it is not possible to delete the database file (e.g some other program 109 // like a virus scanner is accessing it), make sure to remove all entries. 110 openTrackerDatabase(SkipIfNonExistent); 111 if (!m_database.isOpen()) 112 return; 113 114 SQLiteStatement deleteStatement(m_database, "DELETE FROM Origins"); 115 if (deleteStatement.prepare() != SQLResultOk) { 116 LOG_ERROR("Unable to prepare deletion of all origins"); 117 return; 118 } 119 if (!deleteStatement.executeCommand()) { 120 LOG_ERROR("Unable to execute deletion of all origins"); 121 return; 122 } 123 } 124 125 deleteEmptyDirectory(m_localStorageDirectory); 126} 127 128Vector<RefPtr<WebCore::SecurityOrigin>> LocalStorageDatabaseTracker::origins() const 129{ 130 Vector<RefPtr<SecurityOrigin>> origins; 131 origins.reserveInitialCapacity(m_origins.size()); 132 133 for (const String& origin : m_origins) 134 origins.uncheckedAppend(SecurityOrigin::createFromDatabaseIdentifier(origin)); 135 136 return origins; 137} 138 139Vector<LocalStorageDetails> LocalStorageDatabaseTracker::details() 140{ 141 Vector<LocalStorageDetails> result; 142 result.reserveInitialCapacity(m_origins.size()); 143 144 for (const String& origin : m_origins) { 145 String filePath = pathForDatabaseWithOriginIdentifier(origin); 146 time_t time; 147 148 LocalStorageDetails details; 149 details.originIdentifier = origin.isolatedCopy(); 150 details.creationTime = getFileCreationTime(filePath, time) ? time : 0; 151 details.modificationTime = getFileModificationTime(filePath, time) ? time : 0; 152 result.uncheckedAppend(details); 153 } 154 155 return result; 156} 157 158String LocalStorageDatabaseTracker::databasePath(const String& filename) const 159{ 160 if (!makeAllDirectories(m_localStorageDirectory)) { 161 LOG_ERROR("Unable to create LocalStorage database path %s", m_localStorageDirectory.utf8().data()); 162 return String(); 163 } 164 165 return pathByAppendingComponent(m_localStorageDirectory, filename); 166} 167 168String LocalStorageDatabaseTracker::trackerDatabasePath() const 169{ 170 return databasePath("StorageTracker.db"); 171} 172 173void LocalStorageDatabaseTracker::openTrackerDatabase(DatabaseOpeningStrategy openingStrategy) 174{ 175 if (m_database.isOpen()) 176 return; 177 178 String databasePath = trackerDatabasePath(); 179 180 if (!fileExists(databasePath) && openingStrategy == SkipIfNonExistent) 181 return; 182 183 if (!m_database.open(databasePath)) { 184 LOG_ERROR("Failed to open databasePath %s.", databasePath.ascii().data()); 185 return; 186 } 187 188 // Since a WorkQueue isn't bound to a specific thread, we have to disable threading checks 189 // even though we never access the database from different threads simultaneously. 190 m_database.disableThreadingChecks(); 191 192 if (m_database.tableExists("Origins")) 193 return; 194 195 if (!m_database.executeCommand("CREATE TABLE Origins (origin TEXT UNIQUE ON CONFLICT REPLACE, path TEXT);")) 196 LOG_ERROR("Failed to create Origins table."); 197} 198 199void LocalStorageDatabaseTracker::importOriginIdentifiers() 200{ 201 openTrackerDatabase(SkipIfNonExistent); 202 203 if (m_database.isOpen()) { 204 SQLiteStatement statement(m_database, "SELECT origin FROM Origins"); 205 if (statement.prepare() != SQLResultOk) { 206 LOG_ERROR("Failed to prepare statement."); 207 return; 208 } 209 210 int result; 211 212 while ((result = statement.step()) == SQLResultRow) 213 m_origins.add(statement.getColumnText(0)); 214 215 if (result != SQLResultDone) { 216 LOG_ERROR("Failed to read in all origins from the database."); 217 return; 218 } 219 } 220 221 updateTrackerDatabaseFromLocalStorageDatabaseFiles(); 222} 223 224void LocalStorageDatabaseTracker::updateTrackerDatabaseFromLocalStorageDatabaseFiles() 225{ 226 Vector<String> paths = listDirectory(m_localStorageDirectory, "*.localstorage"); 227 228 HashSet<String> origins(m_origins); 229 HashSet<String> originsFromLocalStorageDatabaseFiles; 230 231 for (size_t i = 0; i < paths.size(); ++i) { 232 const String& path = paths[i]; 233 234 if (!path.endsWith(".localstorage")) 235 continue; 236 237 String filename = pathGetFileName(path); 238 239 String originIdentifier = filename.substring(0, filename.length() - strlen(".localstorage")); 240 241 if (!m_origins.contains(originIdentifier)) 242 addDatabaseWithOriginIdentifier(originIdentifier, path); 243 244 originsFromLocalStorageDatabaseFiles.add(originIdentifier); 245 } 246 247 for (auto it = origins.begin(), end = origins.end(); it != end; ++it) { 248 const String& originIdentifier = *it; 249 if (origins.contains(originIdentifier)) 250 continue; 251 252 removeDatabaseWithOriginIdentifier(originIdentifier); 253 } 254} 255 256void LocalStorageDatabaseTracker::addDatabaseWithOriginIdentifier(const String& originIdentifier, const String& databasePath) 257{ 258 openTrackerDatabase(CreateIfNonExistent); 259 if (!m_database.isOpen()) 260 return; 261 262 SQLiteStatement statement(m_database, "INSERT INTO Origins VALUES (?, ?)"); 263 if (statement.prepare() != SQLResultOk) { 264 LOG_ERROR("Unable to establish origin '%s' in the tracker", originIdentifier.utf8().data()); 265 return; 266 } 267 268 statement.bindText(1, originIdentifier); 269 statement.bindText(2, databasePath); 270 271 if (statement.step() != SQLResultDone) 272 LOG_ERROR("Unable to establish origin '%s' in the tracker", originIdentifier.utf8().data()); 273 274 m_origins.add(originIdentifier); 275 276 // FIXME: Tell clients that the origin was added. 277} 278 279void LocalStorageDatabaseTracker::removeDatabaseWithOriginIdentifier(const String& originIdentifier) 280{ 281 openTrackerDatabase(SkipIfNonExistent); 282 if (!m_database.isOpen()) 283 return; 284 285 String path = pathForDatabaseWithOriginIdentifier(originIdentifier); 286 if (path.isEmpty()) 287 return; 288 289 SQLiteStatement deleteStatement(m_database, "DELETE FROM Origins where origin=?"); 290 if (deleteStatement.prepare() != SQLResultOk) { 291 LOG_ERROR("Unable to prepare deletion of origin '%s'", originIdentifier.ascii().data()); 292 return; 293 } 294 deleteStatement.bindText(1, originIdentifier); 295 if (!deleteStatement.executeCommand()) { 296 LOG_ERROR("Unable to execute deletion of origin '%s'", originIdentifier.ascii().data()); 297 return; 298 } 299 300 deleteFile(path); 301 302 m_origins.remove(originIdentifier); 303 if (m_origins.isEmpty()) { 304 // There are no origins left, go ahead and delete the tracker database. 305 m_database.close(); 306 deleteFile(trackerDatabasePath()); 307 deleteEmptyDirectory(m_localStorageDirectory); 308 } 309 310 // FIXME: Tell clients that the origin was removed. 311} 312 313String LocalStorageDatabaseTracker::pathForDatabaseWithOriginIdentifier(const String& originIdentifier) 314{ 315 if (!m_database.isOpen()) 316 return String(); 317 318 SQLiteStatement pathStatement(m_database, "SELECT path FROM Origins WHERE origin=?"); 319 if (pathStatement.prepare() != SQLResultOk) { 320 LOG_ERROR("Unable to prepare selection of path for origin '%s'", originIdentifier.utf8().data()); 321 return String(); 322 } 323 324 pathStatement.bindText(1, originIdentifier); 325 326 int result = pathStatement.step(); 327 if (result != SQLResultRow) 328 return String(); 329 330 return pathStatement.getColumnText(0); 331} 332 333} // namespace WebKit 334