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