// Copyright 2013 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/pepper/pepper_file_io_host.h" #include "base/bind.h" #include "base/callback.h" #include "base/callback_helpers.h" #include "base/files/file_util_proxy.h" #include "base/memory/weak_ptr.h" #include "content/browser/renderer_host/pepper/pepper_file_ref_host.h" #include "content/browser/renderer_host/pepper/pepper_file_system_browser_host.h" #include "content/browser/renderer_host/pepper/pepper_security_helper.h" #include "content/common/fileapi/file_system_messages.h" #include "content/common/sandbox_util.h" #include "content/common/view_messages.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/content_browser_client.h" #include "content/public/browser/render_process_host.h" #include "content/public/browser/storage_partition.h" #include "content/public/common/content_client.h" #include "ppapi/c/pp_errors.h" #include "ppapi/c/ppb_file_io.h" #include "ppapi/host/dispatch_host_message.h" #include "ppapi/host/ppapi_host.h" #include "ppapi/proxy/ppapi_messages.h" #include "ppapi/shared_impl/file_system_util.h" #include "ppapi/shared_impl/file_type_conversion.h" #include "ppapi/shared_impl/time_conversion.h" #include "webkit/browser/fileapi/file_observers.h" #include "webkit/browser/fileapi/file_system_context.h" #include "webkit/browser/fileapi/file_system_operation_runner.h" #include "webkit/browser/fileapi/task_runner_bound_observer_list.h" #include "webkit/common/fileapi/file_system_util.h" namespace content { using ppapi::FileIOStateManager; using ppapi::PPTimeToTime; namespace { PepperFileIOHost::UIThreadStuff GetUIThreadStuffForInternalFileSystems(int render_process_id) { PepperFileIOHost::UIThreadStuff stuff; DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); RenderProcessHost* host = RenderProcessHost::FromID(render_process_id); if (host) { stuff.resolved_render_process_id = base::GetProcId(host->GetHandle()); StoragePartition* storage_partition = host->GetStoragePartition(); if (storage_partition) stuff.file_system_context = storage_partition->GetFileSystemContext(); } return stuff; } base::ProcessId GetResolvedRenderProcessId(int render_process_id) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); RenderProcessHost* host = RenderProcessHost::FromID(render_process_id); if (!host) return base::kNullProcessId; return base::GetProcId(host->GetHandle()); } bool GetPluginAllowedToCallRequestOSFileHandle(int render_process_id, const GURL& document_url) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); ContentBrowserClient* client = GetContentClient()->browser(); RenderProcessHost* host = RenderProcessHost::FromID(render_process_id); return client->IsPluginAllowedToCallRequestOSFileHandle( host->GetBrowserContext(), document_url); } bool FileOpenForWrite(int32_t open_flags) { return (open_flags & (PP_FILEOPENFLAG_WRITE | PP_FILEOPENFLAG_APPEND)) != 0; } } // namespace PepperFileIOHost::PepperFileIOHost(BrowserPpapiHostImpl* host, PP_Instance instance, PP_Resource resource) : ResourceHost(host->GetPpapiHost(), instance, resource), browser_ppapi_host_(host), render_process_host_(NULL), file_(base::kInvalidPlatformFileValue), open_flags_(0), file_system_type_(PP_FILESYSTEMTYPE_INVALID), max_written_offset_(0), check_quota_(false), weak_factory_(this) { int unused; if (!host->GetRenderFrameIDsForInstance(instance, &render_process_id_, &unused)) { render_process_id_ = -1; } file_message_loop_ = BrowserThread::GetMessageLoopProxyForThread( BrowserThread::FILE); } PepperFileIOHost::~PepperFileIOHost() { } int32_t PepperFileIOHost::OnResourceMessageReceived( const IPC::Message& msg, ppapi::host::HostMessageContext* context) { IPC_BEGIN_MESSAGE_MAP(PepperFileIOHost, msg) PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_FileIO_Open, OnHostMsgOpen) PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_FileIO_Touch, OnHostMsgTouch) PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_FileIO_SetLength, OnHostMsgSetLength) PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(PpapiHostMsg_FileIO_Flush, OnHostMsgFlush) PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_FileIO_Close, OnHostMsgClose) PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(PpapiHostMsg_FileIO_RequestOSFileHandle, OnHostMsgRequestOSFileHandle) IPC_END_MESSAGE_MAP() return PP_ERROR_FAILED; } PepperFileIOHost::UIThreadStuff::UIThreadStuff() { resolved_render_process_id = base::kNullProcessId; } PepperFileIOHost::UIThreadStuff::~UIThreadStuff() { } int32_t PepperFileIOHost::OnHostMsgOpen( ppapi::host::HostMessageContext* context, PP_Resource file_ref_resource, int32_t open_flags) { int32_t rv = state_manager_.CheckOperationState( FileIOStateManager::OPERATION_EXCLUSIVE, false); if (rv != PP_OK) return rv; int platform_file_flags = 0; if (!ppapi::PepperFileOpenFlagsToPlatformFileFlags(open_flags, &platform_file_flags)) return PP_ERROR_BADARGUMENT; ppapi::host::ResourceHost* resource_host = host()->GetResourceHost(file_ref_resource); if (!resource_host || !resource_host->IsFileRefHost()) return PP_ERROR_BADRESOURCE; PepperFileRefHost* file_ref_host = static_cast(resource_host); if (file_ref_host->GetFileSystemType() == PP_FILESYSTEMTYPE_INVALID) return PP_ERROR_FAILED; file_system_host_ = file_ref_host->GetFileSystemHost(); open_flags_ = open_flags; file_system_type_ = file_ref_host->GetFileSystemType(); file_system_url_ = file_ref_host->GetFileSystemURL(); if (file_system_type_ != PP_FILESYSTEMTYPE_EXTERNAL) { if (!file_system_url_.is_valid()) return PP_ERROR_BADARGUMENT; if (!CanOpenFileSystemURLWithPepperFlags(open_flags, render_process_id_, file_system_url_)) return PP_ERROR_NOACCESS; BrowserThread::PostTaskAndReplyWithResult( BrowserThread::UI, FROM_HERE, base::Bind(&GetUIThreadStuffForInternalFileSystems, render_process_id_), base::Bind(&PepperFileIOHost::GotUIThreadStuffForInternalFileSystems, weak_factory_.GetWeakPtr(), context->MakeReplyMessageContext(), platform_file_flags)); } else { base::FilePath path = file_ref_host->GetExternalFilePath(); if (!CanOpenWithPepperFlags(open_flags, render_process_id_, path)) return PP_ERROR_NOACCESS; BrowserThread::PostTaskAndReplyWithResult( BrowserThread::UI, FROM_HERE, base::Bind(&GetResolvedRenderProcessId, render_process_id_), base::Bind(&PepperFileIOHost::GotResolvedRenderProcessId, weak_factory_.GetWeakPtr(), context->MakeReplyMessageContext(), path, platform_file_flags)); } state_manager_.SetPendingOperation(FileIOStateManager::OPERATION_EXCLUSIVE); return PP_OK_COMPLETIONPENDING; } void PepperFileIOHost::GotUIThreadStuffForInternalFileSystems( ppapi::host::ReplyMessageContext reply_context, int platform_file_flags, UIThreadStuff ui_thread_stuff) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); file_system_context_ = ui_thread_stuff.file_system_context; resolved_render_process_id_ = ui_thread_stuff.resolved_render_process_id; if (resolved_render_process_id_ == base::kNullProcessId || !file_system_context_.get()) { reply_context.params.set_result(PP_ERROR_FAILED); SendOpenErrorReply(reply_context); return; } if (!file_system_context_->GetFileSystemBackend(file_system_url_.type())) { reply_context.params.set_result(PP_ERROR_FAILED); SendOpenErrorReply(reply_context); return; } DCHECK(file_system_host_.get()); DCHECK(file_system_host_->GetFileSystemOperationRunner()); file_system_host_->GetFileSystemOperationRunner()->OpenFile( file_system_url_, platform_file_flags, base::Bind(&PepperFileIOHost::DidOpenInternalFile, weak_factory_.GetWeakPtr(), reply_context)); } void PepperFileIOHost::DidOpenInternalFile( ppapi::host::ReplyMessageContext reply_context, base::File::Error result, base::PlatformFile file, const base::Closure& on_close_callback) { if (result == base::File::FILE_OK) { on_close_callback_ = on_close_callback; if (FileOpenForWrite(open_flags_) && file_system_host_->ChecksQuota()) { check_quota_ = true; file_system_host_->OpenQuotaFile( this, file_system_url_, base::Bind(&PepperFileIOHost::DidOpenQuotaFile, weak_factory_.GetWeakPtr(), reply_context, file)); return; } } ExecutePlatformOpenFileCallback( reply_context, result, base::PassPlatformFile(&file), true); } void PepperFileIOHost::GotResolvedRenderProcessId( ppapi::host::ReplyMessageContext reply_context, base::FilePath path, int platform_file_flags, base::ProcessId resolved_render_process_id) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); resolved_render_process_id_ = resolved_render_process_id; base::FileUtilProxy::CreateOrOpen( file_message_loop_, path, platform_file_flags, base::Bind(&PepperFileIOHost::ExecutePlatformOpenFileCallback, weak_factory_.GetWeakPtr(), reply_context)); } int32_t PepperFileIOHost::OnHostMsgTouch( ppapi::host::HostMessageContext* context, PP_Time last_access_time, PP_Time last_modified_time) { int32_t rv = state_manager_.CheckOperationState( FileIOStateManager::OPERATION_EXCLUSIVE, true); if (rv != PP_OK) return rv; if (!base::FileUtilProxy::Touch( file_message_loop_, file_, PPTimeToTime(last_access_time), PPTimeToTime(last_modified_time), base::Bind(&PepperFileIOHost::ExecutePlatformGeneralCallback, weak_factory_.GetWeakPtr(), context->MakeReplyMessageContext()))) return PP_ERROR_FAILED; state_manager_.SetPendingOperation(FileIOStateManager::OPERATION_EXCLUSIVE); return PP_OK_COMPLETIONPENDING; } int32_t PepperFileIOHost::OnHostMsgSetLength( ppapi::host::HostMessageContext* context, int64_t length) { int32_t rv = state_manager_.CheckOperationState( FileIOStateManager::OPERATION_EXCLUSIVE, true); if (rv != PP_OK) return rv; if (length < 0) return PP_ERROR_BADARGUMENT; // Quota checks are performed on the plugin side, in order to use the same // quota reservation and request system as Write. if (!base::FileUtilProxy::Truncate( file_message_loop_, file_, length, base::Bind(&PepperFileIOHost::ExecutePlatformGeneralCallback, weak_factory_.GetWeakPtr(), context->MakeReplyMessageContext()))) return PP_ERROR_FAILED; state_manager_.SetPendingOperation(FileIOStateManager::OPERATION_EXCLUSIVE); return PP_OK_COMPLETIONPENDING; } int32_t PepperFileIOHost::OnHostMsgFlush( ppapi::host::HostMessageContext* context) { int32_t rv = state_manager_.CheckOperationState( FileIOStateManager::OPERATION_EXCLUSIVE, true); if (rv != PP_OK) return rv; if (!base::FileUtilProxy::Flush( file_message_loop_, file_, base::Bind(&PepperFileIOHost::ExecutePlatformGeneralCallback, weak_factory_.GetWeakPtr(), context->MakeReplyMessageContext()))) return PP_ERROR_FAILED; state_manager_.SetPendingOperation(FileIOStateManager::OPERATION_EXCLUSIVE); return PP_OK_COMPLETIONPENDING; } int32_t PepperFileIOHost::OnHostMsgClose( ppapi::host::HostMessageContext* context, const ppapi::FileGrowth& file_growth) { if (check_quota_) { file_system_host_->CloseQuotaFile(this, file_growth); check_quota_ = false; } if (file_ != base::kInvalidPlatformFileValue) { base::FileUtilProxy::Close( file_message_loop_, file_, base::Bind(&PepperFileIOHost::DidCloseFile, weak_factory_.GetWeakPtr())); file_ = base::kInvalidPlatformFileValue; } return PP_OK; } void PepperFileIOHost::DidOpenQuotaFile( ppapi::host::ReplyMessageContext reply_context, base::PlatformFile file, int64_t max_written_offset) { max_written_offset_ = max_written_offset; ExecutePlatformOpenFileCallback( reply_context, base::File::FILE_OK, base::PassPlatformFile(&file), true); } void PepperFileIOHost::DidCloseFile(base::File::Error error) { // Silently ignore if we fail to close the file. if (!on_close_callback_.is_null()) { on_close_callback_.Run(); on_close_callback_.Reset(); } } int32_t PepperFileIOHost::OnHostMsgRequestOSFileHandle( ppapi::host::HostMessageContext* context) { if (open_flags_ != PP_FILEOPENFLAG_READ && file_system_host_->ChecksQuota()) return PP_ERROR_FAILED; GURL document_url = browser_ppapi_host_->GetDocumentURLForInstance(pp_instance()); BrowserThread::PostTaskAndReplyWithResult( BrowserThread::UI, FROM_HERE, base::Bind(&GetPluginAllowedToCallRequestOSFileHandle, render_process_id_, document_url), base::Bind(&PepperFileIOHost::GotPluginAllowedToCallRequestOSFileHandle, weak_factory_.GetWeakPtr(), context->MakeReplyMessageContext())); return PP_OK_COMPLETIONPENDING; } void PepperFileIOHost::GotPluginAllowedToCallRequestOSFileHandle( ppapi::host::ReplyMessageContext reply_context, bool plugin_allowed) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); if (!browser_ppapi_host_->external_plugin() || host()->permissions().HasPermission(ppapi::PERMISSION_PRIVATE) || plugin_allowed) { if (!AddFileToReplyContext(open_flags_, &reply_context)) reply_context.params.set_result(PP_ERROR_FAILED); } else { reply_context.params.set_result(PP_ERROR_NOACCESS); } host()->SendReply(reply_context, PpapiPluginMsg_FileIO_RequestOSFileHandleReply()); } void PepperFileIOHost::ExecutePlatformGeneralCallback( ppapi::host::ReplyMessageContext reply_context, base::File::Error error_code) { reply_context.params.set_result( ppapi::FileErrorToPepperError(error_code)); host()->SendReply(reply_context, PpapiPluginMsg_FileIO_GeneralReply()); state_manager_.SetOperationFinished(); } void PepperFileIOHost::ExecutePlatformOpenFileCallback( ppapi::host::ReplyMessageContext reply_context, base::File::Error error_code, base::PassPlatformFile file, bool unused_created) { int32_t pp_error = ppapi::FileErrorToPepperError(error_code); DCHECK(file_ == base::kInvalidPlatformFileValue); file_ = file.ReleaseValue(); if (file_ != base::kInvalidPlatformFileValue && !AddFileToReplyContext(open_flags_, &reply_context)) pp_error = PP_ERROR_FAILED; PP_Resource quota_file_system = 0; if (pp_error == PP_OK) { state_manager_.SetOpenSucceed(); // A non-zero resource id signals the plugin side to check quota. if (check_quota_) quota_file_system = file_system_host_->pp_resource(); } reply_context.params.set_result(pp_error); host()->SendReply(reply_context, PpapiPluginMsg_FileIO_OpenReply(quota_file_system, max_written_offset_)); state_manager_.SetOperationFinished(); } void PepperFileIOHost::SendOpenErrorReply( ppapi::host::ReplyMessageContext reply_context) { host()->SendReply(reply_context, PpapiPluginMsg_FileIO_OpenReply(0, 0)); } bool PepperFileIOHost::AddFileToReplyContext( int32_t open_flags, ppapi::host::ReplyMessageContext* reply_context) const { base::ProcessId plugin_process_id = base::GetProcId(browser_ppapi_host_->GetPluginProcessHandle()); if (plugin_process_id == base::kNullProcessId) plugin_process_id = resolved_render_process_id_; IPC::PlatformFileForTransit transit_file = BrokerGetFileHandleForProcess( file_, plugin_process_id, false); if (transit_file == IPC::InvalidPlatformFileForTransit()) return false; ppapi::proxy::SerializedHandle file_handle; // A non-zero resource id signals NaClIPCAdapter to create a NaClQuotaDesc. PP_Resource quota_file_io = check_quota_ ? pp_resource() : 0; file_handle.set_file_handle(transit_file, open_flags, quota_file_io); reply_context->params.AppendHandle(file_handle); return true; } } // namespace content