| // 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_CURRENTLY_ON(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_CURRENTLY_ON(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_CURRENTLY_ON(BrowserThread::UI); |
| ContentBrowserClient* client = GetContentClient()->browser(); |
| RenderProcessHost* host = RenderProcessHost::FromID(render_process_id); |
| if (!host) |
| return false; |
| return client->IsPluginAllowedToCallRequestOSFileHandle( |
| host->GetBrowserContext(), document_url); |
| } |
| |
| bool FileOpenForWrite(int32_t open_flags) { |
| return (open_flags & (PP_FILEOPENFLAG_WRITE | PP_FILEOPENFLAG_APPEND)) != 0; |
| } |
| |
| void FileCloser(base::File auto_close) { |
| } |
| |
| void DidCloseFile(const base::Closure& on_close_callback) { |
| if (!on_close_callback.is_null()) |
| on_close_callback.Run(); |
| } |
| |
| void DidOpenFile(base::WeakPtr<PepperFileIOHost> file_host, |
| fileapi::FileSystemOperation::OpenFileCallback callback, |
| base::File file, |
| const base::Closure& on_close_callback) { |
| if (file_host) { |
| callback.Run(file.Pass(), on_close_callback); |
| } else { |
| BrowserThread::PostTaskAndReply( |
| BrowserThread::FILE, |
| FROM_HERE, |
| base::Bind(&FileCloser, base::Passed(&file)), |
| base::Bind(&DidCloseFile, on_close_callback)); |
| } |
| } |
| |
| } // 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_(BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE)), |
| open_flags_(0), |
| file_system_type_(PP_FILESYSTEMTYPE_INVALID), |
| max_written_offset_(0), |
| check_quota_(false) { |
| int unused; |
| if (!host->GetRenderFrameIDsForInstance( |
| instance, &render_process_id_, &unused)) { |
| render_process_id_ = -1; |
| } |
| } |
| |
| PepperFileIOHost::~PepperFileIOHost() {} |
| |
| int32_t PepperFileIOHost::OnResourceMessageReceived( |
| const IPC::Message& msg, |
| ppapi::host::HostMessageContext* context) { |
| PPAPI_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) |
| PPAPI_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<PepperFileRefHost*>(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(); |
| |
| // For external file systems, if there is a valid FileSystemURL, then treat |
| // it like internal file systems and access it via the FileSystemURL. |
| bool is_internal_type = (file_system_type_ != PP_FILESYSTEMTYPE_EXTERNAL) || |
| file_system_url_.is_valid(); |
| |
| if (is_internal_type) { |
| if (!file_system_url_.is_valid()) |
| return PP_ERROR_BADARGUMENT; |
| |
| // Not all external file systems are fully supported yet. |
| // Whitelist the supported ones. |
| if (file_system_url_.mount_type() == fileapi::kFileSystemTypeExternal) { |
| switch (file_system_url_.type()) { |
| case fileapi::kFileSystemTypeNativeMedia: |
| case fileapi::kFileSystemTypeDeviceMedia: |
| case fileapi::kFileSystemTypePicasa: |
| case fileapi::kFileSystemTypeItunes: |
| case fileapi::kFileSystemTypeIphoto: |
| break; |
| default: |
| return PP_ERROR_NOACCESS; |
| } |
| } |
| 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, |
| AsWeakPtr(), |
| 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, |
| AsWeakPtr(), |
| 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_CURRENTLY_ON(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(&DidOpenFile, |
| AsWeakPtr(), |
| base::Bind(&PepperFileIOHost::DidOpenInternalFile, |
| AsWeakPtr(), |
| reply_context))); |
| } |
| |
| void PepperFileIOHost::DidOpenInternalFile( |
| ppapi::host::ReplyMessageContext reply_context, |
| base::File file, |
| const base::Closure& on_close_callback) { |
| if (file.IsValid()) { |
| 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, |
| AsWeakPtr(), |
| reply_context, |
| base::Passed(&file))); |
| return; |
| } |
| } |
| |
| DCHECK(!file_.IsValid()); |
| base::File::Error error = |
| file.IsValid() ? base::File::FILE_OK : file.error_details(); |
| file_.SetFile(file.Pass()); |
| OnOpenProxyCallback(reply_context, error); |
| } |
| |
| void PepperFileIOHost::GotResolvedRenderProcessId( |
| ppapi::host::ReplyMessageContext reply_context, |
| base::FilePath path, |
| int file_flags, |
| base::ProcessId resolved_render_process_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| resolved_render_process_id_ = resolved_render_process_id; |
| file_.CreateOrOpen( |
| path, |
| file_flags, |
| base::Bind(&PepperFileIOHost::OnOpenProxyCallback, |
| AsWeakPtr(), |
| 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 (!file_.SetTimes( |
| PPTimeToTime(last_access_time), |
| PPTimeToTime(last_modified_time), |
| base::Bind(&PepperFileIOHost::ExecutePlatformGeneralCallback, |
| AsWeakPtr(), |
| 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 (!file_.SetLength( |
| length, |
| base::Bind(&PepperFileIOHost::ExecutePlatformGeneralCallback, |
| AsWeakPtr(), |
| 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 (!file_.Flush( |
| base::Bind(&PepperFileIOHost::ExecutePlatformGeneralCallback, |
| AsWeakPtr(), |
| 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_.IsValid()) { |
| file_.Close(base::Bind(&PepperFileIOHost::DidCloseFile, |
| AsWeakPtr())); |
| } |
| return PP_OK; |
| } |
| |
| void PepperFileIOHost::DidOpenQuotaFile( |
| ppapi::host::ReplyMessageContext reply_context, |
| base::File file, |
| int64_t max_written_offset) { |
| DCHECK(!file_.IsValid()); |
| DCHECK(file.IsValid()); |
| max_written_offset_ = max_written_offset; |
| file_.SetFile(file.Pass()); |
| |
| OnOpenProxyCallback(reply_context, base::File::FILE_OK); |
| } |
| |
| 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, |
| AsWeakPtr(), |
| context->MakeReplyMessageContext())); |
| return PP_OK_COMPLETIONPENDING; |
| } |
| |
| void PepperFileIOHost::GotPluginAllowedToCallRequestOSFileHandle( |
| ppapi::host::ReplyMessageContext reply_context, |
| bool plugin_allowed) { |
| DCHECK_CURRENTLY_ON(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::OnOpenProxyCallback( |
| ppapi::host::ReplyMessageContext reply_context, |
| base::File::Error error_code) { |
| int32_t pp_error = ppapi::FileErrorToPepperError(error_code); |
| if (file_.IsValid() && !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_.GetPlatformFile(), 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 |