1 // Copyright 2013 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/browser/renderer_host/pepper/pepper_file_system_browser_host.h"
8 #include "base/callback.h"
9 #include "base/logging.h"
10 #include "base/task/task_runner_util.h"
11 #include "base/threading/sequenced_task_runner_handle.h"
12 #include "base/threading/thread_task_runner_handle.h"
13 #include "content/browser/renderer_host/pepper/pepper_file_io_host.h"
14 #include "content/browser/renderer_host/pepper/quota_reservation.h"
15 #include "content/common/pepper_file_util.h"
16 #include "content/public/browser/browser_ppapi_host.h"
17 #include "content/public/browser/browser_task_traits.h"
18 #include "content/public/browser/plugin_service.h"
19 #include "content/public/browser/render_process_host.h"
20 #include "content/public/browser/storage_partition.h"
21 #include "content/public/common/content_plugin_info.h"
22 #include "net/base/mime_util.h"
23 #include "ppapi/c/pp_errors.h"
24 #include "ppapi/host/dispatch_host_message.h"
25 #include "ppapi/host/ppapi_host.h"
26 #include "ppapi/proxy/ppapi_messages.h"
27 #include "ppapi/shared_impl/file_system_util.h"
28 #include "ppapi/shared_impl/file_type_conversion.h"
29 #include "storage/browser/file_system/file_system_operation_runner.h"
30 #include "storage/browser/file_system/file_system_util.h"
31 #include "storage/browser/file_system/isolated_context.h"
32 #include "storage/browser/quota/quota_manager_proxy.h"
33 #include "storage/common/file_system/file_system_util.h"
34 #include "third_party/blink/public/common/storage_key/storage_key.h"
36 #include "url/origin.h"
42 // This is the minimum amount of quota we reserve per file system.
43 const int64_t kMinimumQuotaReservationSize = 1024 * 1024; // 1 MB
45 storage::FileSystemType PepperFileSystemTypeToFileSystemType(
46 PP_FileSystemType type) {
48 case PP_FILESYSTEMTYPE_LOCALTEMPORARY:
49 return storage::kFileSystemTypeTemporary;
50 case PP_FILESYSTEMTYPE_LOCALPERSISTENT:
51 return storage::kFileSystemTypePersistent;
52 case PP_FILESYSTEMTYPE_EXTERNAL:
53 return storage::kFileSystemTypeExternal;
55 return storage::kFileSystemTypeUnknown;
59 void RunOpenQuotaCallbackOnUI(
60 PepperFileSystemBrowserHost::OpenQuotaFileCallback callback,
61 int64_t max_written_offset) {
62 GetUIThreadTaskRunner({})->PostTask(
63 FROM_HERE, base::BindOnce(std::move(callback), max_written_offset));
66 void RunReserveQuotaCallbackOnUI(
67 QuotaReservation::ReserveQuotaCallback callback,
69 const ppapi::FileSizeMap& file_sizes) {
70 GetUIThreadTaskRunner({})->PostTask(
71 FROM_HERE, base::BindOnce(std::move(callback), amount, file_sizes));
76 PepperFileSystemBrowserHost::IOThreadState::IOThreadState(
77 PP_FileSystemType type,
78 base::WeakPtr<PepperFileSystemBrowserHost> host)
80 task_runner_(base::ThreadTaskRunnerHandle::Get()),
83 PepperFileSystemBrowserHost::IOThreadState::~IOThreadState() {
84 // All FileRefs and FileIOs that reference us must have been destroyed. Cancel
85 // all pending file system operations.
86 if (file_system_operation_runner_)
87 file_system_operation_runner_->Shutdown();
90 void PepperFileSystemBrowserHost::IOThreadState::OpenExistingFileSystem(
92 base::OnceClosure callback,
93 scoped_refptr<storage::FileSystemContext> file_system_context) {
94 DCHECK_CURRENTLY_ON(BrowserThread::IO);
96 if (file_system_context.get()) {
99 // If there is no file system context, we log a warning and continue with an
100 // invalid resource (which will produce errors when used), since we have no
101 // way to communicate the error to the caller.
102 LOG(WARNING) << "Could not retrieve file system context.";
104 SetFileSystemContext(file_system_context);
106 ShouldCreateQuotaReservation(base::BindOnce(
107 [](scoped_refptr<IOThreadState> io_thread_state,
108 base::OnceClosure callback, bool should_create_quota_reservation) {
109 if (should_create_quota_reservation) {
110 io_thread_state->CreateQuotaReservation(std::move(callback));
112 io_thread_state->RunCallbackIfHostAlive(std::move(callback));
115 base::WrapRefCounted(this), std::move(callback)));
118 void PepperFileSystemBrowserHost::IOThreadState::OpenFileSystem(
120 ppapi::host::ReplyMessageContext reply_context,
121 storage::FileSystemType file_system_type,
122 scoped_refptr<storage::FileSystemContext> file_system_context) {
123 DCHECK_CURRENTLY_ON(BrowserThread::IO);
124 if (!file_system_context.get()) {
125 OpenFileSystemComplete(reply_context, storage::FileSystemURL(),
126 std::string(), base::File::FILE_ERROR_FAILED);
130 SetFileSystemContext(file_system_context);
132 // TODO(https://crbug.com/1236243): figure out if StorageKey conversion
133 // should replaced with a third-party value: is ppapi only limited to
134 // first-party contexts? If so, the implementation below is correct.
135 file_system_context_->OpenFileSystem(
136 blink::StorageKey(url::Origin::Create(origin)), /*bucket=*/absl::nullopt,
137 file_system_type, storage::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT,
138 base::BindOnce(&IOThreadState::OpenFileSystemComplete, this,
142 void PepperFileSystemBrowserHost::IOThreadState::OpenIsolatedFileSystem(
144 const GURL& root_url,
145 const std::string& plugin_id,
146 ppapi::host::ReplyMessageContext reply_context,
147 const std::string& fsid,
148 PP_IsolatedFileSystemType_Private type,
149 scoped_refptr<storage::FileSystemContext> file_system_context) {
150 DCHECK_CURRENTLY_ON(BrowserThread::IO);
151 if (!file_system_context.get()) {
152 SendReplyForIsolatedFileSystem(reply_context, fsid, PP_ERROR_FAILED);
155 SetFileSystemContext(file_system_context);
157 if (!root_url_.is_valid()) {
158 SendReplyForIsolatedFileSystem(reply_context, fsid, PP_ERROR_FAILED);
163 case PP_ISOLATEDFILESYSTEMTYPE_PRIVATE_CRX:
165 SendReplyForIsolatedFileSystem(reply_context, fsid, PP_OK);
169 SendReplyForIsolatedFileSystem(reply_context, fsid, PP_ERROR_BADARGUMENT);
174 void PepperFileSystemBrowserHost::IOThreadState::OpenFileSystemComplete(
175 ppapi::host::ReplyMessageContext reply_context,
176 const storage::FileSystemURL& root,
177 const std::string& name,
178 base::File::Error error) {
179 DCHECK_CURRENTLY_ON(BrowserThread::IO);
180 int32_t pp_error = ppapi::FileErrorToPepperError(error);
181 if (pp_error == PP_OK) {
183 // TODO(crbug.com/1323925): Store and use FileSystemURL instead.
184 root_url_ = root.ToGURL();
186 ShouldCreateQuotaReservation(base::BindOnce(
187 [](scoped_refptr<IOThreadState> io_thread_state,
188 ppapi::host::ReplyMessageContext reply_context,
189 bool should_create_quota_reservation) {
190 if (should_create_quota_reservation) {
191 io_thread_state->CreateQuotaReservation(base::BindOnce(
192 &IOThreadState::SendReplyForFileSystemIfHostAlive,
193 io_thread_state, reply_context, static_cast<int32_t>(PP_OK)));
195 io_thread_state->SendReplyForFileSystemIfHostAlive(reply_context,
199 base::WrapRefCounted(this), reply_context));
202 SendReplyForFileSystemIfHostAlive(reply_context, pp_error);
205 void PepperFileSystemBrowserHost::IOThreadState::RunCallbackIfHostAlive(
206 base::OnceClosure callback) {
207 if (!task_runner_->BelongsToCurrentThread()) {
208 task_runner_->PostTask(
209 FROM_HERE, base::BindOnce(&IOThreadState::RunCallbackIfHostAlive, this,
210 std::move(callback)));
217 std::move(callback).Run();
220 void PepperFileSystemBrowserHost::IOThreadState::
221 SendReplyForFileSystemIfHostAlive(
222 ppapi::host::ReplyMessageContext reply_context,
224 if (!task_runner_->BelongsToCurrentThread()) {
225 task_runner_->PostTask(
227 base::BindOnce(&IOThreadState::SendReplyForFileSystemIfHostAlive, this,
228 reply_context, pp_error));
235 reply_context.params.set_result(pp_error);
236 host_->host()->SendReply(reply_context,
237 PpapiPluginMsg_FileSystem_OpenReply());
240 void PepperFileSystemBrowserHost::IOThreadState::SendReplyForIsolatedFileSystem(
241 ppapi::host::ReplyMessageContext reply_context,
242 const std::string& fsid,
244 if (!task_runner_->BelongsToCurrentThread()) {
245 task_runner_->PostTask(
247 base::BindOnce(&IOThreadState::SendReplyForIsolatedFileSystem, this,
248 reply_context, fsid, error));
256 storage::IsolatedContext::GetInstance()->RevokeFileSystem(fsid);
257 reply_context.params.set_result(error);
258 host_->SendReply(reply_context,
259 PpapiPluginMsg_FileSystem_InitIsolatedFileSystemReply());
262 void PepperFileSystemBrowserHost::IOThreadState::SetFileSystemContext(
263 scoped_refptr<storage::FileSystemContext> file_system_context) {
264 DCHECK_CURRENTLY_ON(BrowserThread::IO);
265 file_system_context_ = file_system_context;
266 if (type_ != PP_FILESYSTEMTYPE_EXTERNAL || root_url_.is_valid()) {
267 file_system_operation_runner_ =
268 file_system_context_->CreateFileSystemOperationRunner();
272 void PepperFileSystemBrowserHost::IOThreadState::ShouldCreateQuotaReservation(
273 base::OnceCallback<void(bool)> callback) const {
274 DCHECK_CURRENTLY_ON(BrowserThread::IO);
275 // Some file system types don't have quota.
276 if (!ppapi::FileSystemTypeHasQuota(type_)) {
277 std::move(callback).Run(false);
281 // For file system types with quota, some origins have unlimited storage.
282 const scoped_refptr<storage::QuotaManagerProxy>& quota_manager_proxy =
283 file_system_context_->quota_manager_proxy();
284 CHECK(quota_manager_proxy);
285 storage::FileSystemType file_system_type =
286 PepperFileSystemTypeToFileSystemType(type_);
287 quota_manager_proxy->IsStorageUnlimited(
288 blink::StorageKey(url::Origin::Create(root_url_)),
289 storage::FileSystemTypeToQuotaStorageType(file_system_type),
290 base::SequencedTaskRunnerHandle::Get(),
292 [](base::OnceCallback<void(bool)> callback,
293 bool is_storage_unlimited) {
294 std::move(callback).Run(!is_storage_unlimited);
296 std::move(callback)));
299 void PepperFileSystemBrowserHost::IOThreadState::CreateQuotaReservation(
300 base::OnceClosure callback) {
301 DCHECK_CURRENTLY_ON(BrowserThread::IO);
302 DCHECK(root_url_.is_valid());
303 base::PostTaskAndReplyWithResult(
304 file_system_context_->default_file_task_runner(), FROM_HERE,
305 base::BindOnce(&QuotaReservation::Create, file_system_context_,
306 root_url_.DeprecatedGetOriginAsURL(),
307 PepperFileSystemTypeToFileSystemType(type_)),
308 base::BindOnce(&IOThreadState::GotQuotaReservation, this,
309 std::move(callback)));
312 void PepperFileSystemBrowserHost::IOThreadState::GotQuotaReservation(
313 base::OnceClosure callback,
314 scoped_refptr<QuotaReservation> quota_reservation) {
315 DCHECK_CURRENTLY_ON(BrowserThread::IO);
317 task_runner_->PostTask(
319 base::BindOnce(&PepperFileSystemBrowserHost::GotQuotaReservation, host_,
320 std::move(callback), quota_reservation));
323 PepperFileSystemBrowserHost::PepperFileSystemBrowserHost(BrowserPpapiHost* host,
324 PP_Instance instance,
325 PP_Resource resource,
326 PP_FileSystemType type)
327 : ResourceHost(host->GetPpapiHost(), instance, resource),
328 browser_ppapi_host_(host),
332 reserving_quota_(false) {
334 base::MakeRefCounted<IOThreadState>(type, weak_factory_.GetWeakPtr());
337 PepperFileSystemBrowserHost::~PepperFileSystemBrowserHost() {
338 // If |files_| is not empty, the plugin failed to close some files. It must
340 if (!files_.empty()) {
341 io_thread_state_->file_system_context()
342 ->default_file_task_runner()
343 ->PostTask(FROM_HERE, base::BindOnce(&QuotaReservation::OnClientCrash,
344 quota_reservation_));
348 void PepperFileSystemBrowserHost::OpenExisting(const GURL& root_url,
349 base::OnceClosure callback) {
350 int render_process_id = 0;
352 if (!browser_ppapi_host_->GetRenderFrameIDsForInstance(
353 pp_instance(), &render_process_id, &unused)) {
357 // Get the file system context asynchronously, and then complete the Open
358 // operation by calling |callback|.
359 GetIOThreadTaskRunner({})->PostTask(
361 base::BindOnce(&IOThreadState::OpenExistingFileSystem, io_thread_state_,
362 root_url, std::move(callback),
363 GetFileSystemContextFromRenderId(render_process_id)));
366 int32_t PepperFileSystemBrowserHost::OnResourceMessageReceived(
367 const IPC::Message& msg,
368 ppapi::host::HostMessageContext* context) {
369 PPAPI_BEGIN_MESSAGE_MAP(PepperFileSystemBrowserHost, msg)
370 PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_FileSystem_Open,
372 PPAPI_DISPATCH_HOST_RESOURCE_CALL(
373 PpapiHostMsg_FileSystem_InitIsolatedFileSystem,
374 OnHostMsgInitIsolatedFileSystem)
375 PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_FileSystem_ReserveQuota,
376 OnHostMsgReserveQuota)
377 PPAPI_END_MESSAGE_MAP()
378 LOG(ERROR) << "Resource message not resolved";
379 return PP_ERROR_FAILED;
382 bool PepperFileSystemBrowserHost::IsFileSystemHost() {
386 bool PepperFileSystemBrowserHost::IsOpened() const {
387 DCHECK(called_open_);
388 return io_thread_state_->opened();
391 GURL PepperFileSystemBrowserHost::GetRootUrl() const {
392 DCHECK(called_open_);
393 return io_thread_state_->root_url();
396 PepperFileSystemBrowserHost::GetOperationRunnerCallback
397 PepperFileSystemBrowserHost::GetFileSystemOperationRunner() const {
398 return base::BindRepeating(
399 &PepperFileSystemBrowserHost::GetFileSystemOperationRunnerInternal,
403 void PepperFileSystemBrowserHost::OpenQuotaFile(
404 PepperFileIOHost* file_io_host,
405 const storage::FileSystemURL& url,
406 OpenQuotaFileCallback callback) {
407 int32_t id = file_io_host->pp_resource();
408 std::pair<FileMap::iterator, bool> insert_result =
409 files_.insert(std::make_pair(id, file_io_host));
410 if (!insert_result.second) {
415 base::PostTaskAndReplyWithResult(
416 io_thread_state_->file_system_context()->default_file_task_runner(),
418 base::BindOnce(&QuotaReservation::OpenFile, quota_reservation_, id, url),
419 base::BindOnce(RunOpenQuotaCallbackOnUI, std::move(callback)));
422 void PepperFileSystemBrowserHost::CloseQuotaFile(
423 PepperFileIOHost* file_io_host,
424 const ppapi::FileGrowth& file_growth) {
425 int32_t id = file_io_host->pp_resource();
426 auto it = files_.find(id);
427 if (it != files_.end()) {
434 io_thread_state_->file_system_context()->default_file_task_runner()->PostTask(
435 FROM_HERE, base::BindOnce(&QuotaReservation::CloseFile,
436 quota_reservation_, id, file_growth));
439 scoped_refptr<storage::FileSystemContext>
440 PepperFileSystemBrowserHost::GetFileSystemContextFromRenderId(
441 int render_process_id) {
442 DCHECK_CURRENTLY_ON(BrowserThread::UI);
443 RenderProcessHost* host = RenderProcessHost::FromID(render_process_id);
446 StoragePartition* storage_partition = host->GetStoragePartition();
447 if (!storage_partition)
449 return storage_partition->GetFileSystemContext();
452 int32_t PepperFileSystemBrowserHost::OnHostMsgOpen(
453 ppapi::host::HostMessageContext* context,
454 int64_t /* unused */) {
455 // TODO(raymes): The file system size is now unused by FileSystemDispatcher.
456 // Figure out why. Why is the file system size signed?
458 // Not allow multiple opens.
460 LOG(ERROR) << "Open has already been called - Not allow multiple opens";
461 return PP_ERROR_INPROGRESS;
465 storage::FileSystemType file_system_type =
466 PepperFileSystemTypeToFileSystemType(type_);
467 if (file_system_type == storage::kFileSystemTypeUnknown) {
468 LOG(ERROR) << "Unknown file system type";
469 return PP_ERROR_FAILED;
472 int render_process_id = 0;
474 if (!browser_ppapi_host_->GetRenderFrameIDsForInstance(
475 pp_instance(), &render_process_id, &unused)) {
477 return PP_ERROR_FAILED;
480 GURL origin = browser_ppapi_host_->GetDocumentURLForInstance(pp_instance())
481 .DeprecatedGetOriginAsURL();
482 GetIOThreadTaskRunner({})->PostTask(
484 base::BindOnce(&IOThreadState::OpenFileSystem, io_thread_state_, origin,
485 context->MakeReplyMessageContext(), file_system_type,
486 GetFileSystemContextFromRenderId(render_process_id)));
488 return PP_OK_COMPLETIONPENDING;
491 int32_t PepperFileSystemBrowserHost::OnHostMsgInitIsolatedFileSystem(
492 ppapi::host::HostMessageContext* context,
493 const std::string& fsid,
494 PP_IsolatedFileSystemType_Private type) {
495 // Do not allow multiple opens.
497 LOG(ERROR) << "Open has already been called - Not allow multiple opens";
498 return PP_ERROR_INPROGRESS;
502 // Do a sanity check.
503 if (!storage::ValidateIsolatedFileSystemId(fsid)) {
505 return PP_ERROR_BADARGUMENT;
508 int render_process_id = 0;
510 if (!browser_ppapi_host_->GetRenderFrameIDsForInstance(
511 pp_instance(), &render_process_id, &unused)) {
512 storage::IsolatedContext::GetInstance()->RevokeFileSystem(fsid);
514 return PP_ERROR_FAILED;
517 GURL origin = browser_ppapi_host_->GetDocumentURLForInstance(pp_instance())
518 .DeprecatedGetOriginAsURL();
519 GURL root_url = GURL(storage::GetIsolatedFileSystemRootURIString(
520 origin, fsid, ppapi::IsolatedFileSystemTypeToRootName(type)));
522 const std::string& plugin_id = GeneratePluginId(GetPluginMimeType());
524 GetIOThreadTaskRunner({})->PostTask(
526 base::BindOnce(&IOThreadState::OpenIsolatedFileSystem, io_thread_state_,
527 origin, root_url, plugin_id,
528 context->MakeReplyMessageContext(), fsid, type,
529 GetFileSystemContextFromRenderId(render_process_id)));
530 return PP_OK_COMPLETIONPENDING;
533 int32_t PepperFileSystemBrowserHost::OnHostMsgReserveQuota(
534 ppapi::host::HostMessageContext* context,
536 const ppapi::FileGrowthMap& file_growths) {
537 DCHECK(ChecksQuota());
538 DCHECK_GT(amount, 0);
540 if (reserving_quota_) {
541 LOG(ERROR) << "Reserving quota in progress";
542 return PP_ERROR_INPROGRESS;
544 reserving_quota_ = true;
546 int64_t reservation_amount =
547 std::max<int64_t>(kMinimumQuotaReservationSize, amount);
549 QuotaReservation::ReserveQuotaCallback callback = base::BindOnce(
550 &PepperFileSystemBrowserHost::GotReservedQuota,
551 weak_factory_.GetWeakPtr(), context->MakeReplyMessageContext());
553 io_thread_state_->file_system_context()->default_file_task_runner()->PostTask(
556 &QuotaReservation::ReserveQuota, quota_reservation_,
557 reservation_amount, file_growths,
558 base::BindOnce(RunReserveQuotaCallbackOnUI, std::move(callback))));
560 return PP_OK_COMPLETIONPENDING;
563 void PepperFileSystemBrowserHost::GotQuotaReservation(
564 base::OnceClosure callback,
565 scoped_refptr<QuotaReservation> quota_reservation) {
566 quota_reservation_ = quota_reservation;
567 std::move(callback).Run();
570 void PepperFileSystemBrowserHost::GotReservedQuota(
571 ppapi::host::ReplyMessageContext reply_context,
573 const ppapi::FileSizeMap& file_sizes) {
574 DCHECK(reserving_quota_);
575 reserving_quota_ = false;
576 reserved_quota_ = amount;
578 reply_context.params.set_result(PP_OK);
581 PpapiPluginMsg_FileSystem_ReserveQuotaReply(amount, file_sizes));
584 std::string PepperFileSystemBrowserHost::GetPluginMimeType() const {
585 base::FilePath plugin_path = browser_ppapi_host_->GetPluginPath();
586 const ContentPluginInfo* info =
587 PluginService::GetInstance()->GetRegisteredPluginInfo(plugin_path);
588 if (!info || info->mime_types.empty())
589 return std::string();
590 // Use the first element in |info->mime_types| even if several elements exist.
591 return info->mime_types[0].mime_type;
594 std::string PepperFileSystemBrowserHost::GeneratePluginId(
595 const std::string& mime_type) const {
596 // TODO(nhiroki): This function is very specialized for specific plugins (MIME
597 // types). If we bring this API to stable, we might have to make it more
600 std::string top_level_type;
602 if (!net::ParseMimeTypeWithoutParameter(
603 mime_type, &top_level_type, &subtype) ||
604 !net::IsValidTopLevelMimeType(top_level_type))
605 return std::string();
607 // Replace a slash used for type/subtype separator with an underscore.
608 std::string output = top_level_type + "_" + subtype;
610 // Verify |output| contains only alphabets, digits, or "._-".
611 for (std::string::const_iterator it = output.begin(); it != output.end();
613 if (!base::IsAsciiAlpha(*it) && !base::IsAsciiDigit(*it) &&
614 *it != '.' && *it != '_' && *it != '-') {
615 LOG(WARNING) << "Failed to generate a plugin id.";
616 return std::string();
622 storage::FileSystemOperationRunner*
623 PepperFileSystemBrowserHost::GetFileSystemOperationRunnerInternal(
624 scoped_refptr<IOThreadState> io_thread_state) {
625 DCHECK_CURRENTLY_ON(BrowserThread::IO);
626 return io_thread_state->GetFileSystemOperationRunner();
629 } // namespace content