 Chromium Code Reviews
 Chromium Code Reviews Issue 985533004:
  Implement chrome.fileSystem.requestFileSystem().  (Closed) 
  Base URL: https://chromium.googlesource.com/chromium/src.git@master
    
  
    Issue 985533004:
  Implement chrome.fileSystem.requestFileSystem().  (Closed) 
  Base URL: https://chromium.googlesource.com/chromium/src.git@master| Index: chrome/browser/extensions/api/file_system/file_system_api.cc | 
| diff --git a/chrome/browser/extensions/api/file_system/file_system_api.cc b/chrome/browser/extensions/api/file_system/file_system_api.cc | 
| index dbe8bfdc83aa506e9c2dc6cabb020662319e8094..723060f44088ae64c50ebcca6f87498563419cd0 100644 | 
| --- a/chrome/browser/extensions/api/file_system/file_system_api.cc | 
| +++ b/chrome/browser/extensions/api/file_system/file_system_api.cc | 
| @@ -58,7 +58,11 @@ | 
| #endif | 
| #if defined(OS_CHROMEOS) | 
| +#include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h" | 
| #include "chrome/browser/chromeos/file_manager/filesystem_api_util.h" | 
| +#include "chrome/browser/chromeos/file_manager/volume_manager.h" | 
| +#include "components/user_manager/user_manager.h" | 
| +#include "extensions/common/manifest_handlers/kiosk_mode_info.h" | 
| #endif | 
| using apps::SavedFileEntry; | 
| @@ -76,6 +80,9 @@ const char kRequiresFileSystemDirectoryError[] = | 
| const char kMultipleUnsupportedError[] = | 
| "acceptsMultiple: true is not supported for 'saveFile'"; | 
| const char kUnknownIdError[] = "Unknown id"; | 
| +const char kVolumeNotFoundError[] = "Volume not found."; | 
| +const char kSecurityError[] = "Security error."; | 
| +const char kNotSupportedError[] = "Operation not supported."; | 
| namespace file_system = extensions::api::file_system; | 
| namespace ChooseEntry = file_system::ChooseEntry; | 
| @@ -994,4 +1001,196 @@ bool FileSystemGetObservedEntriesFunction::RunSync() { | 
| return false; | 
| } | 
| +FileSystemRequestFileSystemFunction::FileSystemRequestFileSystemFunction() | 
| + : chrome_details_(this) { | 
| +} | 
| + | 
| +ExtensionFunction::ResponseAction FileSystemRequestFileSystemFunction::Run() { | 
| + using extensions::api::file_system::RequestFileSystem::Params; | 
| + const scoped_ptr<Params> params(Params::Create(*args_)); | 
| + EXTENSION_FUNCTION_VALIDATE(params); | 
| + | 
| +#if !defined(OS_CHROMEOS) | 
| + NOTIMPLEMENTED(); | 
| + return RespondNow(Error(kNotSupportedError)); | 
| + | 
| +#else | 
| + using file_manager::VolumeManager; | 
| + using file_manager::VolumeInfo; | 
| + VolumeManager* const volume_manager = | 
| + VolumeManager::Get(chrome_details_.GetProfile()); | 
| + DCHECK(volume_manager); | 
| + | 
| + const bool writable = | 
| + params->options.writable.get() && *params->options.writable.get(); | 
| + LOG(ERROR) << params->options.volume_id << ": " << writable; | 
| 
hirono
2015/03/18 03:41:05
nit: Debug log.
 
mtomasz
2015/03/19 01:17:20
Done.
 | 
| + | 
| + // Only kiosk apps in kiosk sessions can use this API. Additionally component | 
| + // extensions and apps, which is not documented though. | 
| + if ((!user_manager::UserManager::Get()->IsLoggedInAsKioskApp() || | 
| + !KioskModeInfo::IsKioskEnabled(extension())) && | 
| + extension()->location() != Manifest::COMPONENT) { | 
| + return RespondNow(Error(kNotSupportedError)); | 
| + } | 
| + | 
| + if (writable && | 
| + !app_file_handler_util::HasFileSystemWritePermission(extension_.get())) { | 
| + return RespondNow(Error(kRequiresFileSystemWriteError)); | 
| + } | 
| + | 
| + VolumeInfo volume_info; | 
| + if (!volume_manager->FindVolumeInfoById(params->options.volume_id, | 
| + &volume_info)) { | 
| + return RespondNow(Error(kVolumeNotFoundError)); | 
| + } | 
| + | 
| + const GURL site = extensions::util::GetSiteForExtensionId( | 
| + extension_id(), chrome_details_.GetProfile()); | 
| + scoped_refptr<storage::FileSystemContext> file_system_context = | 
| + content::BrowserContext::GetStoragePartitionForSite( | 
| + chrome_details_.GetProfile(), site)->GetFileSystemContext(); | 
| + storage::ExternalFileSystemBackend* const backend = | 
| + file_system_context->external_backend(); | 
| + DCHECK(backend); | 
| + | 
| + base::FilePath virtual_path; | 
| + if (!backend->GetVirtualPath(volume_info.mount_path, &virtual_path)) | 
| + return RespondNow(Error(kSecurityError)); | 
| + | 
| + if (writable && (volume_info.is_read_only)) | 
| + return RespondNow(Error(kSecurityError)); | 
| + | 
| + const bool requires_consent = | 
| + !chromeos::KioskAppManager::Get()->IsAutoLaunchEnabled() && | 
| + extension()->location() != Manifest::COMPONENT; | 
| + if (!requires_consent) { | 
| + OnConsentReceived(volume_info.volume_id, writable, | 
| 
hirono
2015/03/18 03:41:05
Just confirmation, if OnConsentRecived called dire
 
mtomasz
2015/03/19 01:17:20
Good catch. It works as RespondLater() simply retu
 | 
| + true); // Grant without user consent. | 
| + } else { | 
| + // TODO(mtomasz): Create a better display name, which is the most meaningful | 
| + // to the user. | 
| + const std::string display_name = !volume_info.volume_label.empty() | 
| + ? volume_info.volume_label | 
| + : volume_info.volume_id; | 
| + RequestConsent( | 
| + display_name, writable, | 
| + base::Bind(&FileSystemRequestFileSystemFunction::OnConsentReceived, | 
| + this, volume_info.volume_id, writable)); | 
| + } | 
| + | 
| + return RespondLater(); | 
| +#endif | 
| +} | 
| + | 
| +void FileSystemRequestFileSystemFunction::RequestConsent( | 
| + const std::string& display_name, | 
| + bool writable, | 
| + const base::Callback<void(bool)>& callback) { | 
| + // TODO(mtomasz): Implement the consent dialog. | 
| + callback.Run(false); | 
| +} | 
| + | 
| +void FileSystemRequestFileSystemFunction::OnConsentReceived( | 
| + const std::string& volume_id, | 
| + bool writable, | 
| + bool granted) { | 
| + if (!granted) { | 
| + SetError(kSecurityError); | 
| + return; | 
| + } | 
| + | 
| +#if defined(OS_CHROMEOS) | 
| 
hirono
2015/03/18 03:41:05
How about enclosing entire RequsetContext and OnCo
 
mtomasz
2015/03/19 01:17:20
Done.
 | 
| + using file_manager::VolumeManager; | 
| + using file_manager::VolumeInfo; | 
| + | 
| + // Fetch the volume again, in case it's gone by the time the permission is | 
| + // granted. | 
| + VolumeManager* const volume_manager = | 
| + VolumeManager::Get(chrome_details_.GetProfile()); | 
| + DCHECK(volume_manager); | 
| + | 
| + VolumeInfo volume_info; | 
| + if (!volume_manager->FindVolumeInfoById(volume_id, &volume_info)) { | 
| + SetError(kVolumeNotFoundError); | 
| + SendResponse(false); | 
| + return; | 
| + } | 
| + | 
| + const GURL site = extensions::util::GetSiteForExtensionId( | 
| + extension_id(), chrome_details_.GetProfile()); | 
| + scoped_refptr<storage::FileSystemContext> file_system_context = | 
| + content::BrowserContext::GetStoragePartitionForSite( | 
| + chrome_details_.GetProfile(), site)->GetFileSystemContext(); | 
| + storage::ExternalFileSystemBackend* const backend = | 
| + file_system_context->external_backend(); | 
| + DCHECK(backend); | 
| + | 
| + // The volume may be unmounted and remounted by the time we reach this logic. | 
| + // TODO(mtomasz): Add a unique identifier to VolumeInfo to guarantee that the | 
| + // permissions are granted to exactly that volume which was plugged in when | 
| + // the dialog was shown. | 
| + base::FilePath virtual_path; | 
| + if (!backend->GetVirtualPath(volume_info.mount_path, &virtual_path)) { | 
| + SetError(kSecurityError); | 
| + SendResponse(false); | 
| + return; | 
| + } | 
| + | 
| + storage::IsolatedContext* const isolated_context = | 
| + storage::IsolatedContext::GetInstance(); | 
| + DCHECK(isolated_context); | 
| + | 
| + const storage::FileSystemURL original_url = | 
| + file_system_context->CreateCrackedFileSystemURL( | 
| + GURL("chrome-extension://" + extension_id()), | 
| + storage::kFileSystemTypeExternal, virtual_path); | 
| + | 
| + std::string register_name = "fs"; | 
| 
hirono
2015/03/18 03:41:05
Do we need to assign "fs"?
 
mtomasz
2015/03/19 01:17:20
Without it we would leak the mount point path, we
 | 
| + const std::string file_system_id = | 
| + isolated_context->RegisterFileSystemForPath( | 
| + storage::kFileSystemTypeNativeForPlatformApp, | 
| + std::string() /* file_system_id */, original_url.path(), | 
| + ®ister_name); | 
| + if (file_system_id.empty()) { | 
| + SetError(kSecurityError); | 
| + SendResponse(false); | 
| + return; | 
| + } | 
| + | 
| + backend->GrantFileAccessToExtension(extension_->id(), virtual_path); | 
| + | 
| + // Grant file permissions to the renderer hosting component. | 
| + content::ChildProcessSecurityPolicy* policy = | 
| + content::ChildProcessSecurityPolicy::GetInstance(); | 
| + DCHECK(policy); | 
| + | 
| + // Read-only permisisons. | 
| + policy->GrantReadFile(render_view_host()->GetProcess()->GetID(), | 
| + volume_info.mount_path); | 
| + policy->GrantReadFileSystem(render_view_host()->GetProcess()->GetID(), | 
| + file_system_id); | 
| + | 
| + // Additional write permissions. | 
| + if (writable) { | 
| + policy->GrantCreateReadWriteFile(render_view_host()->GetProcess()->GetID(), | 
| + volume_info.mount_path); | 
| + policy->GrantCopyInto(render_view_host()->GetProcess()->GetID(), | 
| + volume_info.mount_path); | 
| + policy->GrantWriteFileSystem(render_view_host()->GetProcess()->GetID(), | 
| + file_system_id); | 
| + policy->GrantDeleteFromFileSystem(render_view_host()->GetProcess()->GetID(), | 
| + file_system_id); | 
| + policy->GrantCreateFileForFileSystem( | 
| + render_view_host()->GetProcess()->GetID(), file_system_id); | 
| + } | 
| + | 
| + base::DictionaryValue* const dict = new base::DictionaryValue(); | 
| + dict->SetString("file_system_id", file_system_id); | 
| + dict->SetString("file_system_path", register_name); | 
| + | 
| + SetResult(dict); | 
| + SendResponse(true); | 
| +#endif | 
| +} | 
| + | 
| } // namespace extensions |