blob: dc0ecd14378efc5135797e1706cdb9d1ca31e0ae [file] [log] [blame]
// 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 "chrome/browser/extensions/api/file_system/file_system_api.h"
#include <stddef.h>
#include <memory>
#include <set>
#include <utility>
#include <vector>
#include "apps/saved_files_service.h"
#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/path_service.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/value_conversions.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/extensions/api/file_handlers/app_file_handler_util.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_util.h"
#include "chrome/browser/extensions/path_util.h"
#include "chrome/browser/platform_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/apps/directory_access_confirmation_dialog.h"
#include "chrome/browser/ui/chrome_select_file_policy.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/extensions/api/file_system.h"
#include "chrome/grit/generated_resources.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/child_process_security_policy.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/app_window/app_window.h"
#include "extensions/browser/app_window/app_window_registry.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/granted_file_entry.h"
#include "extensions/common/permissions/api_permission.h"
#include "extensions/common/permissions/permissions_data.h"
#include "net/base/mime_util.h"
#include "storage/browser/fileapi/external_mount_points.h"
#include "storage/browser/fileapi/file_system_operation_runner.h"
#include "storage/browser/fileapi/isolated_context.h"
#include "storage/common/fileapi/file_system_types.h"
#include "storage/common/fileapi/file_system_util.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/shell_dialogs/select_file_dialog.h"
#include "ui/shell_dialogs/selected_file_info.h"
#if defined(OS_MACOSX)
#include <CoreFoundation/CoreFoundation.h>
#include "base/mac/foundation_util.h"
#endif
#if defined(OS_CHROMEOS)
#include "base/strings/string16.h"
#include "base/threading/thread_task_runner_handle.h"
#include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h"
#include "chrome/browser/chromeos/file_manager/app_id.h"
#include "chrome/browser/chromeos/file_manager/filesystem_api_util.h"
#include "chrome/browser/extensions/api/file_system/request_file_system_dialog_view.h"
#include "chrome/browser/extensions/api/file_system/request_file_system_notification.h"
#include "chrome/browser/ui/simple_message_box.h"
#include "components/user_manager/user_manager.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/constants.h"
#include "extensions/common/manifest_handlers/kiosk_mode_info.h"
#include "url/url_constants.h"
#endif
using apps::SavedFileEntry;
using apps::SavedFilesService;
using storage::IsolatedContext;
const char kInvalidCallingPage[] = "Invalid calling page. This function can't "
"be called from a background page.";
const char kUserCancelled[] = "User cancelled";
const char kWritableFileErrorFormat[] = "Error opening %s";
const char kRequiresFileSystemWriteError[] =
"Operation requires fileSystem.write permission";
const char kRequiresFileSystemDirectoryError[] =
"Operation requires fileSystem.directory permission";
const char kMultipleUnsupportedError[] =
"acceptsMultiple: true is only supported for 'openFile'";
const char kUnknownIdError[] = "Unknown id";
#if !defined(OS_CHROMEOS)
const char kNotSupportedOnCurrentPlatformError[] =
"Operation not supported on the current platform.";
#else
const char kNotSupportedOnNonKioskSessionError[] =
"Operation only supported for kiosk apps running in a kiosk session.";
const char kVolumeNotFoundError[] = "Volume not found.";
const char kSecurityError[] = "Security error.";
const char kConsentImpossible[] =
"Impossible to ask for user consent as there is no app window visible.";
// List of whitelisted component apps and extensions by their ids for
// chrome.fileSystem.requestFileSystem.
const char* const kRequestFileSystemComponentWhitelist[] = {
file_manager::kFileManagerAppId,
file_manager::kVideoPlayerAppId,
file_manager::kGalleryAppId,
file_manager::kAudioPlayerAppId,
file_manager::kImageLoaderExtensionId,
// TODO(mtomasz): Remove this extension id, and add it only for tests.
"pkplfbidichfdicaijlchgnapepdginl" // Testing extensions.
};
#endif
namespace extensions {
namespace file_system = api::file_system;
namespace ChooseEntry = file_system::ChooseEntry;
namespace {
bool g_skip_picker_for_test = false;
bool g_use_suggested_path_for_test = false;
base::FilePath* g_path_to_be_picked_for_test;
std::vector<base::FilePath>* g_paths_to_be_picked_for_test;
bool g_skip_directory_confirmation_for_test = false;
bool g_allow_directory_access_for_test = false;
#if defined(OS_CHROMEOS)
ui::DialogButton g_auto_dialog_button_for_test = ui::DIALOG_BUTTON_NONE;
#endif
// Expand the mime-types and extensions provided in an AcceptOption, returning
// them within the passed extension vector. Returns false if no valid types
// were found.
bool GetFileTypesFromAcceptOption(
const file_system::AcceptOption& accept_option,
std::vector<base::FilePath::StringType>* extensions,
base::string16* description) {
std::set<base::FilePath::StringType> extension_set;
int description_id = 0;
if (accept_option.mime_types.get()) {
std::vector<std::string>* list = accept_option.mime_types.get();
bool valid_type = false;
for (std::vector<std::string>::const_iterator iter = list->begin();
iter != list->end(); ++iter) {
std::vector<base::FilePath::StringType> inner;
std::string accept_type = base::ToLowerASCII(*iter);
net::GetExtensionsForMimeType(accept_type, &inner);
if (inner.empty())
continue;
if (valid_type)
description_id = 0; // We already have an accept type with label; if
// we find another, give up and use the default.
else if (accept_type == "image/*")
description_id = IDS_IMAGE_FILES;
else if (accept_type == "audio/*")
description_id = IDS_AUDIO_FILES;
else if (accept_type == "video/*")
description_id = IDS_VIDEO_FILES;
extension_set.insert(inner.begin(), inner.end());
valid_type = true;
}
}
if (accept_option.extensions.get()) {
std::vector<std::string>* list = accept_option.extensions.get();
for (std::vector<std::string>::const_iterator iter = list->begin();
iter != list->end(); ++iter) {
std::string extension = base::ToLowerASCII(*iter);
#if defined(OS_WIN)
extension_set.insert(base::UTF8ToWide(*iter));
#else
extension_set.insert(*iter);
#endif
}
}
extensions->assign(extension_set.begin(), extension_set.end());
if (extensions->empty())
return false;
if (accept_option.description.get())
*description = base::UTF8ToUTF16(*accept_option.description);
else if (description_id)
*description = l10n_util::GetStringUTF16(description_id);
return true;
}
// Key for the path of the directory of the file last chosen by the user in
// response to a chrome.fileSystem.chooseEntry() call.
const char kLastChooseEntryDirectory[] = "last_choose_file_directory";
const int kGraylistedPaths[] = {
base::DIR_HOME,
#if defined(OS_WIN)
base::DIR_PROGRAM_FILES,
base::DIR_PROGRAM_FILESX86,
base::DIR_WINDOWS,
#endif
};
typedef base::Callback<void(std::unique_ptr<base::File::Info>)>
FileInfoOptCallback;
// Passes optional file info to the UI thread depending on |result| and |info|.
void PassFileInfoToUIThread(const FileInfoOptCallback& callback,
base::File::Error result,
const base::File::Info& info) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
std::unique_ptr<base::File::Info> file_info(
result == base::File::FILE_OK ? new base::File::Info(info) : NULL);
content::BrowserThread::PostTask(
content::BrowserThread::UI,
FROM_HERE,
base::Bind(callback, base::Passed(&file_info)));
}
// Gets a WebContents instance handle for a platform app hosted in
// |render_frame_host|. If not found, then returns NULL.
content::WebContents* GetWebContentsForRenderFrameHost(
Profile* profile,
content::RenderFrameHost* render_frame_host) {
content::WebContents* web_contents =
content::WebContents::FromRenderFrameHost(render_frame_host);
// Check if there is an app window associated with the web contents; if not,
// return null.
return AppWindowRegistry::Get(profile)
->GetAppWindowForWebContents(web_contents)
? web_contents
: nullptr;
}
#if defined(OS_CHROMEOS)
// Gets a WebContents instance handle for a current window of a platform app
// with |app_id|. If not found, then returns NULL.
content::WebContents* GetWebContentsForAppId(Profile* profile,
const std::string& app_id) {
AppWindowRegistry* const registry = AppWindowRegistry::Get(profile);
DCHECK(registry);
AppWindow* const app_window = registry->GetCurrentAppWindowForApp(app_id);
return app_window ? app_window->web_contents() : nullptr;
}
// Fills a list of volumes mounted in the system.
void FillVolumeList(Profile* profile,
std::vector<api::file_system::Volume>* result) {
file_manager::VolumeManager* const volume_manager =
file_manager::VolumeManager::Get(profile);
DCHECK(volume_manager);
const auto& volume_list = volume_manager->GetVolumeList();
// Convert volume_list to result_volume_list.
for (const auto& volume : volume_list) {
api::file_system::Volume result_volume;
result_volume.volume_id = volume->volume_id();
result_volume.writable = !volume->is_read_only();
result->push_back(std::move(result_volume));
}
}
#endif
} // namespace
namespace file_system_api {
base::FilePath GetLastChooseEntryDirectory(const ExtensionPrefs* prefs,
const std::string& extension_id) {
base::FilePath path;
std::string string_path;
if (prefs->ReadPrefAsString(extension_id,
kLastChooseEntryDirectory,
&string_path)) {
path = base::FilePath::FromUTF8Unsafe(string_path);
}
return path;
}
void SetLastChooseEntryDirectory(ExtensionPrefs* prefs,
const std::string& extension_id,
const base::FilePath& path) {
prefs->UpdateExtensionPref(extension_id,
kLastChooseEntryDirectory,
base::CreateFilePathValue(path));
}
#if defined(OS_CHROMEOS)
void DispatchVolumeListChangeEvent(Profile* profile) {
DCHECK(profile);
EventRouter* const event_router = EventRouter::Get(profile);
if (!event_router) // Possible on shutdown.
return;
ExtensionRegistry* const registry = ExtensionRegistry::Get(profile);
if (!registry) // Possible on shutdown.
return;
ConsentProviderDelegate consent_provider_delegate(profile, nullptr);
ConsentProvider consent_provider(&consent_provider_delegate);
api::file_system::VolumeListChangedEvent event_args;
FillVolumeList(profile, &event_args.volumes);
for (const auto& extension : registry->enabled_extensions()) {
if (!consent_provider.IsGrantable(*extension.get()))
continue;
event_router->DispatchEventToExtension(
extension->id(),
base::MakeUnique<Event>(
events::FILE_SYSTEM_ON_VOLUME_LIST_CHANGED,
api::file_system::OnVolumeListChanged::kEventName,
api::file_system::OnVolumeListChanged::Create(event_args)));
}
}
ConsentProvider::ConsentProvider(DelegateInterface* delegate)
: delegate_(delegate) {
DCHECK(delegate_);
}
ConsentProvider::~ConsentProvider() {
}
void ConsentProvider::RequestConsent(
const Extension& extension,
const base::WeakPtr<file_manager::Volume>& volume,
bool writable,
const ConsentCallback& callback) {
DCHECK(IsGrantable(extension));
// If a whitelisted component, then no need to ask or inform the user.
if (extension.location() == Manifest::COMPONENT &&
delegate_->IsWhitelistedComponent(extension)) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::Bind(callback, CONSENT_GRANTED));
return;
}
// If auto-launched kiosk app, then no need to ask user either, but show the
// notification.
if (delegate_->IsAutoLaunched(extension)) {
delegate_->ShowNotification(extension, volume, writable);
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::Bind(callback, CONSENT_GRANTED));
return;
}
// If it's a kiosk app running in manual-launch kiosk session, then show
// the confirmation dialog.
if (KioskModeInfo::IsKioskOnly(&extension) &&
user_manager::UserManager::Get()->IsLoggedInAsKioskApp()) {
delegate_->ShowDialog(extension, volume, writable,
base::Bind(&ConsentProvider::DialogResultToConsent,
base::Unretained(this), callback));
return;
}
NOTREACHED() << "Cannot request consent for non-grantable extensions.";
}
bool ConsentProvider::IsGrantable(const Extension& extension) {
const bool is_whitelisted_component =
delegate_->IsWhitelistedComponent(extension);
const bool is_running_in_kiosk_session =
KioskModeInfo::IsKioskOnly(&extension) &&
user_manager::UserManager::Get()->IsLoggedInAsKioskApp();
return is_whitelisted_component || is_running_in_kiosk_session;
}
void ConsentProvider::DialogResultToConsent(const ConsentCallback& callback,
ui::DialogButton button) {
switch (button) {
case ui::DIALOG_BUTTON_NONE:
callback.Run(CONSENT_IMPOSSIBLE);
break;
case ui::DIALOG_BUTTON_OK:
callback.Run(CONSENT_GRANTED);
break;
case ui::DIALOG_BUTTON_CANCEL:
callback.Run(CONSENT_REJECTED);
break;
}
}
ConsentProviderDelegate::ConsentProviderDelegate(Profile* profile,
content::RenderFrameHost* host)
: profile_(profile), host_(host) {
DCHECK(profile_);
}
ConsentProviderDelegate::~ConsentProviderDelegate() {
}
// static
void ConsentProviderDelegate::SetAutoDialogButtonForTest(
ui::DialogButton button) {
g_auto_dialog_button_for_test = button;
}
void ConsentProviderDelegate::ShowDialog(
const Extension& extension,
const base::WeakPtr<file_manager::Volume>& volume,
bool writable,
const file_system_api::ConsentProvider::ShowDialogCallback& callback) {
DCHECK(host_);
content::WebContents* const foreground_contents =
GetWebContentsForRenderFrameHost(profile_, host_);
// If there is no web contents handle, then the method is most probably
// executed from a background page. Find an app window to host the dialog.
content::WebContents* const web_contents =
foreground_contents ? foreground_contents
: GetWebContentsForAppId(profile_, extension.id());
if (!web_contents) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::Bind(callback, ui::DIALOG_BUTTON_NONE));
return;
}
// Short circuit the user consent dialog for tests. This is far from a pretty
// code design.
if (g_auto_dialog_button_for_test != ui::DIALOG_BUTTON_NONE) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::Bind(callback, g_auto_dialog_button_for_test /* result */));
return;
}
RequestFileSystemDialogView::ShowDialog(web_contents, extension, volume,
writable, base::Bind(callback));
}
void ConsentProviderDelegate::ShowNotification(
const Extension& extension,
const base::WeakPtr<file_manager::Volume>& volume,
bool writable) {
RequestFileSystemNotification::ShowAutoGrantedNotification(
profile_, extension, volume, writable);
}
bool ConsentProviderDelegate::IsAutoLaunched(const Extension& extension) {
chromeos::KioskAppManager::App app_info;
return chromeos::KioskAppManager::Get()->GetApp(extension.id(), &app_info) &&
app_info.was_auto_launched_with_zero_delay;
}
bool ConsentProviderDelegate::IsWhitelistedComponent(
const Extension& extension) {
for (auto* whitelisted_id : kRequestFileSystemComponentWhitelist) {
if (extension.id().compare(whitelisted_id) == 0)
return true;
}
return false;
}
#endif
} // namespace file_system_api
#if defined(OS_CHROMEOS)
using file_system_api::ConsentProvider;
#endif
bool FileSystemGetDisplayPathFunction::RunSync() {
std::string filesystem_name;
std::string filesystem_path;
EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &filesystem_name));
EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &filesystem_path));
base::FilePath file_path;
if (!app_file_handler_util::ValidateFileEntryAndGetPath(
filesystem_name, filesystem_path,
render_frame_host()->GetProcess()->GetID(), &file_path, &error_))
return false;
file_path = path_util::PrettifyPath(file_path);
SetResult(base::MakeUnique<base::StringValue>(file_path.value()));
return true;
}
FileSystemEntryFunction::FileSystemEntryFunction()
: multiple_(false), is_directory_(false) {}
void FileSystemEntryFunction::PrepareFilesForWritableApp(
const std::vector<base::FilePath>& paths) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// TODO(cmihail): Path directory set should be initialized only with the
// paths that are actually directories, but for now we will consider
// all paths directories in case is_directory_ is true, otherwise
// all paths files, as this was the previous logic.
std::set<base::FilePath> path_directory_set_ =
is_directory_ ? std::set<base::FilePath>(paths.begin(), paths.end())
: std::set<base::FilePath>{};
app_file_handler_util::PrepareFilesForWritableApp(
paths, GetProfile(), path_directory_set_,
base::Bind(&FileSystemEntryFunction::RegisterFileSystemsAndSendResponse,
this, paths),
base::Bind(&FileSystemEntryFunction::HandleWritableFileError, this));
}
void FileSystemEntryFunction::RegisterFileSystemsAndSendResponse(
const std::vector<base::FilePath>& paths) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!render_frame_host())
return;
std::unique_ptr<base::DictionaryValue> result = CreateResult();
for (const auto& path : paths)
AddEntryToResult(path, std::string(), result.get());
SetResult(std::move(result));
SendResponse(true);
}
std::unique_ptr<base::DictionaryValue> FileSystemEntryFunction::CreateResult() {
std::unique_ptr<base::DictionaryValue> result(new base::DictionaryValue());
result->Set("entries", base::MakeUnique<base::ListValue>());
result->SetBoolean("multiple", multiple_);
return result;
}
void FileSystemEntryFunction::AddEntryToResult(const base::FilePath& path,
const std::string& id_override,
base::DictionaryValue* result) {
GrantedFileEntry file_entry = app_file_handler_util::CreateFileEntry(
GetProfile(),
extension(),
render_frame_host()->GetProcess()->GetID(),
path,
is_directory_);
base::ListValue* entries;
bool success = result->GetList("entries", &entries);
DCHECK(success);
std::unique_ptr<base::DictionaryValue> entry(new base::DictionaryValue());
entry->SetString("fileSystemId", file_entry.filesystem_id);
entry->SetString("baseName", file_entry.registered_name);
if (id_override.empty())
entry->SetString("id", file_entry.id);
else
entry->SetString("id", id_override);
entry->SetBoolean("isDirectory", is_directory_);
entries->Append(std::move(entry));
}
void FileSystemEntryFunction::HandleWritableFileError(
const base::FilePath& error_path) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
error_ = base::StringPrintf(kWritableFileErrorFormat,
error_path.BaseName().AsUTF8Unsafe().c_str());
SendResponse(false);
}
bool FileSystemGetWritableEntryFunction::RunAsync() {
std::string filesystem_name;
std::string filesystem_path;
EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &filesystem_name));
EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &filesystem_path));
if (!app_file_handler_util::HasFileSystemWritePermission(extension_.get())) {
error_ = kRequiresFileSystemWriteError;
return false;
}
if (!app_file_handler_util::ValidateFileEntryAndGetPath(
filesystem_name, filesystem_path,
render_frame_host()->GetProcess()->GetID(), &path_, &error_))
return false;
content::BrowserThread::PostTaskAndReply(
content::BrowserThread::FILE,
FROM_HERE,
base::Bind(
&FileSystemGetWritableEntryFunction::SetIsDirectoryOnFileThread,
this),
base::Bind(
&FileSystemGetWritableEntryFunction::CheckPermissionAndSendResponse,
this));
return true;
}
void FileSystemGetWritableEntryFunction::CheckPermissionAndSendResponse() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (is_directory_ &&
!extension_->permissions_data()->HasAPIPermission(
APIPermission::kFileSystemDirectory)) {
error_ = kRequiresFileSystemDirectoryError;
SendResponse(false);
}
std::vector<base::FilePath> paths;
paths.push_back(path_);
PrepareFilesForWritableApp(paths);
}
void FileSystemGetWritableEntryFunction::SetIsDirectoryOnFileThread() {
DCHECK_CURRENTLY_ON(content::BrowserThread::FILE);
if (base::DirectoryExists(path_)) {
is_directory_ = true;
}
}
bool FileSystemIsWritableEntryFunction::RunSync() {
std::string filesystem_name;
std::string filesystem_path;
EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &filesystem_name));
EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &filesystem_path));
std::string filesystem_id;
if (!storage::CrackIsolatedFileSystemName(filesystem_name, &filesystem_id)) {
error_ = app_file_handler_util::kInvalidParameters;
return false;
}
content::ChildProcessSecurityPolicy* policy =
content::ChildProcessSecurityPolicy::GetInstance();
int renderer_id = render_frame_host()->GetProcess()->GetID();
bool is_writable = policy->CanReadWriteFileSystem(renderer_id,
filesystem_id);
SetResult(base::MakeUnique<base::FundamentalValue>(is_writable));
return true;
}
// Handles showing a dialog to the user to ask for the filename for a file to
// save or open.
class FileSystemChooseEntryFunction::FilePicker
: public ui::SelectFileDialog::Listener {
public:
FilePicker(FileSystemChooseEntryFunction* function,
content::WebContents* web_contents,
const base::FilePath& suggested_name,
const ui::SelectFileDialog::FileTypeInfo& file_type_info,
ui::SelectFileDialog::Type picker_type)
: function_(function) {
select_file_dialog_ = ui::SelectFileDialog::Create(
this, new ChromeSelectFilePolicy(web_contents));
gfx::NativeWindow owning_window = web_contents ?
platform_util::GetTopLevel(web_contents->GetNativeView()) :
NULL;
if (g_skip_picker_for_test) {
if (g_use_suggested_path_for_test) {
content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
base::Bind(
&FileSystemChooseEntryFunction::FilePicker::FileSelected,
base::Unretained(this), suggested_name, 1,
static_cast<void*>(NULL)));
} else if (g_path_to_be_picked_for_test) {
content::BrowserThread::PostTask(
content::BrowserThread::UI, FROM_HERE,
base::Bind(
&FileSystemChooseEntryFunction::FilePicker::FileSelected,
base::Unretained(this), *g_path_to_be_picked_for_test, 1,
static_cast<void*>(NULL)));
} else if (g_paths_to_be_picked_for_test) {
content::BrowserThread::PostTask(
content::BrowserThread::UI,
FROM_HERE,
base::Bind(
&FileSystemChooseEntryFunction::FilePicker::MultiFilesSelected,
base::Unretained(this),
*g_paths_to_be_picked_for_test,
static_cast<void*>(NULL)));
} else {
content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
base::Bind(
&FileSystemChooseEntryFunction::FilePicker::
FileSelectionCanceled,
base::Unretained(this), static_cast<void*>(NULL)));
}
return;
}
select_file_dialog_->SelectFile(picker_type,
base::string16(),
suggested_name,
&file_type_info,
0,
base::FilePath::StringType(),
owning_window,
NULL);
}
~FilePicker() override {}
private:
// ui::SelectFileDialog::Listener implementation.
void FileSelected(const base::FilePath& path,
int index,
void* params) override {
std::vector<base::FilePath> paths;
paths.push_back(path);
MultiFilesSelected(paths, params);
}
void FileSelectedWithExtraInfo(const ui::SelectedFileInfo& file,
int index,
void* params) override {
// Normally, file.local_path is used because it is a native path to the
// local read-only cached file in the case of remote file system like
// Chrome OS's Google Drive integration. Here, however, |file.file_path| is
// necessary because we need to create a FileEntry denoting the remote file,
// not its cache. On other platforms than Chrome OS, they are the same.
//
// TODO(kinaba): remove this, once after the file picker implements proper
// switch of the path treatment depending on the |allowed_paths|.
FileSelected(file.file_path, index, params);
}
void MultiFilesSelected(const std::vector<base::FilePath>& files,
void* params) override {
function_->FilesSelected(files);
delete this;
}
void MultiFilesSelectedWithExtraInfo(
const std::vector<ui::SelectedFileInfo>& files,
void* params) override {
std::vector<base::FilePath> paths;
for (std::vector<ui::SelectedFileInfo>::const_iterator it = files.begin();
it != files.end(); ++it) {
paths.push_back(it->file_path);
}
MultiFilesSelected(paths, params);
}
void FileSelectionCanceled(void* params) override {
function_->FileSelectionCanceled();
delete this;
}
scoped_refptr<ui::SelectFileDialog> select_file_dialog_;
scoped_refptr<FileSystemChooseEntryFunction> function_;
DISALLOW_COPY_AND_ASSIGN(FilePicker);
};
void FileSystemChooseEntryFunction::ShowPicker(
const ui::SelectFileDialog::FileTypeInfo& file_type_info,
ui::SelectFileDialog::Type picker_type) {
// TODO(asargent/benwells) - As a short term remediation for crbug.com/179010
// we're adding the ability for a whitelisted extension to use this API since
// chrome.fileBrowserHandler.selectFile is ChromeOS-only. Eventually we'd
// like a better solution and likely this code will go back to being
// platform-app only.
content::WebContents* const web_contents =
extension_->is_platform_app()
? GetWebContentsForRenderFrameHost(GetProfile(), render_frame_host())
: GetAssociatedWebContents();
if (!web_contents) {
error_ = kInvalidCallingPage;
SendResponse(false);
return;
}
// The file picker will hold a reference to this function instance, preventing
// its destruction (and subsequent sending of the function response) until the
// user has selected a file or cancelled the picker. At that point, the picker
// will delete itself, which will also free the function instance.
new FilePicker(
this, web_contents, initial_path_, file_type_info, picker_type);
}
// static
void FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathForTest(
base::FilePath* path) {
g_skip_picker_for_test = true;
g_use_suggested_path_for_test = false;
g_path_to_be_picked_for_test = path;
g_paths_to_be_picked_for_test = NULL;
}
void FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathsForTest(
std::vector<base::FilePath>* paths) {
g_skip_picker_for_test = true;
g_use_suggested_path_for_test = false;
g_paths_to_be_picked_for_test = paths;
}
// static
void FileSystemChooseEntryFunction::SkipPickerAndSelectSuggestedPathForTest() {
g_skip_picker_for_test = true;
g_use_suggested_path_for_test = true;
g_path_to_be_picked_for_test = NULL;
g_paths_to_be_picked_for_test = NULL;
}
// static
void FileSystemChooseEntryFunction::SkipPickerAndAlwaysCancelForTest() {
g_skip_picker_for_test = true;
g_use_suggested_path_for_test = false;
g_path_to_be_picked_for_test = NULL;
g_paths_to_be_picked_for_test = NULL;
}
// static
void FileSystemChooseEntryFunction::StopSkippingPickerForTest() {
g_skip_picker_for_test = false;
}
// static
void FileSystemChooseEntryFunction::SkipDirectoryConfirmationForTest() {
g_skip_directory_confirmation_for_test = true;
g_allow_directory_access_for_test = true;
}
// static
void FileSystemChooseEntryFunction::AutoCancelDirectoryConfirmationForTest() {
g_skip_directory_confirmation_for_test = true;
g_allow_directory_access_for_test = false;
}
// static
void FileSystemChooseEntryFunction::StopSkippingDirectoryConfirmationForTest() {
g_skip_directory_confirmation_for_test = false;
}
// static
void FileSystemChooseEntryFunction::RegisterTempExternalFileSystemForTest(
const std::string& name, const base::FilePath& path) {
// For testing on Chrome OS, where to deal with remote and local paths
// smoothly, all accessed paths need to be registered in the list of
// external mount points.
storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
name,
storage::kFileSystemTypeNativeLocal,
storage::FileSystemMountOption(),
path);
}
void FileSystemChooseEntryFunction::FilesSelected(
const std::vector<base::FilePath>& paths) {
DCHECK(!paths.empty());
base::FilePath last_choose_directory;
if (is_directory_) {
last_choose_directory = paths[0];
} else {
last_choose_directory = paths[0].DirName();
}
file_system_api::SetLastChooseEntryDirectory(
ExtensionPrefs::Get(GetProfile()),
extension()->id(),
last_choose_directory);
if (is_directory_) {
// Get the WebContents for the app window to be the parent window of the
// confirmation dialog if necessary.
content::WebContents* const web_contents =
GetWebContentsForRenderFrameHost(GetProfile(), render_frame_host());
if (!web_contents) {
error_ = kInvalidCallingPage;
SendResponse(false);
return;
}
DCHECK_EQ(paths.size(), 1u);
bool non_native_path = false;
#if defined(OS_CHROMEOS)
non_native_path =
file_manager::util::IsUnderNonNativeLocalPath(GetProfile(), paths[0]);
#endif
content::BrowserThread::PostTask(
content::BrowserThread::FILE,
FROM_HERE,
base::Bind(
&FileSystemChooseEntryFunction::ConfirmDirectoryAccessOnFileThread,
this,
non_native_path,
paths,
web_contents));
return;
}
OnDirectoryAccessConfirmed(paths);
}
void FileSystemChooseEntryFunction::FileSelectionCanceled() {
error_ = kUserCancelled;
SendResponse(false);
}
void FileSystemChooseEntryFunction::ConfirmDirectoryAccessOnFileThread(
bool non_native_path,
const std::vector<base::FilePath>& paths,
content::WebContents* web_contents) {
const base::FilePath check_path =
non_native_path ? paths[0] : base::MakeAbsoluteFilePath(paths[0]);
if (check_path.empty()) {
content::BrowserThread::PostTask(
content::BrowserThread::UI,
FROM_HERE,
base::Bind(&FileSystemChooseEntryFunction::FileSelectionCanceled,
this));
return;
}
for (size_t i = 0; i < arraysize(kGraylistedPaths); i++) {
base::FilePath graylisted_path;
if (PathService::Get(kGraylistedPaths[i], &graylisted_path) &&
(check_path == graylisted_path ||
check_path.IsParent(graylisted_path))) {
if (g_skip_directory_confirmation_for_test) {
if (g_allow_directory_access_for_test) {
break;
} else {
content::BrowserThread::PostTask(
content::BrowserThread::UI,
FROM_HERE,
base::Bind(&FileSystemChooseEntryFunction::FileSelectionCanceled,
this));
}
return;
}
content::BrowserThread::PostTask(
content::BrowserThread::UI,
FROM_HERE,
base::Bind(
CreateDirectoryAccessConfirmationDialog,
app_file_handler_util::HasFileSystemWritePermission(
extension_.get()),
base::UTF8ToUTF16(extension_->name()),
web_contents,
base::Bind(
&FileSystemChooseEntryFunction::OnDirectoryAccessConfirmed,
this,
paths),
base::Bind(&FileSystemChooseEntryFunction::FileSelectionCanceled,
this)));
return;
}
}
content::BrowserThread::PostTask(
content::BrowserThread::UI,
FROM_HERE,
base::Bind(&FileSystemChooseEntryFunction::OnDirectoryAccessConfirmed,
this, paths));
}
void FileSystemChooseEntryFunction::OnDirectoryAccessConfirmed(
const std::vector<base::FilePath>& paths) {
if (app_file_handler_util::HasFileSystemWritePermission(extension_.get())) {
PrepareFilesForWritableApp(paths);
return;
}
// Don't need to check the file, it's for reading.
RegisterFileSystemsAndSendResponse(paths);
}
void FileSystemChooseEntryFunction::BuildFileTypeInfo(
ui::SelectFileDialog::FileTypeInfo* file_type_info,
const base::FilePath::StringType& suggested_extension,
const AcceptOptions* accepts,
const bool* acceptsAllTypes) {
file_type_info->include_all_files = true;
if (acceptsAllTypes)
file_type_info->include_all_files = *acceptsAllTypes;
bool need_suggestion = !file_type_info->include_all_files &&
!suggested_extension.empty();
if (accepts) {
for (const file_system::AcceptOption& option : *accepts) {
base::string16 description;
std::vector<base::FilePath::StringType> extensions;
if (!GetFileTypesFromAcceptOption(option, &extensions, &description))
continue; // No extensions were found.
file_type_info->extensions.push_back(extensions);
file_type_info->extension_description_overrides.push_back(description);
// If we still need to find suggested_extension, hunt for it inside the
// extensions returned from GetFileTypesFromAcceptOption.
if (need_suggestion && std::find(extensions.begin(),
extensions.end(), suggested_extension) != extensions.end()) {
need_suggestion = false;
}
}
}
// If there's nothing in our accepted extension list or we couldn't find the
// suggested extension required, then default to accepting all types.
if (file_type_info->extensions.empty() || need_suggestion)
file_type_info->include_all_files = true;
}
void FileSystemChooseEntryFunction::BuildSuggestion(
const std::string *opt_name,
base::FilePath* suggested_name,
base::FilePath::StringType* suggested_extension) {
if (opt_name) {
*suggested_name = base::FilePath::FromUTF8Unsafe(*opt_name);
// Don't allow any path components; shorten to the base name. This should
// result in a relative path, but in some cases may not. Clear the
// suggestion for safety if this is the case.
*suggested_name = suggested_name->BaseName();
if (suggested_name->IsAbsolute())
*suggested_name = base::FilePath();
*suggested_extension = suggested_name->Extension();
if (!suggested_extension->empty())
suggested_extension->erase(suggested_extension->begin()); // drop the .
}
}
void FileSystemChooseEntryFunction::SetInitialPathAndShowPicker(
const base::FilePath& previous_path,
const base::FilePath& suggested_name,
const ui::SelectFileDialog::FileTypeInfo& file_type_info,
ui::SelectFileDialog::Type picker_type,
bool is_previous_path_directory) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (is_previous_path_directory) {
initial_path_ = previous_path.Append(suggested_name);
} else {
base::FilePath documents_dir;
if (PathService::Get(chrome::DIR_USER_DOCUMENTS, &documents_dir)) {
initial_path_ = documents_dir.Append(suggested_name);
} else {
initial_path_ = suggested_name;
}
}
ShowPicker(file_type_info, picker_type);
}
bool FileSystemChooseEntryFunction::RunAsync() {
std::unique_ptr<ChooseEntry::Params> params(
ChooseEntry::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
base::FilePath suggested_name;
ui::SelectFileDialog::FileTypeInfo file_type_info;
ui::SelectFileDialog::Type picker_type =
ui::SelectFileDialog::SELECT_OPEN_FILE;
file_system::ChooseEntryOptions* options = params->options.get();
if (options) {
multiple_ = options->accepts_multiple && *options->accepts_multiple;
if (multiple_)
picker_type = ui::SelectFileDialog::SELECT_OPEN_MULTI_FILE;
if (options->type == file_system::CHOOSE_ENTRY_TYPE_OPENWRITABLEFILE &&
!app_file_handler_util::HasFileSystemWritePermission(
extension_.get())) {
error_ = kRequiresFileSystemWriteError;
return false;
} else if (options->type == file_system::CHOOSE_ENTRY_TYPE_SAVEFILE) {
if (!app_file_handler_util::HasFileSystemWritePermission(
extension_.get())) {
error_ = kRequiresFileSystemWriteError;
return false;
}
if (multiple_) {
error_ = kMultipleUnsupportedError;
return false;
}
picker_type = ui::SelectFileDialog::SELECT_SAVEAS_FILE;
} else if (options->type == file_system::CHOOSE_ENTRY_TYPE_OPENDIRECTORY) {
is_directory_ = true;
if (!extension_->permissions_data()->HasAPIPermission(
APIPermission::kFileSystemDirectory)) {
error_ = kRequiresFileSystemDirectoryError;
return false;
}
if (multiple_) {
error_ = kMultipleUnsupportedError;
return false;
}
picker_type = ui::SelectFileDialog::SELECT_FOLDER;
}
base::FilePath::StringType suggested_extension;
BuildSuggestion(options->suggested_name.get(), &suggested_name,
&suggested_extension);
BuildFileTypeInfo(&file_type_info, suggested_extension,
options->accepts.get(), options->accepts_all_types.get());
}
file_type_info.allowed_paths = ui::SelectFileDialog::FileTypeInfo::ANY_PATH;
base::FilePath previous_path = file_system_api::GetLastChooseEntryDirectory(
ExtensionPrefs::Get(GetProfile()), extension()->id());
if (previous_path.empty()) {
SetInitialPathAndShowPicker(previous_path, suggested_name, file_type_info,
picker_type, false);
return true;
}
base::Callback<void(bool)> set_initial_path_callback = base::Bind(
&FileSystemChooseEntryFunction::SetInitialPathAndShowPicker, this,
previous_path, suggested_name, file_type_info, picker_type);
// Check whether the |previous_path| is a non-native directory.
#if defined(OS_CHROMEOS)
if (file_manager::util::IsUnderNonNativeLocalPath(GetProfile(),
previous_path)) {
file_manager::util::IsNonNativeLocalPathDirectory(
GetProfile(), previous_path, set_initial_path_callback);
return true;
}
#endif
content::BrowserThread::PostTaskAndReplyWithResult(
content::BrowserThread::FILE, FROM_HERE,
base::Bind(&base::DirectoryExists, previous_path),
set_initial_path_callback);
return true;
}
bool FileSystemRetainEntryFunction::RunAsync() {
std::string entry_id;
EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &entry_id));
SavedFilesService* saved_files_service = SavedFilesService::Get(GetProfile());
// Add the file to the retain list if it is not already on there.
if (!saved_files_service->IsRegistered(extension_->id(), entry_id)) {
std::string filesystem_name;
std::string filesystem_path;
base::FilePath path;
EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &filesystem_name));
EXTENSION_FUNCTION_VALIDATE(args_->GetString(2, &filesystem_path));
if (!app_file_handler_util::ValidateFileEntryAndGetPath(
filesystem_name, filesystem_path,
render_frame_host()->GetProcess()->GetID(), &path, &error_)) {
return false;
}
std::string filesystem_id;
if (!storage::CrackIsolatedFileSystemName(filesystem_name, &filesystem_id))
return false;
const GURL site = util::GetSiteForExtensionId(extension_id(), GetProfile());
storage::FileSystemContext* const context =
content::BrowserContext::GetStoragePartitionForSite(GetProfile(), site)
->GetFileSystemContext();
const storage::FileSystemURL url = context->CreateCrackedFileSystemURL(
site,
storage::kFileSystemTypeIsolated,
IsolatedContext::GetInstance()
->CreateVirtualRootPath(filesystem_id)
.Append(base::FilePath::FromUTF8Unsafe(filesystem_path)));
content::BrowserThread::PostTask(
content::BrowserThread::IO, FROM_HERE,
base::Bind(
base::IgnoreResult(
&storage::FileSystemOperationRunner::GetMetadata),
context->operation_runner()->AsWeakPtr(), url,
storage::FileSystemOperation::GET_METADATA_FIELD_IS_DIRECTORY,
base::Bind(
&PassFileInfoToUIThread,
base::Bind(&FileSystemRetainEntryFunction::RetainFileEntry,
this, entry_id, path))));
return true;
}
saved_files_service->EnqueueFileEntry(extension_->id(), entry_id);
SendResponse(true);
return true;
}
void FileSystemRetainEntryFunction::RetainFileEntry(
const std::string& entry_id,
const base::FilePath& path,
std::unique_ptr<base::File::Info> file_info) {
if (!file_info) {
SendResponse(false);
return;
}
SavedFilesService* saved_files_service = SavedFilesService::Get(GetProfile());
saved_files_service->RegisterFileEntry(
extension_->id(), entry_id, path, file_info->is_directory);
saved_files_service->EnqueueFileEntry(extension_->id(), entry_id);
SendResponse(true);
}
bool FileSystemIsRestorableFunction::RunSync() {
std::string entry_id;
EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &entry_id));
SetResult(base::MakeUnique<base::FundamentalValue>(
SavedFilesService::Get(GetProfile())
->IsRegistered(extension_->id(), entry_id)));
return true;
}
bool FileSystemRestoreEntryFunction::RunAsync() {
std::string entry_id;
bool needs_new_entry;
EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &entry_id));
EXTENSION_FUNCTION_VALIDATE(args_->GetBoolean(1, &needs_new_entry));
const SavedFileEntry* file_entry = SavedFilesService::Get(
GetProfile())->GetFileEntry(extension_->id(), entry_id);
if (!file_entry) {
error_ = kUnknownIdError;
return false;
}
SavedFilesService::Get(GetProfile())
->EnqueueFileEntry(extension_->id(), entry_id);
// Only create a new file entry if the renderer requests one.
// |needs_new_entry| will be false if the renderer already has an Entry for
// |entry_id|.
if (needs_new_entry) {
is_directory_ = file_entry->is_directory;
std::unique_ptr<base::DictionaryValue> result = CreateResult();
AddEntryToResult(file_entry->path, file_entry->id, result.get());
SetResult(std::move(result));
}
SendResponse(true);
return true;
}
bool FileSystemObserveDirectoryFunction::RunSync() {
NOTIMPLEMENTED();
error_ = kUnknownIdError;
return false;
}
bool FileSystemUnobserveEntryFunction::RunSync() {
NOTIMPLEMENTED();
error_ = kUnknownIdError;
return false;
}
bool FileSystemGetObservedEntriesFunction::RunSync() {
NOTIMPLEMENTED();
error_ = kUnknownIdError;
return false;
}
#if !defined(OS_CHROMEOS)
ExtensionFunction::ResponseAction FileSystemRequestFileSystemFunction::Run() {
using api::file_system::RequestFileSystem::Params;
const std::unique_ptr<Params> params(Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params);
NOTIMPLEMENTED();
return RespondNow(Error(kNotSupportedOnCurrentPlatformError));
}
ExtensionFunction::ResponseAction FileSystemGetVolumeListFunction::Run() {
NOTIMPLEMENTED();
return RespondNow(Error(kNotSupportedOnCurrentPlatformError));
}
#else
FileSystemRequestFileSystemFunction::FileSystemRequestFileSystemFunction()
: chrome_details_(this) {
}
FileSystemRequestFileSystemFunction::~FileSystemRequestFileSystemFunction() {
}
ExtensionFunction::ResponseAction FileSystemRequestFileSystemFunction::Run() {
using api::file_system::RequestFileSystem::Params;
const std::unique_ptr<Params> params(Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params);
// Only kiosk apps in kiosk sessions can use this API.
// Additionally it is enabled for whitelisted component extensions and apps.
file_system_api::ConsentProviderDelegate consent_provider_delegate(
chrome_details_.GetProfile(), render_frame_host());
file_system_api::ConsentProvider consent_provider(&consent_provider_delegate);
if (!consent_provider.IsGrantable(*extension()))
return RespondNow(Error(kNotSupportedOnNonKioskSessionError));
using file_manager::VolumeManager;
using file_manager::Volume;
VolumeManager* const volume_manager =
VolumeManager::Get(chrome_details_.GetProfile());
DCHECK(volume_manager);
const bool writable =
params->options.writable.get() && *params->options.writable.get();
if (writable &&
!app_file_handler_util::HasFileSystemWritePermission(extension_.get())) {
return RespondNow(Error(kRequiresFileSystemWriteError));
}
base::WeakPtr<file_manager::Volume> volume =
volume_manager->FindVolumeById(params->options.volume_id);
if (!volume.get())
return RespondNow(Error(kVolumeNotFoundError));
const GURL site =
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->mount_path(), &virtual_path))
return RespondNow(Error(kSecurityError));
if (writable && (volume->is_read_only()))
return RespondNow(Error(kSecurityError));
consent_provider.RequestConsent(
*extension(), volume, writable,
base::Bind(&FileSystemRequestFileSystemFunction::OnConsentReceived, this,
volume, writable));
return RespondLater();
}
void FileSystemRequestFileSystemFunction::OnConsentReceived(
const base::WeakPtr<file_manager::Volume>& volume,
bool writable,
ConsentProvider::Consent result) {
using file_manager::VolumeManager;
using file_manager::Volume;
switch (result) {
case ConsentProvider::CONSENT_REJECTED:
SetError(kSecurityError);
SendResponse(false);
return;
case ConsentProvider::CONSENT_IMPOSSIBLE:
SetError(kConsentImpossible);
SendResponse(false);
return;
case ConsentProvider::CONSENT_GRANTED:
break;
}
if (!volume.get()) {
SetError(kVolumeNotFoundError);
SendResponse(false);
return;
}
const GURL site =
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->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(std::string(kExtensionScheme) + url::kStandardSchemeSeparator +
extension_id()),
storage::kFileSystemTypeExternal, virtual_path);
// Set a fixed register name, as the automatic one would leak the mount point
// directory.
std::string register_name = "fs";
const std::string file_system_id =
isolated_context->RegisterFileSystemForPath(
storage::kFileSystemTypeNativeForPlatformApp,
std::string() /* file_system_id */, original_url.path(),
&register_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_frame_host()->GetProcess()->GetID(),
volume->mount_path());
policy->GrantReadFileSystem(render_frame_host()->GetProcess()->GetID(),
file_system_id);
// Additional write permissions.
if (writable) {
policy->GrantCreateReadWriteFile(render_frame_host()->GetProcess()->GetID(),
volume->mount_path());
policy->GrantCopyInto(render_frame_host()->GetProcess()->GetID(),
volume->mount_path());
policy->GrantWriteFileSystem(render_frame_host()->GetProcess()->GetID(),
file_system_id);
policy->GrantDeleteFromFileSystem(
render_frame_host()->GetProcess()->GetID(), file_system_id);
policy->GrantCreateFileForFileSystem(
render_frame_host()->GetProcess()->GetID(), file_system_id);
}
std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
dict->SetString("file_system_id", file_system_id);
dict->SetString("file_system_path", register_name);
SetResult(std::move(dict));
SendResponse(true);
}
FileSystemGetVolumeListFunction::FileSystemGetVolumeListFunction()
: chrome_details_(this) {
}
FileSystemGetVolumeListFunction::~FileSystemGetVolumeListFunction() {
}
ExtensionFunction::ResponseAction FileSystemGetVolumeListFunction::Run() {
// Only kiosk apps in kiosk sessions can use this API.
// Additionally it is enabled for whitelisted component extensions and apps.
file_system_api::ConsentProviderDelegate consent_provider_delegate(
chrome_details_.GetProfile(), render_frame_host());
file_system_api::ConsentProvider consent_provider(&consent_provider_delegate);
if (!consent_provider.IsGrantable(*extension()))
return RespondNow(Error(kNotSupportedOnNonKioskSessionError));
std::vector<api::file_system::Volume> result_volume_list;
FillVolumeList(chrome_details_.GetProfile(), &result_volume_list);
return RespondNow(ArgumentList(
api::file_system::GetVolumeList::Results::Create(result_volume_list)));
}
#endif
} // namespace extensions