1 /* 2 * Copyright (C) 2011 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 "StorageTracker.h" 28 29 #if ENABLE(DOM_STORAGE) 30 31 #include "DatabaseThread.h" 32 #include "FileSystem.h" 33 #include "LocalStorageTask.h" 34 #include "LocalStorageThread.h" 35 #include "Logging.h" 36 #include "PageGroup.h" 37 #include "SQLiteFileSystem.h" 38 #include "SQLiteStatement.h" 39 #include "SecurityOrigin.h" 40 #include "StorageTrackerClient.h" 41 #include "TextEncoding.h" 42 #include <wtf/MainThread.h> 43 #include <wtf/StdLibExtras.h> 44 #include <wtf/Vector.h> 45 #include <wtf/text/CString.h> 46 47 namespace WebCore { 48 49 static StorageTracker* storageTracker = 0; 50 51 void StorageTracker::initializeTracker(const String& storagePath) 52 { 53 ASSERT(isMainThread()); 54 ASSERT(!storageTracker); 55 56 if (!storageTracker) 57 storageTracker = new StorageTracker(storagePath); 58 59 // Make sure text encoding maps have been built on the main thread, as the StorageTracker thread might try to do it there instead. 60 // FIXME (<rdar://problem/9127819>): Is there a more explicit way of doing this besides accessing the UTF8Encoding? 61 UTF8Encoding(); 62 63 SQLiteFileSystem::registerSQLiteVFS(); 64 storageTracker->setIsActive(true); 65 storageTracker->m_thread->start(); 66 storageTracker->importOriginIdentifiers(); 67 } 68 69 StorageTracker& StorageTracker::tracker() 70 { 71 if (!storageTracker) 72 storageTracker = new StorageTracker(""); 73 74 return *storageTracker; 75 } 76 77 StorageTracker::StorageTracker(const String& storagePath) 78 : m_client(0) 79 , m_thread(LocalStorageThread::create()) 80 , m_isActive(false) 81 { 82 setStorageDirectoryPath(storagePath); 83 } 84 85 void StorageTracker::setStorageDirectoryPath(const String& path) 86 { 87 MutexLocker lockDatabase(m_databaseGuard); 88 ASSERT(!m_database.isOpen()); 89 90 m_storageDirectoryPath = path.threadsafeCopy(); 91 } 92 93 String StorageTracker::trackerDatabasePath() 94 { 95 ASSERT(!m_databaseGuard.tryLock()); 96 return SQLiteFileSystem::appendDatabaseFileNameToPath(m_storageDirectoryPath, "StorageTracker.db"); 97 } 98 99 void StorageTracker::openTrackerDatabase(bool createIfDoesNotExist) 100 { 101 ASSERT(m_isActive); 102 ASSERT(!isMainThread()); 103 ASSERT(!m_databaseGuard.tryLock()); 104 105 if (m_database.isOpen()) 106 return; 107 108 String databasePath = trackerDatabasePath(); 109 110 if (!SQLiteFileSystem::ensureDatabaseFileExists(databasePath, createIfDoesNotExist)) { 111 if (createIfDoesNotExist) 112 LOG_ERROR("Failed to create database file '%s'", databasePath.ascii().data()); 113 return; 114 } 115 116 if (!m_database.open(databasePath)) { 117 LOG_ERROR("Failed to open databasePath %s.", databasePath.ascii().data()); 118 return; 119 } 120 121 m_database.disableThreadingChecks(); 122 123 if (!m_database.tableExists("Origins")) { 124 if (!m_database.executeCommand("CREATE TABLE Origins (origin TEXT UNIQUE ON CONFLICT REPLACE, path TEXT);")) 125 LOG_ERROR("Failed to create Origins table."); 126 } 127 } 128 129 void StorageTracker::importOriginIdentifiers() 130 { 131 if (!m_isActive) 132 return; 133 134 ASSERT(isMainThread()); 135 ASSERT(m_thread); 136 137 m_thread->scheduleTask(LocalStorageTask::createOriginIdentifiersImport()); 138 } 139 140 void StorageTracker::syncImportOriginIdentifiers() 141 { 142 ASSERT(m_isActive); 143 144 ASSERT(!isMainThread()); 145 146 { 147 MutexLocker lockDatabase(m_databaseGuard); 148 149 // Don't force creation of StorageTracker's db just because a tracker 150 // was initialized. It will be created if local storage dbs are found 151 // by syncFileSystemAndTrackerDatabse() or the next time a local storage 152 // db is created by StorageAreaSync. 153 openTrackerDatabase(false); 154 155 if (m_database.isOpen()) { 156 SQLiteStatement statement(m_database, "SELECT origin FROM Origins"); 157 if (statement.prepare() != SQLResultOk) { 158 LOG_ERROR("Failed to prepare statement."); 159 return; 160 } 161 162 int result; 163 164 { 165 MutexLocker lockOrigins(m_originSetGuard); 166 while ((result = statement.step()) == SQLResultRow) 167 m_originSet.add(statement.getColumnText(0).threadsafeCopy()); 168 } 169 170 if (result != SQLResultDone) { 171 LOG_ERROR("Failed to read in all origins from the database."); 172 return; 173 } 174 } 175 } 176 177 syncFileSystemAndTrackerDatabase(); 178 179 { 180 MutexLocker lockClient(m_clientGuard); 181 if (m_client) { 182 MutexLocker lockOrigins(m_originSetGuard); 183 OriginSet::const_iterator end = m_originSet.end(); 184 for (OriginSet::const_iterator it = m_originSet.begin(); it != end; ++it) 185 m_client->dispatchDidModifyOrigin(*it); 186 } 187 } 188 } 189 190 void StorageTracker::syncFileSystemAndTrackerDatabase() 191 { 192 ASSERT(!isMainThread()); 193 ASSERT(m_isActive); 194 195 m_databaseGuard.lock(); 196 DEFINE_STATIC_LOCAL(const String, fileMatchPattern, ("*.localstorage")); 197 DEFINE_STATIC_LOCAL(const String, fileExt, (".localstorage")); 198 DEFINE_STATIC_LOCAL(const unsigned, fileExtLength, (fileExt.length())); 199 m_databaseGuard.unlock(); 200 201 Vector<String> paths; 202 { 203 MutexLocker lock(m_databaseGuard); 204 paths = listDirectory(m_storageDirectoryPath, fileMatchPattern); 205 } 206 207 // Use a copy of m_originSet to find expired entries and to schedule their 208 // deletions from disk and from m_originSet. 209 OriginSet originSetCopy; 210 { 211 MutexLocker lock(m_originSetGuard); 212 OriginSet::const_iterator end = m_originSet.end(); 213 for (OriginSet::const_iterator it = m_originSet.begin(); it != end; ++it) 214 originSetCopy.add((*it).threadsafeCopy()); 215 } 216 217 // Add missing StorageTracker records. 218 OriginSet foundOrigins; 219 Vector<String>::const_iterator end = paths.end(); 220 for (Vector<String>::const_iterator it = paths.begin(); it != end; ++it) { 221 String path = *it; 222 if (path.endsWith(fileExt, true) && path.length() > fileExtLength) { 223 String file = pathGetFileName(path); 224 String originIdentifier = file.substring(0, file.length() - fileExtLength); 225 if (!originSetCopy.contains(originIdentifier)) 226 syncSetOriginDetails(originIdentifier, path); 227 228 foundOrigins.add(originIdentifier); 229 } 230 } 231 232 // Delete stale StorageTracker records. 233 OriginSet::const_iterator setEnd = originSetCopy.end(); 234 for (OriginSet::const_iterator it = originSetCopy.begin(); it != setEnd; ++it) { 235 if (!foundOrigins.contains(*it)) { 236 RefPtr<StringImpl> originIdentifier = (*it).threadsafeCopy().impl(); 237 callOnMainThread(deleteOriginOnMainThread, originIdentifier.release().leakRef()); 238 } 239 } 240 } 241 242 void StorageTracker::setOriginDetails(const String& originIdentifier, const String& databaseFile) 243 { 244 if (!m_isActive) 245 return; 246 247 { 248 MutexLocker lockOrigins(m_originSetGuard); 249 250 if (m_originSet.contains(originIdentifier)) 251 return; 252 253 m_originSet.add(originIdentifier); 254 } 255 256 OwnPtr<LocalStorageTask> task = LocalStorageTask::createSetOriginDetails(originIdentifier.threadsafeCopy(), databaseFile); 257 258 if (isMainThread()) { 259 ASSERT(m_thread); 260 m_thread->scheduleTask(task.release()); 261 } else 262 callOnMainThread(scheduleTask, reinterpret_cast<void*>(task.leakPtr())); 263 } 264 265 void StorageTracker::scheduleTask(void* taskIn) 266 { 267 ASSERT(isMainThread()); 268 ASSERT(StorageTracker::tracker().m_thread); 269 270 OwnPtr<LocalStorageTask> task = adoptPtr(reinterpret_cast<LocalStorageTask*>(taskIn)); 271 272 StorageTracker::tracker().m_thread->scheduleTask(task.release()); 273 } 274 275 void StorageTracker::syncSetOriginDetails(const String& originIdentifier, const String& databaseFile) 276 { 277 ASSERT(!isMainThread()); 278 279 MutexLocker lockDatabase(m_databaseGuard); 280 281 openTrackerDatabase(true); 282 283 if (!m_database.isOpen()) 284 return; 285 286 SQLiteStatement statement(m_database, "INSERT INTO Origins VALUES (?, ?)"); 287 if (statement.prepare() != SQLResultOk) { 288 LOG_ERROR("Unable to establish origin '%s' in the tracker", originIdentifier.ascii().data()); 289 return; 290 } 291 292 statement.bindText(1, originIdentifier); 293 statement.bindText(2, databaseFile); 294 295 if (statement.step() != SQLResultDone) 296 LOG_ERROR("Unable to establish origin '%s' in the tracker", originIdentifier.ascii().data()); 297 298 { 299 MutexLocker lockOrigins(m_originSetGuard); 300 if (!m_originSet.contains(originIdentifier)) 301 m_originSet.add(originIdentifier); 302 } 303 304 { 305 MutexLocker lockClient(m_clientGuard); 306 if (m_client) 307 m_client->dispatchDidModifyOrigin(originIdentifier); 308 } 309 } 310 311 void StorageTracker::origins(Vector<RefPtr<SecurityOrigin> >& result) 312 { 313 ASSERT(m_isActive); 314 315 if (!m_isActive) 316 return; 317 318 MutexLocker lockOrigins(m_originSetGuard); 319 320 OriginSet::const_iterator end = m_originSet.end(); 321 for (OriginSet::const_iterator it = m_originSet.begin(); it != end; ++it) 322 result.append(SecurityOrigin::createFromDatabaseIdentifier(*it)); 323 } 324 325 void StorageTracker::deleteAllOrigins() 326 { 327 ASSERT(m_isActive); 328 ASSERT(isMainThread()); 329 ASSERT(m_thread); 330 331 if (!m_isActive) 332 return; 333 334 { 335 MutexLocker lockOrigins(m_originSetGuard); 336 willDeleteAllOrigins(); 337 m_originSet.clear(); 338 } 339 340 PageGroup::clearLocalStorageForAllOrigins(); 341 342 m_thread->scheduleTask(LocalStorageTask::createDeleteAllOrigins()); 343 } 344 345 void StorageTracker::syncDeleteAllOrigins() 346 { 347 ASSERT(!isMainThread()); 348 349 MutexLocker lockDatabase(m_databaseGuard); 350 351 openTrackerDatabase(false); 352 if (!m_database.isOpen()) 353 return; 354 355 SQLiteStatement statement(m_database, "SELECT origin, path FROM Origins"); 356 if (statement.prepare() != SQLResultOk) { 357 LOG_ERROR("Failed to prepare statement."); 358 return; 359 } 360 361 int result; 362 while ((result = statement.step()) == SQLResultRow) { 363 if (!canDeleteOrigin(statement.getColumnText(0))) 364 continue; 365 366 SQLiteFileSystem::deleteDatabaseFile(statement.getColumnText(1)); 367 368 { 369 MutexLocker lockClient(m_clientGuard); 370 if (m_client) 371 m_client->dispatchDidModifyOrigin(statement.getColumnText(0)); 372 } 373 } 374 375 if (result != SQLResultDone) 376 LOG_ERROR("Failed to read in all origins from the database."); 377 378 if (m_database.isOpen()) 379 m_database.close(); 380 381 if (!SQLiteFileSystem::deleteDatabaseFile(trackerDatabasePath())) { 382 // In the case where it is not possible to delete the database file (e.g some other program 383 // like a virus scanner is accessing it), make sure to remove all entries. 384 openTrackerDatabase(false); 385 if (!m_database.isOpen()) 386 return; 387 SQLiteStatement deleteStatement(m_database, "DELETE FROM Origins"); 388 if (deleteStatement.prepare() != SQLResultOk) { 389 LOG_ERROR("Unable to prepare deletion of all origins"); 390 return; 391 } 392 if (!deleteStatement.executeCommand()) { 393 LOG_ERROR("Unable to execute deletion of all origins"); 394 return; 395 } 396 } 397 SQLiteFileSystem::deleteEmptyDatabaseDirectory(m_storageDirectoryPath); 398 } 399 400 void StorageTracker::deleteOriginOnMainThread(void* originIdentifier) 401 { 402 ASSERT(isMainThread()); 403 404 String identifier = adoptRef(reinterpret_cast<StringImpl*>(originIdentifier)); 405 tracker().deleteOrigin(identifier); 406 } 407 408 void StorageTracker::deleteOrigin(const String& originIdentifier) 409 { 410 deleteOrigin(SecurityOrigin::createFromDatabaseIdentifier(originIdentifier).get()); 411 } 412 413 void StorageTracker::deleteOrigin(SecurityOrigin* origin) 414 { 415 ASSERT(m_isActive); 416 ASSERT(isMainThread()); 417 ASSERT(m_thread); 418 419 if (!m_isActive) 420 return; 421 422 // Before deleting database, we need to clear in-memory local storage data 423 // in StorageArea, and to close the StorageArea db. It's possible for an 424 // item to be added immediately after closing the db and cause StorageAreaSync 425 // to reopen the db before the db is deleted by a StorageTracker thread. 426 // In this case, reopening the db in StorageAreaSync will cancel a pending 427 // StorageTracker db deletion. 428 PageGroup::clearLocalStorageForOrigin(origin); 429 430 String originId = origin->databaseIdentifier(); 431 432 { 433 MutexLocker lockOrigins(m_originSetGuard); 434 willDeleteOrigin(originId); 435 m_originSet.remove(originId); 436 } 437 438 m_thread->scheduleTask(LocalStorageTask::createDeleteOrigin(originId)); 439 } 440 441 void StorageTracker::syncDeleteOrigin(const String& originIdentifier) 442 { 443 ASSERT(!isMainThread()); 444 445 MutexLocker lockDatabase(m_databaseGuard); 446 447 if (!canDeleteOrigin(originIdentifier)) { 448 LOG_ERROR("Attempted to delete origin '%s' while it was being created\n", originIdentifier.ascii().data()); 449 return; 450 } 451 452 openTrackerDatabase(false); 453 if (!m_database.isOpen()) 454 return; 455 456 // Get origin's db file path, delete entry in tracker's db, then delete db file. 457 SQLiteStatement pathStatement(m_database, "SELECT path FROM Origins WHERE origin=?"); 458 if (pathStatement.prepare() != SQLResultOk) { 459 LOG_ERROR("Unable to prepare selection of path for origin '%s'", originIdentifier.ascii().data()); 460 return; 461 } 462 pathStatement.bindText(1, originIdentifier); 463 int result = pathStatement.step(); 464 if (result != SQLResultRow) { 465 LOG_ERROR("Unable to find origin '%s' in Origins table", originIdentifier.ascii().data()); 466 return; 467 } 468 469 String path = pathStatement.getColumnText(0); 470 471 ASSERT(!path.isEmpty()); 472 473 SQLiteStatement deleteStatement(m_database, "DELETE FROM Origins where origin=?"); 474 if (deleteStatement.prepare() != SQLResultOk) { 475 LOG_ERROR("Unable to prepare deletion of origin '%s'", originIdentifier.ascii().data()); 476 return; 477 } 478 deleteStatement.bindText(1, originIdentifier); 479 if (!deleteStatement.executeCommand()) { 480 LOG_ERROR("Unable to execute deletion of origin '%s'", originIdentifier.ascii().data()); 481 return; 482 } 483 484 SQLiteFileSystem::deleteDatabaseFile(path); 485 486 bool shouldDeleteTrackerFiles = false; 487 { 488 MutexLocker originLock(m_originSetGuard); 489 m_originSet.remove(originIdentifier); 490 shouldDeleteTrackerFiles = m_originSet.isEmpty(); 491 } 492 493 if (shouldDeleteTrackerFiles) { 494 m_database.close(); 495 SQLiteFileSystem::deleteDatabaseFile(trackerDatabasePath()); 496 SQLiteFileSystem::deleteEmptyDatabaseDirectory(m_storageDirectoryPath); 497 } 498 499 { 500 MutexLocker lockClient(m_clientGuard); 501 if (m_client) 502 m_client->dispatchDidModifyOrigin(originIdentifier); 503 } 504 } 505 506 void StorageTracker::willDeleteAllOrigins() 507 { 508 ASSERT(!m_originSetGuard.tryLock()); 509 510 OriginSet::const_iterator end = m_originSet.end(); 511 for (OriginSet::const_iterator it = m_originSet.begin(); it != end; ++it) 512 m_originsBeingDeleted.add((*it).threadsafeCopy()); 513 } 514 515 void StorageTracker::willDeleteOrigin(const String& originIdentifier) 516 { 517 ASSERT(isMainThread()); 518 ASSERT(!m_originSetGuard.tryLock()); 519 520 m_originsBeingDeleted.add(originIdentifier); 521 } 522 523 bool StorageTracker::canDeleteOrigin(const String& originIdentifier) 524 { 525 ASSERT(!m_databaseGuard.tryLock()); 526 MutexLocker lockOrigins(m_originSetGuard); 527 return m_originsBeingDeleted.contains(originIdentifier); 528 } 529 530 void StorageTracker::cancelDeletingOrigin(const String& originIdentifier) 531 { 532 if (!m_isActive) 533 return; 534 535 MutexLocker lockDatabase(m_databaseGuard); 536 MutexLocker lockOrigins(m_originSetGuard); 537 if (!m_originsBeingDeleted.isEmpty()) 538 m_originsBeingDeleted.remove(originIdentifier); 539 } 540 541 void StorageTracker::setClient(StorageTrackerClient* client) 542 { 543 MutexLocker lockClient(m_clientGuard); 544 m_client = client; 545 } 546 547 void StorageTracker::syncLocalStorage() 548 { 549 PageGroup::syncLocalStorage(); 550 } 551 552 bool StorageTracker::isActive() 553 { 554 return m_isActive; 555 } 556 557 void StorageTracker::setIsActive(bool flag) 558 { 559 m_isActive = flag; 560 } 561 562 } // namespace WebCore 563 564 #endif // ENABLE(DOM_STORAGE) 565