// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "content/browser/renderer_host/database_message_filter.h" #include #include #include "base/bind.h" #include "base/metrics/histogram.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "base/threading/thread.h" #include "base/trace_event/trace_event.h" #include "build/build_config.h" #include "content/browser/bad_message.h" #include "content/common/database_messages.h" #include "content/public/browser/user_metrics.h" #include "content/public/common/origin_util.h" #include "content/public/common/result_codes.h" #include "storage/browser/database/database_util.h" #include "storage/browser/database/vfs_backend.h" #include "storage/browser/quota/quota_manager.h" #include "storage/browser/quota/quota_manager_proxy.h" #include "storage/common/database/database_identifier.h" #include "third_party/sqlite/sqlite3.h" #if defined(OS_POSIX) #include "base/file_descriptor_posix.h" #endif using storage::QuotaManager; using storage::QuotaStatusCode; using storage::DatabaseTracker; using storage::DatabaseUtil; using storage::VfsBackend; namespace content { namespace { const int kNumDeleteRetries = 2; const int kDelayDeleteRetryMs = 100; } // namespace DatabaseMessageFilter::DatabaseMessageFilter( storage::DatabaseTracker* db_tracker) : BrowserMessageFilter(DatabaseMsgStart), db_tracker_(db_tracker), observer_added_(false) { DCHECK(db_tracker_.get()); } void DatabaseMessageFilter::OnChannelClosing() { if (observer_added_) { observer_added_ = false; BrowserThread::PostTask( BrowserThread::FILE, FROM_HERE, base::Bind(&DatabaseMessageFilter::RemoveObserver, this)); } } void DatabaseMessageFilter::AddObserver() { DCHECK_CURRENTLY_ON(BrowserThread::FILE); db_tracker_->AddObserver(this); } void DatabaseMessageFilter::RemoveObserver() { DCHECK_CURRENTLY_ON(BrowserThread::FILE); db_tracker_->RemoveObserver(this); // If the renderer process died without closing all databases, // then we need to manually close those connections db_tracker_->CloseDatabases(database_connections_); database_connections_.RemoveAllConnections(); } void DatabaseMessageFilter::OverrideThreadForMessage( const IPC::Message& message, BrowserThread::ID* thread) { if (message.type() == DatabaseHostMsg_GetSpaceAvailable::ID) *thread = BrowserThread::IO; else if (IPC_MESSAGE_CLASS(message) == DatabaseMsgStart) *thread = BrowserThread::FILE; if (message.type() == DatabaseHostMsg_Opened::ID && !observer_added_) { observer_added_ = true; BrowserThread::PostTask( BrowserThread::FILE, FROM_HERE, base::Bind(&DatabaseMessageFilter::AddObserver, this)); } } bool DatabaseMessageFilter::OnMessageReceived(const IPC::Message& message) { bool handled = true; IPC_BEGIN_MESSAGE_MAP(DatabaseMessageFilter, message) IPC_MESSAGE_HANDLER(DatabaseHostMsg_OpenFile, OnDatabaseOpenFile) IPC_MESSAGE_HANDLER_DELAY_REPLY(DatabaseHostMsg_DeleteFile, OnDatabaseDeleteFile) IPC_MESSAGE_HANDLER(DatabaseHostMsg_GetFileAttributes, OnDatabaseGetFileAttributes) IPC_MESSAGE_HANDLER(DatabaseHostMsg_GetFileSize, OnDatabaseGetFileSize) IPC_MESSAGE_HANDLER_DELAY_REPLY(DatabaseHostMsg_GetSpaceAvailable, OnDatabaseGetSpaceAvailable) IPC_MESSAGE_HANDLER(DatabaseHostMsg_SetFileSize, OnDatabaseSetFileSize) IPC_MESSAGE_HANDLER(DatabaseHostMsg_Opened, OnDatabaseOpened) IPC_MESSAGE_HANDLER(DatabaseHostMsg_Modified, OnDatabaseModified) IPC_MESSAGE_HANDLER(DatabaseHostMsg_Closed, OnDatabaseClosed) IPC_MESSAGE_HANDLER(DatabaseHostMsg_HandleSqliteError, OnHandleSqliteError) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() return handled; } DatabaseMessageFilter::~DatabaseMessageFilter() { } void DatabaseMessageFilter::OnDatabaseOpenFile( const base::string16& vfs_file_name, int desired_flags, IPC::PlatformFileForTransit* handle) { DCHECK_CURRENTLY_ON(BrowserThread::FILE); base::File file; const base::File* tracked_file = NULL; std::string origin_identifier; base::string16 database_name; // When in incognito mode, we want to make sure that all DB files are // removed when the incognito browser context goes away, so we add the // SQLITE_OPEN_DELETEONCLOSE flag when opening all files, and keep // open handles to them in the database tracker to make sure they're // around for as long as needed. if (vfs_file_name.empty()) { file = VfsBackend::OpenTempFileInDirectory(db_tracker_->DatabaseDirectory(), desired_flags); } else if (DatabaseUtil::CrackVfsFileName(vfs_file_name, &origin_identifier, &database_name, NULL) && !db_tracker_->IsDatabaseScheduledForDeletion(origin_identifier, database_name)) { base::FilePath db_file = DatabaseUtil::GetFullFilePathForVfsFile( db_tracker_.get(), vfs_file_name); if (!db_file.empty()) { if (db_tracker_->IsIncognitoProfile()) { tracked_file = db_tracker_->GetIncognitoFile(vfs_file_name); if (!tracked_file) { file = VfsBackend::OpenFile(db_file, desired_flags | SQLITE_OPEN_DELETEONCLOSE); if (!(desired_flags & SQLITE_OPEN_DELETEONCLOSE)) { tracked_file = db_tracker_->SaveIncognitoFile(vfs_file_name, std::move(file)); } } } else { file = VfsBackend::OpenFile(db_file, desired_flags); } } } // Then we duplicate the file handle to make it useable in the renderer // process. The original handle is closed, unless we saved it in the // database tracker. *handle = IPC::InvalidPlatformFileForTransit(); if (file.IsValid()) { *handle = IPC::TakeFileHandleForProcess(std::move(file), PeerHandle()); } else if (tracked_file) { DCHECK(tracked_file->IsValid()); *handle = IPC::GetFileHandleForProcess(tracked_file->GetPlatformFile(), PeerHandle(), false); } } void DatabaseMessageFilter::OnDatabaseDeleteFile( const base::string16& vfs_file_name, const bool& sync_dir, IPC::Message* reply_msg) { DatabaseDeleteFile(vfs_file_name, sync_dir, reply_msg, kNumDeleteRetries); } void DatabaseMessageFilter::DatabaseDeleteFile( const base::string16& vfs_file_name, bool sync_dir, IPC::Message* reply_msg, int reschedule_count) { DCHECK_CURRENTLY_ON(BrowserThread::FILE); // Return an error if the file name is invalid or if the file could not // be deleted after kNumDeleteRetries attempts. int error_code = SQLITE_IOERR_DELETE; base::FilePath db_file = DatabaseUtil::GetFullFilePathForVfsFile(db_tracker_.get(), vfs_file_name); if (!db_file.empty()) { // In order to delete a journal file in incognito mode, we only need to // close the open handle to it that's stored in the database tracker. if (db_tracker_->IsIncognitoProfile()) { const base::string16 wal_suffix(base::ASCIIToUTF16("-wal")); base::string16 sqlite_suffix; // WAL files can be deleted without having previously been opened. if (!db_tracker_->HasSavedIncognitoFileHandle(vfs_file_name) && DatabaseUtil::CrackVfsFileName(vfs_file_name, NULL, NULL, &sqlite_suffix) && sqlite_suffix == wal_suffix) { error_code = SQLITE_OK; } else { db_tracker_->CloseIncognitoFileHandle(vfs_file_name); error_code = SQLITE_OK; } } else { error_code = VfsBackend::DeleteFile(db_file, sync_dir); } if ((error_code == SQLITE_IOERR_DELETE) && reschedule_count) { // If the file could not be deleted, try again. BrowserThread::PostDelayedTask( BrowserThread::FILE, FROM_HERE, base::Bind(&DatabaseMessageFilter::DatabaseDeleteFile, this, vfs_file_name, sync_dir, reply_msg, reschedule_count - 1), base::TimeDelta::FromMilliseconds(kDelayDeleteRetryMs)); return; } } DatabaseHostMsg_DeleteFile::WriteReplyParams(reply_msg, error_code); Send(reply_msg); } void DatabaseMessageFilter::OnDatabaseGetFileAttributes( const base::string16& vfs_file_name, int32_t* attributes) { DCHECK_CURRENTLY_ON(BrowserThread::FILE); *attributes = -1; base::FilePath db_file = DatabaseUtil::GetFullFilePathForVfsFile(db_tracker_.get(), vfs_file_name); if (!db_file.empty()) *attributes = VfsBackend::GetFileAttributes(db_file); } void DatabaseMessageFilter::OnDatabaseGetFileSize( const base::string16& vfs_file_name, int64_t* size) { DCHECK_CURRENTLY_ON(BrowserThread::FILE); *size = 0; base::FilePath db_file = DatabaseUtil::GetFullFilePathForVfsFile(db_tracker_.get(), vfs_file_name); if (!db_file.empty()) *size = VfsBackend::GetFileSize(db_file); } void DatabaseMessageFilter::OnDatabaseGetSpaceAvailable( const std::string& origin_identifier, IPC::Message* reply_msg) { DCHECK_CURRENTLY_ON(BrowserThread::IO); DCHECK(db_tracker_->quota_manager_proxy()); QuotaManager* quota_manager = db_tracker_->quota_manager_proxy()->quota_manager(); if (!quota_manager) { NOTREACHED(); // The system is shutting down, messages are unexpected. DatabaseHostMsg_GetSpaceAvailable::WriteReplyParams( reply_msg, static_cast(0)); Send(reply_msg); return; } // crbug.com/349708 TRACE_EVENT0("io", "DatabaseMessageFilter::OnDatabaseGetSpaceAvailable"); quota_manager->GetUsageAndQuota( storage::GetOriginFromIdentifier(origin_identifier), storage::kStorageTypeTemporary, base::Bind( &DatabaseMessageFilter::OnDatabaseGetUsageAndQuota, this, reply_msg)); } void DatabaseMessageFilter::OnDatabaseGetUsageAndQuota( IPC::Message* reply_msg, storage::QuotaStatusCode status, int64_t usage, int64_t quota) { int64_t available = 0; if ((status == storage::kQuotaStatusOk) && (usage < quota)) available = quota - usage; DatabaseHostMsg_GetSpaceAvailable::WriteReplyParams(reply_msg, available); Send(reply_msg); } void DatabaseMessageFilter::OnDatabaseSetFileSize( const base::string16& vfs_file_name, int64_t size, bool* success) { DCHECK_CURRENTLY_ON(BrowserThread::FILE); *success = false; base::FilePath db_file = DatabaseUtil::GetFullFilePathForVfsFile(db_tracker_.get(), vfs_file_name); if (!db_file.empty()) *success = VfsBackend::SetFileSize(db_file, size); } void DatabaseMessageFilter::OnDatabaseOpened( const std::string& origin_identifier, const base::string16& database_name, const base::string16& description, int64_t estimated_size) { DCHECK_CURRENTLY_ON(BrowserThread::FILE); if (!storage::IsValidOriginIdentifier(origin_identifier)) { bad_message::ReceivedBadMessage(this, bad_message::DBMF_INVALID_ORIGIN_ON_OPEN); return; } UMA_HISTOGRAM_BOOLEAN( "websql.OpenDatabase", IsOriginSecure(storage::GetOriginFromIdentifier(origin_identifier))); int64_t database_size = 0; db_tracker_->DatabaseOpened(origin_identifier, database_name, description, estimated_size, &database_size); database_connections_.AddConnection(origin_identifier, database_name); Send(new DatabaseMsg_UpdateSize(origin_identifier, database_name, database_size)); } void DatabaseMessageFilter::OnDatabaseModified( const std::string& origin_identifier, const base::string16& database_name) { DCHECK_CURRENTLY_ON(BrowserThread::FILE); if (!database_connections_.IsDatabaseOpened( origin_identifier, database_name)) { bad_message::ReceivedBadMessage(this, bad_message::DBMF_DB_NOT_OPEN_ON_MODIFY); return; } db_tracker_->DatabaseModified(origin_identifier, database_name); } void DatabaseMessageFilter::OnDatabaseClosed( const std::string& origin_identifier, const base::string16& database_name) { DCHECK_CURRENTLY_ON(BrowserThread::FILE); if (!database_connections_.IsDatabaseOpened( origin_identifier, database_name)) { bad_message::ReceivedBadMessage(this, bad_message::DBMF_DB_NOT_OPEN_ON_CLOSE); return; } database_connections_.RemoveConnection(origin_identifier, database_name); db_tracker_->DatabaseClosed(origin_identifier, database_name); } void DatabaseMessageFilter::OnHandleSqliteError( const std::string& origin_identifier, const base::string16& database_name, int error) { DCHECK_CURRENTLY_ON(BrowserThread::FILE); if (!storage::IsValidOriginIdentifier(origin_identifier)) { bad_message::ReceivedBadMessage( this, bad_message::DBMF_INVALID_ORIGIN_ON_SQLITE_ERROR); return; } db_tracker_->HandleSqliteError(origin_identifier, database_name, error); } void DatabaseMessageFilter::OnDatabaseSizeChanged( const std::string& origin_identifier, const base::string16& database_name, int64_t database_size) { DCHECK_CURRENTLY_ON(BrowserThread::FILE); if (database_connections_.IsOriginUsed(origin_identifier)) { Send(new DatabaseMsg_UpdateSize(origin_identifier, database_name, database_size)); } } void DatabaseMessageFilter::OnDatabaseScheduledForDeletion( const std::string& origin_identifier, const base::string16& database_name) { DCHECK_CURRENTLY_ON(BrowserThread::FILE); Send(new DatabaseMsg_CloseImmediately(origin_identifier, database_name)); } } // namespace content