/* * Copyright (C) 2012 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "DatabaseManager.h" #include "Database.h" #include "DatabaseCallback.h" #include "DatabaseContext.h" #include "DatabaseTask.h" #include "DatabaseTracker.h" #include "Document.h" #include "InspectorInstrumentation.h" #include "Logging.h" #include "PlatformStrategies.h" #include "ScriptController.h" #include "SecurityOrigin.h" #include "SecurityOriginData.h" #include "WindowEventLoop.h" #include namespace WebCore { class DatabaseManager::ProposedDatabase { public: ProposedDatabase(DatabaseManager&, SecurityOrigin&, const String& name, const String& displayName, unsigned long estimatedSize); ~ProposedDatabase(); SecurityOrigin& origin() { return m_origin; } DatabaseDetails& details() { return m_details; } private: DatabaseManager& m_manager; Ref m_origin; DatabaseDetails m_details; }; DatabaseManager::ProposedDatabase::ProposedDatabase(DatabaseManager& manager, SecurityOrigin& origin, const String& name, const String& displayName, unsigned long estimatedSize) : m_manager(manager) , m_origin(origin.isolatedCopy()) , m_details(name.isolatedCopy(), displayName.isolatedCopy(), estimatedSize, 0, std::nullopt, std::nullopt) { m_manager.addProposedDatabase(*this); } inline DatabaseManager::ProposedDatabase::~ProposedDatabase() { m_manager.removeProposedDatabase(*this); } DatabaseManager& DatabaseManager::singleton() { static NeverDestroyed instance; return instance; } void DatabaseManager::initialize(const String& databasePath) { platformInitialize(databasePath); DatabaseTracker::initializeTracker(databasePath); } void DatabaseManager::setClient(DatabaseManagerClient* client) { m_client = client; DatabaseTracker::singleton().setClient(client); } bool DatabaseManager::isAvailable() { return m_databaseIsAvailable; } void DatabaseManager::setIsAvailable(bool available) { m_databaseIsAvailable = available; } Ref DatabaseManager::databaseContext(Document& document) { if (auto databaseContext = document.databaseContext()) return *databaseContext; return adoptRef(*new DatabaseContext(document)); } #if LOG_DISABLED static inline void logOpenDatabaseError(Document&, const String&) { } #else static void logOpenDatabaseError(Document& document, const String& name) { LOG(StorageAPI, "Database %s for origin %s not allowed to be established", name.utf8().data(), document.securityOrigin().toString().utf8().data()); } #endif ExceptionOr> DatabaseManager::openDatabaseBackend(Document& document, const String& name, const String& expectedVersion, const String& displayName, unsigned estimatedSize, bool setVersionInNewDatabase) { auto backend = tryToOpenDatabaseBackend(document, name, expectedVersion, displayName, estimatedSize, setVersionInNewDatabase, FirstTryToOpenDatabase); if (backend.hasException()) { if (backend.exception().code() == QuotaExceededError) { // Notify the client that we've exceeded the database quota. // The client may want to increase the quota, and we'll give it // one more try after if that is the case. { ProposedDatabase proposedDatabase { *this, document.securityOrigin(), name, displayName, estimatedSize }; this->databaseContext(document)->databaseExceededQuota(name, proposedDatabase.details()); } backend = tryToOpenDatabaseBackend(document, name, expectedVersion, displayName, estimatedSize, setVersionInNewDatabase, RetryOpenDatabase); } } if (backend.hasException()) { if (backend.exception().code() == InvalidStateError) logErrorMessage(document, backend.exception().message()); else logOpenDatabaseError(document, name); } return backend; } ExceptionOr> DatabaseManager::tryToOpenDatabaseBackend(Document& document, const String& name, const String& expectedVersion, const String& displayName, unsigned estimatedSize, bool setVersionInNewDatabase, OpenAttempt attempt) { auto* page = document.page(); if (!page || page->usesEphemeralSession()) return Exception { SecurityError }; auto backendContext = this->databaseContext(document); ExceptionOr preflightResult; switch (attempt) { case FirstTryToOpenDatabase: preflightResult = DatabaseTracker::singleton().canEstablishDatabase(backendContext, name, estimatedSize); break; case RetryOpenDatabase: preflightResult = DatabaseTracker::singleton().retryCanEstablishDatabase(backendContext, name, estimatedSize); break; } if (preflightResult.hasException()) return preflightResult.releaseException(); auto database = adoptRef(*new Database(backendContext, name, expectedVersion, displayName, estimatedSize)); auto openResult = database->openAndVerifyVersion(setVersionInNewDatabase); if (openResult.hasException()) return openResult.releaseException(); // FIXME: What guarantees backendContext.securityOrigin() is non-null? DatabaseTracker::singleton().setDatabaseDetails(backendContext->securityOrigin(), name, displayName, estimatedSize); return database; } void DatabaseManager::addProposedDatabase(ProposedDatabase& database) { Locker locker { m_proposedDatabasesLock }; m_proposedDatabases.add(&database); } void DatabaseManager::removeProposedDatabase(ProposedDatabase& database) { Locker locker { m_proposedDatabasesLock }; m_proposedDatabases.remove(&database); } ExceptionOr> DatabaseManager::openDatabase(Document& document, const String& name, const String& expectedVersion, const String& displayName, unsigned estimatedSize, RefPtr&& creationCallback) { ASSERT(isMainThread()); // FIXME: Remove this call to ScriptController::initializeMainThread(). The // main thread should have been initialized by a WebKit entrypoint already. // Also, initializeMainThread() does nothing on iOS. ScriptController::initializeMainThread(); bool setVersionInNewDatabase = !creationCallback; auto openResult = openDatabaseBackend(document, name, expectedVersion, displayName, estimatedSize, setVersionInNewDatabase); if (openResult.hasException()) return openResult.releaseException(); RefPtr database = openResult.releaseReturnValue(); auto databaseContext = this->databaseContext(document); databaseContext->setHasOpenDatabases(); InspectorInstrumentation::didOpenDatabase(*database); if (database->isNew() && creationCallback.get()) { LOG(StorageAPI, "Scheduling DatabaseCreationCallbackTask for database %p\n", database.get()); database->setHasPendingCreationEvent(true); database->m_document->eventLoop().queueTask(TaskSource::Networking, [creationCallback, database]() { creationCallback->handleEvent(*database); database->setHasPendingCreationEvent(false); }); } return database.releaseNonNull(); } bool DatabaseManager::hasOpenDatabases(Document& document) { auto databaseContext = document.databaseContext(); return databaseContext && databaseContext->hasOpenDatabases(); } void DatabaseManager::stopDatabases(Document& document, DatabaseTaskSynchronizer* synchronizer) { auto databaseContext = document.databaseContext(); if (!databaseContext || !databaseContext->stopDatabases(synchronizer)) { if (synchronizer) synchronizer->taskCompleted(); } } String DatabaseManager::fullPathForDatabase(SecurityOrigin& origin, const String& name, bool createIfDoesNotExist) { { Locker locker { m_proposedDatabasesLock }; for (auto* proposedDatabase : m_proposedDatabases) { if (proposedDatabase->details().name() == name && proposedDatabase->origin().equal(&origin)) return String(); } } return DatabaseTracker::singleton().fullPathForDatabase(origin.data(), name, createIfDoesNotExist); } DatabaseDetails DatabaseManager::detailsForNameAndOrigin(const String& name, SecurityOrigin& origin) { { Locker locker { m_proposedDatabasesLock }; for (auto* proposedDatabase : m_proposedDatabases) { if (proposedDatabase->details().name() == name && proposedDatabase->origin().equal(&origin)) { ASSERT(&proposedDatabase->details().thread() == &Thread::current() || isMainThread()); return proposedDatabase->details(); } } } return DatabaseTracker::singleton().detailsForNameAndOrigin(name, origin.data()); } void DatabaseManager::logErrorMessage(Document& document, const String& message) { document.addConsoleMessage(MessageSource::Storage, MessageLevel::Error, message); } #if !PLATFORM(COCOA) void DatabaseManager::platformInitialize(const String& databasePath) { UNUSED_PARAM(databasePath); } #endif } // namespace WebCore