| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright 2017 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/extensions/api/file_system/file_system_api.h" | 5 #include "chrome/browser/extensions/api/file_system/chrome_file_system_delegate.
h" |
| 6 | 6 |
| 7 #include <stddef.h> | |
| 8 | |
| 9 #include <memory> | |
| 10 #include <set> | |
| 11 #include <utility> | 7 #include <utility> |
| 12 #include <vector> | 8 #include <vector> |
| 13 | 9 |
| 14 #include "apps/saved_files_service.h" | 10 #include "apps/saved_files_service.h" |
| 15 #include "base/bind.h" | 11 #include "base/bind.h" |
| 16 #include "base/callback.h" | 12 #include "base/callback.h" |
| 17 #include "base/files/file_path.h" | 13 #include "base/files/file_path.h" |
| 18 #include "base/files/file_util.h" | 14 #include "base/logging.h" |
| 19 #include "base/macros.h" | |
| 20 #include "base/memory/ptr_util.h" | 15 #include "base/memory/ptr_util.h" |
| 21 #include "base/path_service.h" | 16 #include "base/path_service.h" |
| 22 #include "base/stl_util.h" | 17 #include "base/strings/string16.h" |
| 23 #include "base/strings/string_util.h" | |
| 24 #include "base/strings/stringprintf.h" | |
| 25 #include "base/strings/sys_string_conversions.h" | |
| 26 #include "base/strings/utf_string_conversions.h" | |
| 27 #include "base/task_scheduler/post_task.h" | |
| 28 #include "base/value_conversions.h" | |
| 29 #include "base/values.h" | |
| 30 #include "build/build_config.h" | |
| 31 #include "chrome/browser/extensions/api/file_system/file_entry_picker.h" | 18 #include "chrome/browser/extensions/api/file_system/file_entry_picker.h" |
| 32 #include "chrome/browser/platform_util.h" | 19 #include "chrome/browser/extensions/chrome_extension_function_details.h" |
| 33 #include "chrome/browser/profiles/profile.h" | 20 #include "chrome/browser/profiles/profile.h" |
| 34 #include "chrome/browser/ui/apps/directory_access_confirmation_dialog.h" | 21 #include "chrome/browser/ui/apps/directory_access_confirmation_dialog.h" |
| 35 #include "chrome/browser/ui/chrome_select_file_policy.h" | |
| 36 #include "chrome/common/chrome_paths.h" | 22 #include "chrome/common/chrome_paths.h" |
| 37 #include "chrome/common/extensions/api/file_system.h" | |
| 38 #include "chrome/grit/generated_resources.h" | 23 #include "chrome/grit/generated_resources.h" |
| 39 #include "content/public/browser/browser_thread.h" | 24 #include "content/public/browser/browser_context.h" |
| 40 #include "content/public/browser/child_process_security_policy.h" | 25 #include "content/public/browser/child_process_security_policy.h" |
| 41 #include "content/public/browser/render_frame_host.h" | 26 #include "content/public/browser/render_frame_host.h" |
| 42 #include "content/public/browser/render_process_host.h" | 27 #include "content/public/browser/render_process_host.h" |
| 43 #include "content/public/browser/storage_partition.h" | 28 #include "content/public/browser/storage_partition.h" |
| 44 #include "content/public/browser/web_contents.h" | 29 #include "content/public/browser/web_contents.h" |
| 45 #include "extensions/browser/api/file_handlers/app_file_handler_util.h" | 30 #include "extensions/browser/api/file_handlers/app_file_handler_util.h" |
| 46 #include "extensions/browser/api/file_system/saved_file_entry.h" | 31 #include "extensions/browser/api/file_system/saved_files_service_interface.h" |
| 47 #include "extensions/browser/app_window/app_window.h" | 32 #include "extensions/browser/app_window/app_window.h" |
| 48 #include "extensions/browser/app_window/app_window_registry.h" | 33 #include "extensions/browser/app_window/app_window_registry.h" |
| 34 #include "extensions/browser/extension_function.h" |
| 49 #include "extensions/browser/extension_prefs.h" | 35 #include "extensions/browser/extension_prefs.h" |
| 50 #include "extensions/browser/extension_system.h" | 36 #include "extensions/browser/extension_system.h" |
| 51 #include "extensions/browser/extension_util.h" | 37 #include "extensions/browser/extension_util.h" |
| 52 #include "extensions/browser/granted_file_entry.h" | 38 #include "extensions/common/api/file_system.h" |
| 53 #include "extensions/browser/path_util.h" | 39 #include "extensions/common/extension.h" |
| 54 #include "extensions/common/permissions/api_permission.h" | |
| 55 #include "extensions/common/permissions/permissions_data.h" | |
| 56 #include "net/base/mime_util.h" | |
| 57 #include "storage/browser/fileapi/external_mount_points.h" | 40 #include "storage/browser/fileapi/external_mount_points.h" |
| 58 #include "storage/browser/fileapi/file_system_operation_runner.h" | |
| 59 #include "storage/browser/fileapi/isolated_context.h" | 41 #include "storage/browser/fileapi/isolated_context.h" |
| 60 #include "storage/common/fileapi/file_system_types.h" | 42 #include "storage/common/fileapi/file_system_types.h" |
| 61 #include "storage/common/fileapi/file_system_util.h" | 43 #include "storage/common/fileapi/file_system_util.h" |
| 62 #include "ui/base/l10n/l10n_util.h" | |
| 63 #include "ui/base/ui_base_types.h" | |
| 64 #include "ui/shell_dialogs/select_file_dialog.h" | 44 #include "ui/shell_dialogs/select_file_dialog.h" |
| 65 #include "ui/shell_dialogs/select_file_policy.h" | |
| 66 | 45 |
| 67 #if defined(OS_MACOSX) | 46 #if defined(OS_MACOSX) |
| 68 #include <CoreFoundation/CoreFoundation.h> | 47 #include <CoreFoundation/CoreFoundation.h> |
| 69 #include "base/mac/foundation_util.h" | 48 #include "base/mac/foundation_util.h" |
| 70 #endif | 49 #endif |
| 71 | 50 |
| 72 #if defined(OS_CHROMEOS) | 51 #if defined(OS_CHROMEOS) |
| 73 #include "base/strings/string16.h" | |
| 74 #include "chrome/browser/chromeos/file_manager/filesystem_api_util.h" | |
| 75 #include "chrome/browser/chromeos/file_manager/volume_manager.h" | 52 #include "chrome/browser/chromeos/file_manager/volume_manager.h" |
| 53 #include "chrome/browser/extensions/api/file_system/consent_provider.h" |
| 76 #include "extensions/browser/event_router.h" | 54 #include "extensions/browser/event_router.h" |
| 77 #include "extensions/browser/extension_registry.h" | 55 #include "extensions/browser/extension_registry.h" |
| 78 #include "extensions/common/constants.h" | 56 #include "extensions/common/constants.h" |
| 79 #include "url/url_constants.h" | 57 #include "url/url_constants.h" |
| 80 #endif | 58 #endif |
| 81 | 59 |
| 82 using apps::SavedFilesService; | 60 namespace extensions { |
| 83 using storage::IsolatedContext; | 61 |
| 84 | 62 namespace file_system = api::file_system; |
| 85 const char kInvalidCallingPage[] = | 63 |
| 86 "Invalid calling page. " | 64 #if defined(OS_CHROMEOS) |
| 87 "This function can't be called from a background page."; | 65 using file_system_api::ConsentProvider; |
| 88 const char kUserCancelled[] = "User cancelled"; | 66 using file_system_api::ConsentProviderDelegate; |
| 89 const char kWritableFileErrorFormat[] = "Error opening %s"; | 67 |
| 68 namespace { |
| 69 |
| 70 const char kConsentImpossible[] = |
| 71 "Impossible to ask for user consent as there is no app window visible."; |
| 72 const char kNotSupportedOnNonKioskSessionError[] = |
| 73 "Operation only supported for kiosk apps running in a kiosk session."; |
| 90 const char kRequiresFileSystemWriteError[] = | 74 const char kRequiresFileSystemWriteError[] = |
| 91 "Operation requires fileSystem.write permission"; | 75 "Operation requires fileSystem.write permission"; |
| 92 const char kRequiresFileSystemDirectoryError[] = | 76 const char kSecurityError[] = "Security error."; |
| 93 "Operation requires fileSystem.directory permission"; | |
| 94 const char kMultipleUnsupportedError[] = | |
| 95 "acceptsMultiple: true is only supported for 'openFile'"; | |
| 96 const char kUnknownIdError[] = "Unknown id"; | |
| 97 | |
| 98 #if !defined(OS_CHROMEOS) | |
| 99 const char kNotSupportedOnCurrentPlatformError[] = | |
| 100 "Operation not supported on the current platform."; | |
| 101 #else | |
| 102 const char kNotSupportedOnNonKioskSessionError[] = | |
| 103 "Operation only supported for kiosk apps running in a kiosk session."; | |
| 104 const char kVolumeNotFoundError[] = "Volume not found."; | 77 const char kVolumeNotFoundError[] = "Volume not found."; |
| 105 const char kSecurityError[] = "Security error."; | 78 |
| 106 const char kConsentImpossible[] = | |
| 107 "Impossible to ask for user consent as there is no app window visible."; | |
| 108 #endif | |
| 109 | |
| 110 namespace extensions { | |
| 111 | |
| 112 namespace file_system = api::file_system; | |
| 113 namespace ChooseEntry = file_system::ChooseEntry; | |
| 114 | |
| 115 namespace { | |
| 116 | |
| 117 bool g_skip_picker_for_test = false; | |
| 118 bool g_use_suggested_path_for_test = false; | |
| 119 base::FilePath* g_path_to_be_picked_for_test; | |
| 120 std::vector<base::FilePath>* g_paths_to_be_picked_for_test; | |
| 121 bool g_skip_directory_confirmation_for_test = false; | |
| 122 bool g_allow_directory_access_for_test = false; | |
| 123 | |
| 124 // Expand the mime-types and extensions provided in an AcceptOption, returning | |
| 125 // them within the passed extension vector. Returns false if no valid types | |
| 126 // were found. | |
| 127 bool GetFileTypesFromAcceptOption( | |
| 128 const file_system::AcceptOption& accept_option, | |
| 129 std::vector<base::FilePath::StringType>* extensions, | |
| 130 base::string16* description) { | |
| 131 std::set<base::FilePath::StringType> extension_set; | |
| 132 int description_id = 0; | |
| 133 | |
| 134 if (accept_option.mime_types.get()) { | |
| 135 std::vector<std::string>* list = accept_option.mime_types.get(); | |
| 136 bool valid_type = false; | |
| 137 for (std::vector<std::string>::const_iterator iter = list->begin(); | |
| 138 iter != list->end(); ++iter) { | |
| 139 std::vector<base::FilePath::StringType> inner; | |
| 140 std::string accept_type = base::ToLowerASCII(*iter); | |
| 141 net::GetExtensionsForMimeType(accept_type, &inner); | |
| 142 if (inner.empty()) | |
| 143 continue; | |
| 144 | |
| 145 if (valid_type) | |
| 146 description_id = 0; // We already have an accept type with label; if | |
| 147 // we find another, give up and use the default. | |
| 148 else if (accept_type == "image/*") | |
| 149 description_id = IDS_IMAGE_FILES; | |
| 150 else if (accept_type == "audio/*") | |
| 151 description_id = IDS_AUDIO_FILES; | |
| 152 else if (accept_type == "video/*") | |
| 153 description_id = IDS_VIDEO_FILES; | |
| 154 | |
| 155 extension_set.insert(inner.begin(), inner.end()); | |
| 156 valid_type = true; | |
| 157 } | |
| 158 } | |
| 159 | |
| 160 if (accept_option.extensions.get()) { | |
| 161 std::vector<std::string>* list = accept_option.extensions.get(); | |
| 162 for (std::vector<std::string>::const_iterator iter = list->begin(); | |
| 163 iter != list->end(); ++iter) { | |
| 164 std::string extension = base::ToLowerASCII(*iter); | |
| 165 #if defined(OS_WIN) | |
| 166 extension_set.insert(base::UTF8ToWide(*iter)); | |
| 167 #else | |
| 168 extension_set.insert(*iter); | |
| 169 #endif | |
| 170 } | |
| 171 } | |
| 172 | |
| 173 extensions->assign(extension_set.begin(), extension_set.end()); | |
| 174 if (extensions->empty()) | |
| 175 return false; | |
| 176 | |
| 177 if (accept_option.description.get()) | |
| 178 *description = base::UTF8ToUTF16(*accept_option.description); | |
| 179 else if (description_id) | |
| 180 *description = l10n_util::GetStringUTF16(description_id); | |
| 181 | |
| 182 return true; | |
| 183 } | |
| 184 | |
| 185 // Key for the path of the directory of the file last chosen by the user in | |
| 186 // response to a chrome.fileSystem.chooseEntry() call. | |
| 187 const char kLastChooseEntryDirectory[] = "last_choose_file_directory"; | |
| 188 | |
| 189 const int kGraylistedPaths[] = { | |
| 190 base::DIR_HOME, | |
| 191 #if defined(OS_WIN) | |
| 192 base::DIR_PROGRAM_FILES, base::DIR_PROGRAM_FILESX86, base::DIR_WINDOWS, | |
| 193 #endif | |
| 194 }; | |
| 195 | |
| 196 typedef base::Callback<void(std::unique_ptr<base::File::Info>)> | |
| 197 FileInfoOptCallback; | |
| 198 | |
| 199 // Passes optional file info to the UI thread depending on |result| and |info|. | |
| 200 void PassFileInfoToUIThread(const FileInfoOptCallback& callback, | |
| 201 base::File::Error result, | |
| 202 const base::File::Info& info) { | |
| 203 DCHECK_CURRENTLY_ON(content::BrowserThread::IO); | |
| 204 std::unique_ptr<base::File::Info> file_info( | |
| 205 result == base::File::FILE_OK ? new base::File::Info(info) : NULL); | |
| 206 content::BrowserThread::PostTask( | |
| 207 content::BrowserThread::UI, FROM_HERE, | |
| 208 base::BindOnce(callback, base::Passed(&file_info))); | |
| 209 } | |
| 210 | |
| 211 // Gets a WebContents instance handle for a platform app hosted in | |
| 212 // |render_frame_host|. If not found, then returns NULL. | |
| 213 content::WebContents* GetWebContentsForRenderFrameHost( | |
| 214 content::BrowserContext* browser_context, | |
| 215 content::RenderFrameHost* render_frame_host) { | |
| 216 content::WebContents* web_contents = | |
| 217 content::WebContents::FromRenderFrameHost(render_frame_host); | |
| 218 // Check if there is an app window associated with the web contents; if not, | |
| 219 // return null. | |
| 220 return AppWindowRegistry::Get(browser_context) | |
| 221 ->GetAppWindowForWebContents(web_contents) | |
| 222 ? web_contents | |
| 223 : nullptr; | |
| 224 } | |
| 225 | |
| 226 #if defined(OS_CHROMEOS) | |
| 227 // Fills a list of volumes mounted in the system. | 79 // Fills a list of volumes mounted in the system. |
| 228 void FillVolumeList(Profile* profile, | 80 void FillVolumeList(content::BrowserContext* browser_context, |
| 229 std::vector<api::file_system::Volume>* result) { | 81 std::vector<file_system::Volume>* result) { |
| 230 file_manager::VolumeManager* const volume_manager = | 82 file_manager::VolumeManager* const volume_manager = |
| 231 file_manager::VolumeManager::Get(profile); | 83 file_manager::VolumeManager::Get(browser_context); |
| 232 DCHECK(volume_manager); | 84 DCHECK(volume_manager); |
| 233 | 85 |
| 234 const auto& volume_list = volume_manager->GetVolumeList(); | 86 const auto& volume_list = volume_manager->GetVolumeList(); |
| 235 // Convert volume_list to result_volume_list. | 87 // Convert volume_list to result_volume_list. |
| 236 for (const auto& volume : volume_list) { | 88 for (const auto& volume : volume_list) { |
| 237 api::file_system::Volume result_volume; | 89 file_system::Volume result_volume; |
| 238 result_volume.volume_id = volume->volume_id(); | 90 result_volume.volume_id = volume->volume_id(); |
| 239 result_volume.writable = !volume->is_read_only(); | 91 result_volume.writable = !volume->is_read_only(); |
| 240 result->push_back(std::move(result_volume)); | 92 result->push_back(std::move(result_volume)); |
| 241 } | 93 } |
| 242 } | 94 } |
| 243 #endif | 95 |
| 244 | 96 // Callback called when consent is granted or denied. |
| 245 // Creates and shows a SelectFileDialog, or returns false if the dialog could | 97 void OnConsentReceived( |
| 246 // not be created. | 98 content::BrowserContext* browser_context, |
| 247 bool ShowSelectFileDialog( | 99 scoped_refptr<UIThreadExtensionFunction> requester, |
| 100 const FileSystemDelegate::FileSystemCallback& success_callback, |
| 101 const FileSystemDelegate::ErrorCallback& error_callback, |
| 102 const std::string& extension_id, |
| 103 const base::WeakPtr<file_manager::Volume>& volume, |
| 104 bool writable, |
| 105 ConsentProvider::Consent result) { |
| 106 using file_manager::VolumeManager; |
| 107 using file_manager::Volume; |
| 108 |
| 109 // Render frame host can be gone before this callback method is executed. |
| 110 if (!requester->render_frame_host()) { |
| 111 error_callback.Run(std::string()); |
| 112 return; |
| 113 } |
| 114 |
| 115 switch (result) { |
| 116 case ConsentProvider::CONSENT_REJECTED: |
| 117 error_callback.Run(kSecurityError); |
| 118 return; |
| 119 |
| 120 case ConsentProvider::CONSENT_IMPOSSIBLE: |
| 121 error_callback.Run(kConsentImpossible); |
| 122 return; |
| 123 |
| 124 case ConsentProvider::CONSENT_GRANTED: |
| 125 break; |
| 126 } |
| 127 |
| 128 if (!volume.get()) { |
| 129 error_callback.Run(kVolumeNotFoundError); |
| 130 return; |
| 131 } |
| 132 |
| 133 const GURL site = util::GetSiteForExtensionId(extension_id, browser_context); |
| 134 scoped_refptr<storage::FileSystemContext> file_system_context = |
| 135 content::BrowserContext::GetStoragePartitionForSite(browser_context, site) |
| 136 ->GetFileSystemContext(); |
| 137 storage::ExternalFileSystemBackend* const backend = |
| 138 file_system_context->external_backend(); |
| 139 DCHECK(backend); |
| 140 |
| 141 base::FilePath virtual_path; |
| 142 if (!backend->GetVirtualPath(volume->mount_path(), &virtual_path)) { |
| 143 error_callback.Run(kSecurityError); |
| 144 return; |
| 145 } |
| 146 |
| 147 storage::IsolatedContext* const isolated_context = |
| 148 storage::IsolatedContext::GetInstance(); |
| 149 DCHECK(isolated_context); |
| 150 |
| 151 const storage::FileSystemURL original_url = |
| 152 file_system_context->CreateCrackedFileSystemURL( |
| 153 GURL(std::string(kExtensionScheme) + url::kStandardSchemeSeparator + |
| 154 extension_id), |
| 155 storage::kFileSystemTypeExternal, virtual_path); |
| 156 |
| 157 // Set a fixed register name, as the automatic one would leak the mount point |
| 158 // directory. |
| 159 std::string register_name = "fs"; |
| 160 const std::string file_system_id = |
| 161 isolated_context->RegisterFileSystemForPath( |
| 162 storage::kFileSystemTypeNativeForPlatformApp, |
| 163 std::string() /* file_system_id */, original_url.path(), |
| 164 ®ister_name); |
| 165 if (file_system_id.empty()) { |
| 166 error_callback.Run(kSecurityError); |
| 167 return; |
| 168 } |
| 169 |
| 170 backend->GrantFileAccessToExtension(extension_id, virtual_path); |
| 171 |
| 172 // Grant file permissions to the renderer hosting component. |
| 173 content::ChildProcessSecurityPolicy* policy = |
| 174 content::ChildProcessSecurityPolicy::GetInstance(); |
| 175 DCHECK(policy); |
| 176 |
| 177 const auto process_id = requester->render_frame_host()->GetProcess()->GetID(); |
| 178 // Read-only permisisons. |
| 179 policy->GrantReadFile(process_id, volume->mount_path()); |
| 180 policy->GrantReadFileSystem(process_id, file_system_id); |
| 181 |
| 182 // Additional write permissions. |
| 183 if (writable) { |
| 184 policy->GrantCreateReadWriteFile(process_id, volume->mount_path()); |
| 185 policy->GrantCopyInto(process_id, volume->mount_path()); |
| 186 policy->GrantWriteFileSystem(process_id, file_system_id); |
| 187 policy->GrantDeleteFromFileSystem(process_id, file_system_id); |
| 188 policy->GrantCreateFileForFileSystem(process_id, file_system_id); |
| 189 } |
| 190 |
| 191 success_callback.Run(file_system_id, register_name); |
| 192 } |
| 193 |
| 194 } // namespace |
| 195 |
| 196 namespace file_system_api { |
| 197 |
| 198 void DispatchVolumeListChangeEvent(content::BrowserContext* browser_context) { |
| 199 DCHECK(browser_context); |
| 200 EventRouter* const event_router = EventRouter::Get(browser_context); |
| 201 if (!event_router) // Possible on shutdown. |
| 202 return; |
| 203 |
| 204 ExtensionRegistry* const registry = ExtensionRegistry::Get(browser_context); |
| 205 if (!registry) // Possible on shutdown. |
| 206 return; |
| 207 |
| 208 ConsentProviderDelegate consent_provider_delegate( |
| 209 Profile::FromBrowserContext(browser_context)); |
| 210 ConsentProvider consent_provider(&consent_provider_delegate); |
| 211 file_system::VolumeListChangedEvent event_args; |
| 212 FillVolumeList(browser_context, &event_args.volumes); |
| 213 for (const auto& extension : registry->enabled_extensions()) { |
| 214 if (!consent_provider.IsGrantable(*extension.get())) |
| 215 continue; |
| 216 event_router->DispatchEventToExtension( |
| 217 extension->id(), |
| 218 base::MakeUnique<Event>( |
| 219 events::FILE_SYSTEM_ON_VOLUME_LIST_CHANGED, |
| 220 file_system::OnVolumeListChanged::kEventName, |
| 221 file_system::OnVolumeListChanged::Create(event_args))); |
| 222 } |
| 223 } |
| 224 |
| 225 } // namespace file_system_api |
| 226 #endif // defined(OS_CHROMEOS) |
| 227 |
| 228 ChromeFileSystemDelegate::ChromeFileSystemDelegate() {} |
| 229 |
| 230 ChromeFileSystemDelegate::~ChromeFileSystemDelegate() {} |
| 231 |
| 232 base::FilePath ChromeFileSystemDelegate::GetDefaultDirectory() { |
| 233 base::FilePath documents_dir; |
| 234 PathService::Get(chrome::DIR_USER_DOCUMENTS, &documents_dir); |
| 235 return documents_dir; |
| 236 } |
| 237 |
| 238 bool ChromeFileSystemDelegate::ShowSelectFileDialog( |
| 248 scoped_refptr<UIThreadExtensionFunction> extension_function, | 239 scoped_refptr<UIThreadExtensionFunction> extension_function, |
| 249 ui::SelectFileDialog::Type type, | 240 ui::SelectFileDialog::Type type, |
| 250 const base::FilePath& default_path, | 241 const base::FilePath& default_path, |
| 251 const ui::SelectFileDialog::FileTypeInfo* file_types, | 242 const ui::SelectFileDialog::FileTypeInfo* file_types, |
| 252 FileEntryPicker::FilesSelectedCallback files_selected_callback, | 243 FileSystemDelegate::FilesSelectedCallback files_selected_callback, |
| 253 base::OnceClosure file_selection_canceled_callback) { | 244 base::OnceClosure file_selection_canceled_callback) { |
| 245 const Extension* extension = extension_function->extension(); |
| 246 content::WebContents* web_contents = nullptr; |
| 247 |
| 254 // TODO(asargent/benwells) - As a short term remediation for | 248 // TODO(asargent/benwells) - As a short term remediation for |
| 255 // crbug.com/179010 we're adding the ability for a whitelisted extension to | 249 // crbug.com/179010 we're adding the ability for a whitelisted extension to |
| 256 // use this API since chrome.fileBrowserHandler.selectFile is ChromeOS-only. | 250 // use this API since chrome.fileBrowserHandler.selectFile is ChromeOS-only. |
| 257 // Eventually we'd like a better solution and likely this code will go back | 251 // Eventually we'd like a better solution and likely this code will go back |
| 258 // to being platform-app only. | 252 // to being platform-app only. |
| 259 content::WebContents* const web_contents = | 253 if (extension->is_platform_app()) { |
| 260 extension_function->extension()->is_platform_app() | 254 content::WebContents* candidate = content::WebContents::FromRenderFrameHost( |
| 261 ? GetWebContentsForRenderFrameHost( | 255 extension_function->render_frame_host()); |
| 262 extension_function->browser_context(), | 256 // Make sure there is an app window associated with the web contents. |
| 263 extension_function->render_frame_host()) | 257 if (AppWindowRegistry::Get(extension_function->browser_context()) |
| 264 : extension_function->GetAssociatedWebContents(); | 258 ->GetAppWindowForWebContents(candidate)) { |
| 259 web_contents = candidate; |
| 260 } |
| 261 } else { |
| 262 // TODO(michaelpg): As a workaround for crbug.com/736930, allow this to work |
| 263 // from a background page by using the the extended GetAssociatedWebContents |
| 264 // function provided by ChromeExtensionFunctionDetails. This is safe because |
| 265 // only whitelisted extensions can access the chrome.fileSystem API; |
| 266 // platform apps cannot open the file picker from a background page. |
| 267 web_contents = ChromeExtensionFunctionDetails(extension_function.get()) |
| 268 .GetAssociatedWebContents(); |
| 269 } |
| 270 |
| 265 if (!web_contents) | 271 if (!web_contents) |
| 266 return false; | 272 return false; |
| 267 | 273 |
| 268 // The file picker will hold a reference to the UIThreadExtensionFunction | 274 // The file picker will hold a reference to the UIThreadExtensionFunction |
| 269 // instance, preventing its destruction (and subsequent sending of the | 275 // instance, preventing its destruction (and subsequent sending of the |
| 270 // function response) until the user has selected a file or cancelled the | 276 // function response) until the user has selected a file or cancelled the |
| 271 // picker. At that point, the picker will delete itself, which will also free | 277 // picker. At that point, the picker will delete itself, which will also free |
| 272 // the function instance. | 278 // the function instance. |
| 273 new FileEntryPicker(web_contents, default_path, *file_types, type, | 279 new FileEntryPicker(web_contents, default_path, *file_types, type, |
| 274 std::move(files_selected_callback), | 280 std::move(files_selected_callback), |
| 275 std::move(file_selection_canceled_callback)); | 281 std::move(file_selection_canceled_callback)); |
| 276 return true; | 282 return true; |
| 277 } | 283 } |
| 278 | 284 |
| 279 } // namespace | 285 void ChromeFileSystemDelegate::ConfirmSensitiveDirectoryAccess( |
| 280 | 286 bool has_write_permission, |
| 281 namespace file_system_api { | 287 const base::string16& app_name, |
| 282 | 288 content::WebContents* web_contents, |
| 283 base::FilePath GetLastChooseEntryDirectory(const ExtensionPrefs* prefs, | 289 const base::Closure& on_accept, |
| 284 const std::string& extension_id) { | 290 const base::Closure& on_cancel) { |
| 285 base::FilePath path; | 291 CreateDirectoryAccessConfirmationDialog(has_write_permission, app_name, |
| 286 std::string string_path; | 292 web_contents, on_accept, on_cancel); |
| 287 if (prefs->ReadPrefAsString(extension_id, kLastChooseEntryDirectory, | 293 } |
| 288 &string_path)) { | 294 |
| 289 path = base::FilePath::FromUTF8Unsafe(string_path); | 295 int ChromeFileSystemDelegate::GetDescriptionIdForAcceptType( |
| 290 } | 296 const std::string& accept_type) { |
| 291 return path; | 297 if (accept_type == "image/*") |
| 292 } | 298 return IDS_IMAGE_FILES; |
| 293 | 299 if (accept_type == "audio/*") |
| 294 void SetLastChooseEntryDirectory(ExtensionPrefs* prefs, | 300 return IDS_AUDIO_FILES; |
| 295 const std::string& extension_id, | 301 if (accept_type == "video/*") |
| 296 const base::FilePath& path) { | 302 return IDS_VIDEO_FILES; |
| 297 prefs->UpdateExtensionPref(extension_id, kLastChooseEntryDirectory, | 303 return 0; |
| 298 base::CreateFilePathValue(path)); | |
| 299 } | 304 } |
| 300 | 305 |
| 301 #if defined(OS_CHROMEOS) | 306 #if defined(OS_CHROMEOS) |
| 302 void DispatchVolumeListChangeEvent(Profile* profile) { | 307 bool ChromeFileSystemDelegate::IsGrantable( |
| 303 DCHECK(profile); | 308 content::BrowserContext* browser_context, |
| 304 EventRouter* const event_router = EventRouter::Get(profile); | 309 content::RenderFrameHost* render_frame_host, |
| 305 if (!event_router) // Possible on shutdown. | 310 const Extension& extension) { |
| 306 return; | |
| 307 | |
| 308 ExtensionRegistry* const registry = ExtensionRegistry::Get(profile); | |
| 309 if (!registry) // Possible on shutdown. | |
| 310 return; | |
| 311 | |
| 312 ConsentProviderDelegate consent_provider_delegate(profile, nullptr); | |
| 313 ConsentProvider consent_provider(&consent_provider_delegate); | |
| 314 api::file_system::VolumeListChangedEvent event_args; | |
| 315 FillVolumeList(profile, &event_args.volumes); | |
| 316 for (const auto& extension : registry->enabled_extensions()) { | |
| 317 if (!consent_provider.IsGrantable(*extension.get())) | |
| 318 continue; | |
| 319 event_router->DispatchEventToExtension( | |
| 320 extension->id(), | |
| 321 base::MakeUnique<Event>( | |
| 322 events::FILE_SYSTEM_ON_VOLUME_LIST_CHANGED, | |
| 323 api::file_system::OnVolumeListChanged::kEventName, | |
| 324 api::file_system::OnVolumeListChanged::Create(event_args))); | |
| 325 } | |
| 326 } | |
| 327 #endif | |
| 328 | |
| 329 } // namespace file_system_api | |
| 330 | |
| 331 #if defined(OS_CHROMEOS) | |
| 332 using file_system_api::ConsentProvider; | |
| 333 #endif | |
| 334 | |
| 335 ExtensionFunction::ResponseAction FileSystemGetDisplayPathFunction::Run() { | |
| 336 std::string filesystem_name; | |
| 337 std::string filesystem_path; | |
| 338 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &filesystem_name)); | |
| 339 EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &filesystem_path)); | |
| 340 | |
| 341 base::FilePath file_path; | |
| 342 std::string error; | |
| 343 if (!app_file_handler_util::ValidateFileEntryAndGetPath( | |
| 344 filesystem_name, filesystem_path, | |
| 345 render_frame_host()->GetProcess()->GetID(), &file_path, &error)) { | |
| 346 return RespondNow(Error(error)); | |
| 347 } | |
| 348 | |
| 349 file_path = path_util::PrettifyPath(file_path); | |
| 350 return RespondNow( | |
| 351 OneArgument(base::MakeUnique<base::Value>(file_path.value()))); | |
| 352 } | |
| 353 | |
| 354 FileSystemEntryFunction::FileSystemEntryFunction() | |
| 355 : multiple_(false), is_directory_(false) {} | |
| 356 | |
| 357 void FileSystemEntryFunction::PrepareFilesForWritableApp( | |
| 358 const std::vector<base::FilePath>& paths) { | |
| 359 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | |
| 360 // TODO(cmihail): Path directory set should be initialized only with the | |
| 361 // paths that are actually directories, but for now we will consider | |
| 362 // all paths directories in case is_directory_ is true, otherwise | |
| 363 // all paths files, as this was the previous logic. | |
| 364 std::set<base::FilePath> path_directory_set_ = | |
| 365 is_directory_ ? std::set<base::FilePath>(paths.begin(), paths.end()) | |
| 366 : std::set<base::FilePath>{}; | |
| 367 app_file_handler_util::PrepareFilesForWritableApp( | |
| 368 paths, GetProfile(), path_directory_set_, | |
| 369 base::Bind(&FileSystemEntryFunction::RegisterFileSystemsAndSendResponse, | |
| 370 this, paths), | |
| 371 base::Bind(&FileSystemEntryFunction::HandleWritableFileError, this)); | |
| 372 } | |
| 373 | |
| 374 void FileSystemEntryFunction::RegisterFileSystemsAndSendResponse( | |
| 375 const std::vector<base::FilePath>& paths) { | |
| 376 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | |
| 377 if (!render_frame_host()) | |
| 378 return; | |
| 379 | |
| 380 std::unique_ptr<base::DictionaryValue> result = CreateResult(); | |
| 381 for (const auto& path : paths) | |
| 382 AddEntryToResult(path, std::string(), result.get()); | |
| 383 SetResult(std::move(result)); | |
| 384 SendResponse(true); | |
| 385 } | |
| 386 | |
| 387 std::unique_ptr<base::DictionaryValue> FileSystemEntryFunction::CreateResult() { | |
| 388 std::unique_ptr<base::DictionaryValue> result(new base::DictionaryValue()); | |
| 389 result->Set("entries", base::MakeUnique<base::ListValue>()); | |
| 390 result->SetBoolean("multiple", multiple_); | |
| 391 return result; | |
| 392 } | |
| 393 | |
| 394 void FileSystemEntryFunction::AddEntryToResult(const base::FilePath& path, | |
| 395 const std::string& id_override, | |
| 396 base::DictionaryValue* result) { | |
| 397 GrantedFileEntry file_entry = app_file_handler_util::CreateFileEntry( | |
| 398 GetProfile(), extension(), render_frame_host()->GetProcess()->GetID(), | |
| 399 path, is_directory_); | |
| 400 base::ListValue* entries; | |
| 401 bool success = result->GetList("entries", &entries); | |
| 402 DCHECK(success); | |
| 403 | |
| 404 std::unique_ptr<base::DictionaryValue> entry(new base::DictionaryValue()); | |
| 405 entry->SetString("fileSystemId", file_entry.filesystem_id); | |
| 406 entry->SetString("baseName", file_entry.registered_name); | |
| 407 if (id_override.empty()) | |
| 408 entry->SetString("id", file_entry.id); | |
| 409 else | |
| 410 entry->SetString("id", id_override); | |
| 411 entry->SetBoolean("isDirectory", is_directory_); | |
| 412 entries->Append(std::move(entry)); | |
| 413 } | |
| 414 | |
| 415 void FileSystemEntryFunction::HandleWritableFileError( | |
| 416 const base::FilePath& error_path) { | |
| 417 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | |
| 418 error_ = base::StringPrintf(kWritableFileErrorFormat, | |
| 419 error_path.BaseName().AsUTF8Unsafe().c_str()); | |
| 420 SendResponse(false); | |
| 421 } | |
| 422 | |
| 423 bool FileSystemGetWritableEntryFunction::RunAsync() { | |
| 424 std::string filesystem_name; | |
| 425 std::string filesystem_path; | |
| 426 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &filesystem_name)); | |
| 427 EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &filesystem_path)); | |
| 428 | |
| 429 if (!app_file_handler_util::HasFileSystemWritePermission(extension_.get())) { | |
| 430 error_ = kRequiresFileSystemWriteError; | |
| 431 return false; | |
| 432 } | |
| 433 | |
| 434 if (!app_file_handler_util::ValidateFileEntryAndGetPath( | |
| 435 filesystem_name, filesystem_path, | |
| 436 render_frame_host()->GetProcess()->GetID(), &path_, &error_)) | |
| 437 return false; | |
| 438 | |
| 439 base::PostTaskWithTraitsAndReply( | |
| 440 FROM_HERE, {base::MayBlock(), base::TaskPriority::BACKGROUND}, | |
| 441 base::BindOnce(&FileSystemGetWritableEntryFunction::SetIsDirectoryAsync, | |
| 442 this), | |
| 443 base::BindOnce( | |
| 444 &FileSystemGetWritableEntryFunction::CheckPermissionAndSendResponse, | |
| 445 this)); | |
| 446 return true; | |
| 447 } | |
| 448 | |
| 449 void FileSystemGetWritableEntryFunction::CheckPermissionAndSendResponse() { | |
| 450 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | |
| 451 if (is_directory_ && !extension_->permissions_data()->HasAPIPermission( | |
| 452 APIPermission::kFileSystemDirectory)) { | |
| 453 error_ = kRequiresFileSystemDirectoryError; | |
| 454 SendResponse(false); | |
| 455 } | |
| 456 std::vector<base::FilePath> paths; | |
| 457 paths.push_back(path_); | |
| 458 PrepareFilesForWritableApp(paths); | |
| 459 } | |
| 460 | |
| 461 void FileSystemGetWritableEntryFunction::SetIsDirectoryAsync() { | |
| 462 if (base::DirectoryExists(path_)) { | |
| 463 is_directory_ = true; | |
| 464 } | |
| 465 } | |
| 466 | |
| 467 ExtensionFunction::ResponseAction FileSystemIsWritableEntryFunction::Run() { | |
| 468 std::string filesystem_name; | |
| 469 std::string filesystem_path; | |
| 470 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &filesystem_name)); | |
| 471 EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &filesystem_path)); | |
| 472 | |
| 473 std::string filesystem_id; | |
| 474 if (!storage::CrackIsolatedFileSystemName(filesystem_name, &filesystem_id)) | |
| 475 return RespondNow(Error(app_file_handler_util::kInvalidParameters)); | |
| 476 | |
| 477 content::ChildProcessSecurityPolicy* policy = | |
| 478 content::ChildProcessSecurityPolicy::GetInstance(); | |
| 479 int renderer_id = render_frame_host()->GetProcess()->GetID(); | |
| 480 bool is_writable = policy->CanReadWriteFileSystem(renderer_id, filesystem_id); | |
| 481 | |
| 482 return RespondNow(OneArgument(base::MakeUnique<base::Value>(is_writable))); | |
| 483 } | |
| 484 | |
| 485 void FileSystemChooseEntryFunction::ShowPicker( | |
| 486 const ui::SelectFileDialog::FileTypeInfo& file_type_info, | |
| 487 ui::SelectFileDialog::Type picker_type) { | |
| 488 if (g_skip_picker_for_test) { | |
| 489 std::vector<base::FilePath> test_paths; | |
| 490 if (g_use_suggested_path_for_test) | |
| 491 test_paths.push_back(initial_path_); | |
| 492 else if (g_path_to_be_picked_for_test) | |
| 493 test_paths.push_back(*g_path_to_be_picked_for_test); | |
| 494 else if (g_paths_to_be_picked_for_test) | |
| 495 test_paths = *g_paths_to_be_picked_for_test; | |
| 496 | |
| 497 content::BrowserThread::PostTask( | |
| 498 content::BrowserThread::UI, FROM_HERE, | |
| 499 test_paths.size() > 0 | |
| 500 ? base::BindOnce(&FileSystemChooseEntryFunction::FilesSelected, | |
| 501 this, test_paths) | |
| 502 : base::BindOnce( | |
| 503 &FileSystemChooseEntryFunction::FileSelectionCanceled, this)); | |
| 504 return; | |
| 505 } | |
| 506 | |
| 507 // The callbacks passed to the dialog will retain references to this | |
| 508 // UIThreadExtenisonFunction, preventing its destruction (and subsequent | |
| 509 // sending of the function response) until the user has selected a file or | |
| 510 // cancelled the picker. | |
| 511 if (!ShowSelectFileDialog( | |
| 512 this, picker_type, initial_path_, &file_type_info, | |
| 513 base::BindOnce(&FileSystemChooseEntryFunction::FilesSelected, this), | |
| 514 base::BindOnce(&FileSystemChooseEntryFunction::FileSelectionCanceled, | |
| 515 this))) { | |
| 516 error_ = kInvalidCallingPage; | |
| 517 SendResponse(false); | |
| 518 } | |
| 519 } | |
| 520 | |
| 521 // static | |
| 522 void FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathForTest( | |
| 523 base::FilePath* path) { | |
| 524 g_skip_picker_for_test = true; | |
| 525 g_use_suggested_path_for_test = false; | |
| 526 g_path_to_be_picked_for_test = path; | |
| 527 g_paths_to_be_picked_for_test = NULL; | |
| 528 } | |
| 529 | |
| 530 void FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathsForTest( | |
| 531 std::vector<base::FilePath>* paths) { | |
| 532 g_skip_picker_for_test = true; | |
| 533 g_use_suggested_path_for_test = false; | |
| 534 g_paths_to_be_picked_for_test = paths; | |
| 535 } | |
| 536 | |
| 537 // static | |
| 538 void FileSystemChooseEntryFunction::SkipPickerAndSelectSuggestedPathForTest() { | |
| 539 g_skip_picker_for_test = true; | |
| 540 g_use_suggested_path_for_test = true; | |
| 541 g_path_to_be_picked_for_test = NULL; | |
| 542 g_paths_to_be_picked_for_test = NULL; | |
| 543 } | |
| 544 | |
| 545 // static | |
| 546 void FileSystemChooseEntryFunction::SkipPickerAndAlwaysCancelForTest() { | |
| 547 g_skip_picker_for_test = true; | |
| 548 g_use_suggested_path_for_test = false; | |
| 549 g_path_to_be_picked_for_test = NULL; | |
| 550 g_paths_to_be_picked_for_test = NULL; | |
| 551 } | |
| 552 | |
| 553 // static | |
| 554 void FileSystemChooseEntryFunction::StopSkippingPickerForTest() { | |
| 555 g_skip_picker_for_test = false; | |
| 556 } | |
| 557 | |
| 558 // static | |
| 559 void FileSystemChooseEntryFunction::SkipDirectoryConfirmationForTest() { | |
| 560 g_skip_directory_confirmation_for_test = true; | |
| 561 g_allow_directory_access_for_test = true; | |
| 562 } | |
| 563 | |
| 564 // static | |
| 565 void FileSystemChooseEntryFunction::AutoCancelDirectoryConfirmationForTest() { | |
| 566 g_skip_directory_confirmation_for_test = true; | |
| 567 g_allow_directory_access_for_test = false; | |
| 568 } | |
| 569 | |
| 570 // static | |
| 571 void FileSystemChooseEntryFunction::StopSkippingDirectoryConfirmationForTest() { | |
| 572 g_skip_directory_confirmation_for_test = false; | |
| 573 } | |
| 574 | |
| 575 // static | |
| 576 void FileSystemChooseEntryFunction::RegisterTempExternalFileSystemForTest( | |
| 577 const std::string& name, | |
| 578 const base::FilePath& path) { | |
| 579 // For testing on Chrome OS, where to deal with remote and local paths | |
| 580 // smoothly, all accessed paths need to be registered in the list of | |
| 581 // external mount points. | |
| 582 storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem( | |
| 583 name, storage::kFileSystemTypeNativeLocal, | |
| 584 storage::FileSystemMountOption(), path); | |
| 585 } | |
| 586 | |
| 587 void FileSystemChooseEntryFunction::FilesSelected( | |
| 588 const std::vector<base::FilePath>& paths) { | |
| 589 DCHECK(!paths.empty()); | |
| 590 base::FilePath last_choose_directory; | |
| 591 if (is_directory_) { | |
| 592 last_choose_directory = paths[0]; | |
| 593 } else { | |
| 594 last_choose_directory = paths[0].DirName(); | |
| 595 } | |
| 596 file_system_api::SetLastChooseEntryDirectory( | |
| 597 ExtensionPrefs::Get(GetProfile()), extension()->id(), | |
| 598 last_choose_directory); | |
| 599 if (is_directory_) { | |
| 600 // Get the WebContents for the app window to be the parent window of the | |
| 601 // confirmation dialog if necessary. | |
| 602 content::WebContents* const web_contents = | |
| 603 GetWebContentsForRenderFrameHost(GetProfile(), render_frame_host()); | |
| 604 if (!web_contents) { | |
| 605 error_ = kInvalidCallingPage; | |
| 606 SendResponse(false); | |
| 607 return; | |
| 608 } | |
| 609 | |
| 610 DCHECK_EQ(paths.size(), 1u); | |
| 611 bool non_native_path = false; | |
| 612 #if defined(OS_CHROMEOS) | |
| 613 non_native_path = | |
| 614 file_manager::util::IsUnderNonNativeLocalPath(GetProfile(), paths[0]); | |
| 615 #endif | |
| 616 | |
| 617 base::PostTaskWithTraits( | |
| 618 FROM_HERE, {base::MayBlock(), base::TaskPriority::BACKGROUND}, | |
| 619 base::BindOnce( | |
| 620 &FileSystemChooseEntryFunction::ConfirmDirectoryAccessAsync, this, | |
| 621 non_native_path, paths, web_contents)); | |
| 622 return; | |
| 623 } | |
| 624 | |
| 625 OnDirectoryAccessConfirmed(paths); | |
| 626 } | |
| 627 | |
| 628 void FileSystemChooseEntryFunction::FileSelectionCanceled() { | |
| 629 error_ = kUserCancelled; | |
| 630 SendResponse(false); | |
| 631 } | |
| 632 | |
| 633 void FileSystemChooseEntryFunction::ConfirmDirectoryAccessAsync( | |
| 634 bool non_native_path, | |
| 635 const std::vector<base::FilePath>& paths, | |
| 636 content::WebContents* web_contents) { | |
| 637 const base::FilePath check_path = | |
| 638 non_native_path ? paths[0] : base::MakeAbsoluteFilePath(paths[0]); | |
| 639 if (check_path.empty()) { | |
| 640 content::BrowserThread::PostTask( | |
| 641 content::BrowserThread::UI, FROM_HERE, | |
| 642 base::BindOnce(&FileSystemChooseEntryFunction::FileSelectionCanceled, | |
| 643 this)); | |
| 644 return; | |
| 645 } | |
| 646 | |
| 647 for (size_t i = 0; i < arraysize(kGraylistedPaths); i++) { | |
| 648 base::FilePath graylisted_path; | |
| 649 if (PathService::Get(kGraylistedPaths[i], &graylisted_path) && | |
| 650 (check_path == graylisted_path || | |
| 651 check_path.IsParent(graylisted_path))) { | |
| 652 if (g_skip_directory_confirmation_for_test) { | |
| 653 if (g_allow_directory_access_for_test) { | |
| 654 break; | |
| 655 } else { | |
| 656 content::BrowserThread::PostTask( | |
| 657 content::BrowserThread::UI, FROM_HERE, | |
| 658 base::BindOnce( | |
| 659 &FileSystemChooseEntryFunction::FileSelectionCanceled, this)); | |
| 660 } | |
| 661 return; | |
| 662 } | |
| 663 | |
| 664 content::BrowserThread::PostTask( | |
| 665 content::BrowserThread::UI, FROM_HERE, | |
| 666 base::BindOnce( | |
| 667 CreateDirectoryAccessConfirmationDialog, | |
| 668 app_file_handler_util::HasFileSystemWritePermission( | |
| 669 extension_.get()), | |
| 670 base::UTF8ToUTF16(extension_->name()), web_contents, | |
| 671 base::Bind( | |
| 672 &FileSystemChooseEntryFunction::OnDirectoryAccessConfirmed, | |
| 673 this, paths), | |
| 674 base::Bind(&FileSystemChooseEntryFunction::FileSelectionCanceled, | |
| 675 this))); | |
| 676 return; | |
| 677 } | |
| 678 } | |
| 679 | |
| 680 content::BrowserThread::PostTask( | |
| 681 content::BrowserThread::UI, FROM_HERE, | |
| 682 base::BindOnce(&FileSystemChooseEntryFunction::OnDirectoryAccessConfirmed, | |
| 683 this, paths)); | |
| 684 } | |
| 685 | |
| 686 void FileSystemChooseEntryFunction::OnDirectoryAccessConfirmed( | |
| 687 const std::vector<base::FilePath>& paths) { | |
| 688 if (app_file_handler_util::HasFileSystemWritePermission(extension_.get())) { | |
| 689 PrepareFilesForWritableApp(paths); | |
| 690 return; | |
| 691 } | |
| 692 | |
| 693 // Don't need to check the file, it's for reading. | |
| 694 RegisterFileSystemsAndSendResponse(paths); | |
| 695 } | |
| 696 | |
| 697 void FileSystemChooseEntryFunction::BuildFileTypeInfo( | |
| 698 ui::SelectFileDialog::FileTypeInfo* file_type_info, | |
| 699 const base::FilePath::StringType& suggested_extension, | |
| 700 const AcceptOptions* accepts, | |
| 701 const bool* acceptsAllTypes) { | |
| 702 file_type_info->include_all_files = true; | |
| 703 if (acceptsAllTypes) | |
| 704 file_type_info->include_all_files = *acceptsAllTypes; | |
| 705 | |
| 706 bool need_suggestion = | |
| 707 !file_type_info->include_all_files && !suggested_extension.empty(); | |
| 708 | |
| 709 if (accepts) { | |
| 710 for (const file_system::AcceptOption& option : *accepts) { | |
| 711 base::string16 description; | |
| 712 std::vector<base::FilePath::StringType> extensions; | |
| 713 | |
| 714 if (!GetFileTypesFromAcceptOption(option, &extensions, &description)) | |
| 715 continue; // No extensions were found. | |
| 716 | |
| 717 file_type_info->extensions.push_back(extensions); | |
| 718 file_type_info->extension_description_overrides.push_back(description); | |
| 719 | |
| 720 // If we still need to find suggested_extension, hunt for it inside the | |
| 721 // extensions returned from GetFileTypesFromAcceptOption. | |
| 722 if (need_suggestion && | |
| 723 base::ContainsValue(extensions, suggested_extension)) { | |
| 724 need_suggestion = false; | |
| 725 } | |
| 726 } | |
| 727 } | |
| 728 | |
| 729 // If there's nothing in our accepted extension list or we couldn't find the | |
| 730 // suggested extension required, then default to accepting all types. | |
| 731 if (file_type_info->extensions.empty() || need_suggestion) | |
| 732 file_type_info->include_all_files = true; | |
| 733 } | |
| 734 | |
| 735 void FileSystemChooseEntryFunction::BuildSuggestion( | |
| 736 const std::string* opt_name, | |
| 737 base::FilePath* suggested_name, | |
| 738 base::FilePath::StringType* suggested_extension) { | |
| 739 if (opt_name) { | |
| 740 *suggested_name = base::FilePath::FromUTF8Unsafe(*opt_name); | |
| 741 | |
| 742 // Don't allow any path components; shorten to the base name. This should | |
| 743 // result in a relative path, but in some cases may not. Clear the | |
| 744 // suggestion for safety if this is the case. | |
| 745 *suggested_name = suggested_name->BaseName(); | |
| 746 if (suggested_name->IsAbsolute()) | |
| 747 *suggested_name = base::FilePath(); | |
| 748 | |
| 749 *suggested_extension = suggested_name->Extension(); | |
| 750 if (!suggested_extension->empty()) | |
| 751 suggested_extension->erase(suggested_extension->begin()); // drop the . | |
| 752 } | |
| 753 } | |
| 754 | |
| 755 void FileSystemChooseEntryFunction::SetInitialPathAndShowPicker( | |
| 756 const base::FilePath& previous_path, | |
| 757 const base::FilePath& suggested_name, | |
| 758 const ui::SelectFileDialog::FileTypeInfo& file_type_info, | |
| 759 ui::SelectFileDialog::Type picker_type, | |
| 760 bool is_previous_path_directory) { | |
| 761 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | |
| 762 if (is_previous_path_directory) { | |
| 763 initial_path_ = previous_path.Append(suggested_name); | |
| 764 } else { | |
| 765 base::FilePath documents_dir; | |
| 766 if (PathService::Get(chrome::DIR_USER_DOCUMENTS, &documents_dir)) { | |
| 767 initial_path_ = documents_dir.Append(suggested_name); | |
| 768 } else { | |
| 769 initial_path_ = suggested_name; | |
| 770 } | |
| 771 } | |
| 772 ShowPicker(file_type_info, picker_type); | |
| 773 } | |
| 774 | |
| 775 bool FileSystemChooseEntryFunction::RunAsync() { | |
| 776 std::unique_ptr<ChooseEntry::Params> params( | |
| 777 ChooseEntry::Params::Create(*args_)); | |
| 778 EXTENSION_FUNCTION_VALIDATE(params.get()); | |
| 779 | |
| 780 base::FilePath suggested_name; | |
| 781 ui::SelectFileDialog::FileTypeInfo file_type_info; | |
| 782 ui::SelectFileDialog::Type picker_type = | |
| 783 ui::SelectFileDialog::SELECT_OPEN_FILE; | |
| 784 | |
| 785 file_system::ChooseEntryOptions* options = params->options.get(); | |
| 786 if (options) { | |
| 787 multiple_ = options->accepts_multiple && *options->accepts_multiple; | |
| 788 if (multiple_) | |
| 789 picker_type = ui::SelectFileDialog::SELECT_OPEN_MULTI_FILE; | |
| 790 | |
| 791 if (options->type == file_system::CHOOSE_ENTRY_TYPE_OPENWRITABLEFILE && | |
| 792 !app_file_handler_util::HasFileSystemWritePermission( | |
| 793 extension_.get())) { | |
| 794 error_ = kRequiresFileSystemWriteError; | |
| 795 return false; | |
| 796 } else if (options->type == file_system::CHOOSE_ENTRY_TYPE_SAVEFILE) { | |
| 797 if (!app_file_handler_util::HasFileSystemWritePermission( | |
| 798 extension_.get())) { | |
| 799 error_ = kRequiresFileSystemWriteError; | |
| 800 return false; | |
| 801 } | |
| 802 if (multiple_) { | |
| 803 error_ = kMultipleUnsupportedError; | |
| 804 return false; | |
| 805 } | |
| 806 picker_type = ui::SelectFileDialog::SELECT_SAVEAS_FILE; | |
| 807 } else if (options->type == file_system::CHOOSE_ENTRY_TYPE_OPENDIRECTORY) { | |
| 808 is_directory_ = true; | |
| 809 if (!extension_->permissions_data()->HasAPIPermission( | |
| 810 APIPermission::kFileSystemDirectory)) { | |
| 811 error_ = kRequiresFileSystemDirectoryError; | |
| 812 return false; | |
| 813 } | |
| 814 if (multiple_) { | |
| 815 error_ = kMultipleUnsupportedError; | |
| 816 return false; | |
| 817 } | |
| 818 picker_type = ui::SelectFileDialog::SELECT_FOLDER; | |
| 819 } | |
| 820 | |
| 821 base::FilePath::StringType suggested_extension; | |
| 822 BuildSuggestion(options->suggested_name.get(), &suggested_name, | |
| 823 &suggested_extension); | |
| 824 | |
| 825 BuildFileTypeInfo(&file_type_info, suggested_extension, | |
| 826 options->accepts.get(), options->accepts_all_types.get()); | |
| 827 } | |
| 828 | |
| 829 file_type_info.allowed_paths = ui::SelectFileDialog::FileTypeInfo::ANY_PATH; | |
| 830 | |
| 831 base::FilePath previous_path = file_system_api::GetLastChooseEntryDirectory( | |
| 832 ExtensionPrefs::Get(GetProfile()), extension()->id()); | |
| 833 | |
| 834 if (previous_path.empty()) { | |
| 835 SetInitialPathAndShowPicker(previous_path, suggested_name, file_type_info, | |
| 836 picker_type, false); | |
| 837 return true; | |
| 838 } | |
| 839 | |
| 840 base::Callback<void(bool)> set_initial_path_callback = base::Bind( | |
| 841 &FileSystemChooseEntryFunction::SetInitialPathAndShowPicker, this, | |
| 842 previous_path, suggested_name, file_type_info, picker_type); | |
| 843 | |
| 844 // Check whether the |previous_path| is a non-native directory. | |
| 845 #if defined(OS_CHROMEOS) | |
| 846 if (file_manager::util::IsUnderNonNativeLocalPath(GetProfile(), | |
| 847 previous_path)) { | |
| 848 file_manager::util::IsNonNativeLocalPathDirectory( | |
| 849 GetProfile(), previous_path, set_initial_path_callback); | |
| 850 return true; | |
| 851 } | |
| 852 #endif | |
| 853 base::PostTaskWithTraitsAndReplyWithResult( | |
| 854 FROM_HERE, {base::MayBlock(), base::TaskPriority::BACKGROUND}, | |
| 855 base::Bind(&base::DirectoryExists, previous_path), | |
| 856 set_initial_path_callback); | |
| 857 | |
| 858 return true; | |
| 859 } | |
| 860 | |
| 861 bool FileSystemRetainEntryFunction::RunAsync() { | |
| 862 std::string entry_id; | |
| 863 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &entry_id)); | |
| 864 SavedFilesService* saved_files_service = SavedFilesService::Get(GetProfile()); | |
| 865 // Add the file to the retain list if it is not already on there. | |
| 866 if (!saved_files_service->IsRegistered(extension_->id(), entry_id)) { | |
| 867 std::string filesystem_name; | |
| 868 std::string filesystem_path; | |
| 869 base::FilePath path; | |
| 870 EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &filesystem_name)); | |
| 871 EXTENSION_FUNCTION_VALIDATE(args_->GetString(2, &filesystem_path)); | |
| 872 if (!app_file_handler_util::ValidateFileEntryAndGetPath( | |
| 873 filesystem_name, filesystem_path, | |
| 874 render_frame_host()->GetProcess()->GetID(), &path, &error_)) { | |
| 875 return false; | |
| 876 } | |
| 877 | |
| 878 std::string filesystem_id; | |
| 879 if (!storage::CrackIsolatedFileSystemName(filesystem_name, &filesystem_id)) | |
| 880 return false; | |
| 881 | |
| 882 const GURL site = util::GetSiteForExtensionId(extension_id(), GetProfile()); | |
| 883 storage::FileSystemContext* const context = | |
| 884 content::BrowserContext::GetStoragePartitionForSite(GetProfile(), site) | |
| 885 ->GetFileSystemContext(); | |
| 886 const storage::FileSystemURL url = context->CreateCrackedFileSystemURL( | |
| 887 site, storage::kFileSystemTypeIsolated, | |
| 888 IsolatedContext::GetInstance() | |
| 889 ->CreateVirtualRootPath(filesystem_id) | |
| 890 .Append(base::FilePath::FromUTF8Unsafe(filesystem_path))); | |
| 891 | |
| 892 content::BrowserThread::PostTask( | |
| 893 content::BrowserThread::IO, FROM_HERE, | |
| 894 base::BindOnce( | |
| 895 base::IgnoreResult( | |
| 896 &storage::FileSystemOperationRunner::GetMetadata), | |
| 897 context->operation_runner()->AsWeakPtr(), url, | |
| 898 storage::FileSystemOperation::GET_METADATA_FIELD_IS_DIRECTORY, | |
| 899 base::Bind( | |
| 900 &PassFileInfoToUIThread, | |
| 901 base::Bind(&FileSystemRetainEntryFunction::RetainFileEntry, | |
| 902 this, entry_id, path)))); | |
| 903 return true; | |
| 904 } | |
| 905 | |
| 906 saved_files_service->EnqueueFileEntry(extension_->id(), entry_id); | |
| 907 SendResponse(true); | |
| 908 return true; | |
| 909 } | |
| 910 | |
| 911 void FileSystemRetainEntryFunction::RetainFileEntry( | |
| 912 const std::string& entry_id, | |
| 913 const base::FilePath& path, | |
| 914 std::unique_ptr<base::File::Info> file_info) { | |
| 915 if (!file_info) { | |
| 916 SendResponse(false); | |
| 917 return; | |
| 918 } | |
| 919 | |
| 920 SavedFilesService* saved_files_service = SavedFilesService::Get(GetProfile()); | |
| 921 saved_files_service->RegisterFileEntry(extension_->id(), entry_id, path, | |
| 922 file_info->is_directory); | |
| 923 saved_files_service->EnqueueFileEntry(extension_->id(), entry_id); | |
| 924 SendResponse(true); | |
| 925 } | |
| 926 | |
| 927 ExtensionFunction::ResponseAction FileSystemIsRestorableFunction::Run() { | |
| 928 std::string entry_id; | |
| 929 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &entry_id)); | |
| 930 return RespondNow(OneArgument(base::MakeUnique<base::Value>( | |
| 931 SavedFilesService::Get(Profile::FromBrowserContext(browser_context())) | |
| 932 ->IsRegistered(extension_->id(), entry_id)))); | |
| 933 } | |
| 934 | |
| 935 bool FileSystemRestoreEntryFunction::RunAsync() { | |
| 936 std::string entry_id; | |
| 937 bool needs_new_entry; | |
| 938 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &entry_id)); | |
| 939 EXTENSION_FUNCTION_VALIDATE(args_->GetBoolean(1, &needs_new_entry)); | |
| 940 const SavedFileEntry* file_entry = | |
| 941 SavedFilesService::Get(GetProfile()) | |
| 942 ->GetFileEntry(extension_->id(), entry_id); | |
| 943 if (!file_entry) { | |
| 944 error_ = kUnknownIdError; | |
| 945 return false; | |
| 946 } | |
| 947 | |
| 948 SavedFilesService::Get(GetProfile()) | |
| 949 ->EnqueueFileEntry(extension_->id(), entry_id); | |
| 950 | |
| 951 // Only create a new file entry if the renderer requests one. | |
| 952 // |needs_new_entry| will be false if the renderer already has an Entry for | |
| 953 // |entry_id|. | |
| 954 if (needs_new_entry) { | |
| 955 is_directory_ = file_entry->is_directory; | |
| 956 std::unique_ptr<base::DictionaryValue> result = CreateResult(); | |
| 957 AddEntryToResult(file_entry->path, file_entry->id, result.get()); | |
| 958 SetResult(std::move(result)); | |
| 959 } | |
| 960 SendResponse(true); | |
| 961 return true; | |
| 962 } | |
| 963 | |
| 964 ExtensionFunction::ResponseAction FileSystemObserveDirectoryFunction::Run() { | |
| 965 NOTIMPLEMENTED(); | |
| 966 return RespondNow(Error(kUnknownIdError)); | |
| 967 } | |
| 968 | |
| 969 ExtensionFunction::ResponseAction FileSystemUnobserveEntryFunction::Run() { | |
| 970 NOTIMPLEMENTED(); | |
| 971 return RespondNow(Error(kUnknownIdError)); | |
| 972 } | |
| 973 | |
| 974 ExtensionFunction::ResponseAction FileSystemGetObservedEntriesFunction::Run() { | |
| 975 NOTIMPLEMENTED(); | |
| 976 return RespondNow(Error(kUnknownIdError)); | |
| 977 } | |
| 978 | |
| 979 #if !defined(OS_CHROMEOS) | |
| 980 ExtensionFunction::ResponseAction FileSystemRequestFileSystemFunction::Run() { | |
| 981 using api::file_system::RequestFileSystem::Params; | |
| 982 const std::unique_ptr<Params> params(Params::Create(*args_)); | |
| 983 EXTENSION_FUNCTION_VALIDATE(params); | |
| 984 | |
| 985 NOTIMPLEMENTED(); | |
| 986 return RespondNow(Error(kNotSupportedOnCurrentPlatformError)); | |
| 987 } | |
| 988 | |
| 989 ExtensionFunction::ResponseAction FileSystemGetVolumeListFunction::Run() { | |
| 990 NOTIMPLEMENTED(); | |
| 991 return RespondNow(Error(kNotSupportedOnCurrentPlatformError)); | |
| 992 } | |
| 993 #else | |
| 994 | |
| 995 FileSystemRequestFileSystemFunction::FileSystemRequestFileSystemFunction() | |
| 996 : chrome_details_(this) {} | |
| 997 | |
| 998 FileSystemRequestFileSystemFunction::~FileSystemRequestFileSystemFunction() {} | |
| 999 | |
| 1000 ExtensionFunction::ResponseAction FileSystemRequestFileSystemFunction::Run() { | |
| 1001 using api::file_system::RequestFileSystem::Params; | |
| 1002 const std::unique_ptr<Params> params(Params::Create(*args_)); | |
| 1003 EXTENSION_FUNCTION_VALIDATE(params); | |
| 1004 | |
| 1005 // Only kiosk apps in kiosk sessions can use this API. | 311 // Only kiosk apps in kiosk sessions can use this API. |
| 1006 // Additionally it is enabled for whitelisted component extensions and apps. | 312 // Additionally it is enabled for whitelisted component extensions and apps. |
| 1007 file_system_api::ConsentProviderDelegate consent_provider_delegate( | 313 ConsentProviderDelegate consent_provider_delegate( |
| 1008 chrome_details_.GetProfile(), render_frame_host()); | 314 Profile::FromBrowserContext(browser_context)); |
| 1009 file_system_api::ConsentProvider consent_provider(&consent_provider_delegate); | 315 return ConsentProvider(&consent_provider_delegate).IsGrantable(extension); |
| 1010 | 316 } |
| 1011 if (!consent_provider.IsGrantable(*extension())) | 317 |
| 1012 return RespondNow(Error(kNotSupportedOnNonKioskSessionError)); | 318 void ChromeFileSystemDelegate::RequestFileSystem( |
| 319 content::BrowserContext* browser_context, |
| 320 scoped_refptr<UIThreadExtensionFunction> requester, |
| 321 const Extension& extension, |
| 322 std::string volume_id, |
| 323 bool writable, |
| 324 const FileSystemCallback& success_callback, |
| 325 const ErrorCallback& error_callback) { |
| 326 ConsentProviderDelegate consent_provider_delegate( |
| 327 Profile::FromBrowserContext(browser_context)); |
| 328 ConsentProvider consent_provider(&consent_provider_delegate); |
| 329 |
| 330 if (!consent_provider.IsGrantable(extension)) { |
| 331 error_callback.Run(kNotSupportedOnNonKioskSessionError); |
| 332 return; |
| 333 } |
| 1013 | 334 |
| 1014 using file_manager::VolumeManager; | 335 using file_manager::VolumeManager; |
| 1015 using file_manager::Volume; | 336 using file_manager::Volume; |
| 1016 VolumeManager* const volume_manager = | 337 VolumeManager* const volume_manager = VolumeManager::Get(browser_context); |
| 1017 VolumeManager::Get(chrome_details_.GetProfile()); | |
| 1018 DCHECK(volume_manager); | 338 DCHECK(volume_manager); |
| 1019 | 339 |
| 1020 const bool writable = | |
| 1021 params->options.writable.get() && *params->options.writable.get(); | |
| 1022 if (writable && | 340 if (writable && |
| 1023 !app_file_handler_util::HasFileSystemWritePermission(extension_.get())) { | 341 !app_file_handler_util::HasFileSystemWritePermission(&extension)) { |
| 1024 return RespondNow(Error(kRequiresFileSystemWriteError)); | 342 error_callback.Run(kRequiresFileSystemWriteError); |
| 343 return; |
| 1025 } | 344 } |
| 1026 | 345 |
| 1027 base::WeakPtr<file_manager::Volume> volume = | 346 base::WeakPtr<file_manager::Volume> volume = |
| 1028 volume_manager->FindVolumeById(params->options.volume_id); | 347 volume_manager->FindVolumeById(volume_id); |
| 1029 if (!volume.get()) | 348 if (!volume.get()) { |
| 1030 return RespondNow(Error(kVolumeNotFoundError)); | 349 error_callback.Run(kVolumeNotFoundError); |
| 350 return; |
| 351 } |
| 1031 | 352 |
| 1032 const GURL site = | 353 const GURL site = |
| 1033 util::GetSiteForExtensionId(extension_id(), chrome_details_.GetProfile()); | 354 util::GetSiteForExtensionId(extension.id(), browser_context); |
| 1034 scoped_refptr<storage::FileSystemContext> file_system_context = | 355 scoped_refptr<storage::FileSystemContext> file_system_context = |
| 1035 content::BrowserContext::GetStoragePartitionForSite( | 356 content::BrowserContext::GetStoragePartitionForSite(browser_context, site) |
| 1036 chrome_details_.GetProfile(), site) | |
| 1037 ->GetFileSystemContext(); | 357 ->GetFileSystemContext(); |
| 1038 storage::ExternalFileSystemBackend* const backend = | 358 storage::ExternalFileSystemBackend* const backend = |
| 1039 file_system_context->external_backend(); | 359 file_system_context->external_backend(); |
| 1040 DCHECK(backend); | |
| 1041 | |
| 1042 base::FilePath virtual_path; | |
| 1043 if (!backend->GetVirtualPath(volume->mount_path(), &virtual_path)) | |
| 1044 return RespondNow(Error(kSecurityError)); | |
| 1045 | |
| 1046 if (writable && (volume->is_read_only())) | |
| 1047 return RespondNow(Error(kSecurityError)); | |
| 1048 | |
| 1049 consent_provider.RequestConsent( | |
| 1050 *extension(), volume, writable, | |
| 1051 base::Bind(&FileSystemRequestFileSystemFunction::OnConsentReceived, this, | |
| 1052 volume, writable)); | |
| 1053 return RespondLater(); | |
| 1054 } | |
| 1055 | |
| 1056 void FileSystemRequestFileSystemFunction::OnConsentReceived( | |
| 1057 const base::WeakPtr<file_manager::Volume>& volume, | |
| 1058 bool writable, | |
| 1059 ConsentProvider::Consent result) { | |
| 1060 using file_manager::VolumeManager; | |
| 1061 using file_manager::Volume; | |
| 1062 | |
| 1063 // Render frame host can be gone before this callback method is executed. | |
| 1064 if (!render_frame_host()) { | |
| 1065 Respond(Error("")); | |
| 1066 return; | |
| 1067 } | |
| 1068 | |
| 1069 switch (result) { | |
| 1070 case ConsentProvider::CONSENT_REJECTED: | |
| 1071 Respond(Error(kSecurityError)); | |
| 1072 return; | |
| 1073 | |
| 1074 case ConsentProvider::CONSENT_IMPOSSIBLE: | |
| 1075 Respond(Error(kConsentImpossible)); | |
| 1076 return; | |
| 1077 | |
| 1078 case ConsentProvider::CONSENT_GRANTED: | |
| 1079 break; | |
| 1080 } | |
| 1081 | |
| 1082 if (!volume.get()) { | |
| 1083 Respond(Error(kVolumeNotFoundError)); | |
| 1084 return; | |
| 1085 } | |
| 1086 | |
| 1087 const GURL site = | |
| 1088 util::GetSiteForExtensionId(extension_id(), chrome_details_.GetProfile()); | |
| 1089 scoped_refptr<storage::FileSystemContext> file_system_context = | |
| 1090 content::BrowserContext::GetStoragePartitionForSite( | |
| 1091 chrome_details_.GetProfile(), site) | |
| 1092 ->GetFileSystemContext(); | |
| 1093 storage::ExternalFileSystemBackend* const backend = | |
| 1094 file_system_context->external_backend(); | |
| 1095 DCHECK(backend); | 360 DCHECK(backend); |
| 1096 | 361 |
| 1097 base::FilePath virtual_path; | 362 base::FilePath virtual_path; |
| 1098 if (!backend->GetVirtualPath(volume->mount_path(), &virtual_path)) { | 363 if (!backend->GetVirtualPath(volume->mount_path(), &virtual_path)) { |
| 1099 Respond(Error(kSecurityError)); | 364 error_callback.Run(kSecurityError); |
| 1100 return; | 365 return; |
| 1101 } | 366 } |
| 1102 | 367 |
| 1103 storage::IsolatedContext* const isolated_context = | 368 if (writable && (volume->is_read_only())) { |
| 1104 storage::IsolatedContext::GetInstance(); | 369 error_callback.Run(kSecurityError); |
| 1105 DCHECK(isolated_context); | |
| 1106 | |
| 1107 const storage::FileSystemURL original_url = | |
| 1108 file_system_context->CreateCrackedFileSystemURL( | |
| 1109 GURL(std::string(kExtensionScheme) + url::kStandardSchemeSeparator + | |
| 1110 extension_id()), | |
| 1111 storage::kFileSystemTypeExternal, virtual_path); | |
| 1112 | |
| 1113 // Set a fixed register name, as the automatic one would leak the mount point | |
| 1114 // directory. | |
| 1115 std::string register_name = "fs"; | |
| 1116 const std::string file_system_id = | |
| 1117 isolated_context->RegisterFileSystemForPath( | |
| 1118 storage::kFileSystemTypeNativeForPlatformApp, | |
| 1119 std::string() /* file_system_id */, original_url.path(), | |
| 1120 ®ister_name); | |
| 1121 if (file_system_id.empty()) { | |
| 1122 Respond(Error(kSecurityError)); | |
| 1123 return; | 370 return; |
| 1124 } | 371 } |
| 1125 | 372 |
| 1126 backend->GrantFileAccessToExtension(extension_->id(), virtual_path); | 373 const ConsentProvider::ConsentCallback& callback = base::Bind( |
| 374 &OnConsentReceived, browser_context, requester, success_callback, |
| 375 error_callback, extension.id(), volume, writable); |
| 1127 | 376 |
| 1128 // Grant file permissions to the renderer hosting component. | 377 consent_provider.RequestConsent(extension, requester->render_frame_host(), |
| 1129 content::ChildProcessSecurityPolicy* policy = | 378 volume, writable, callback); |
| 1130 content::ChildProcessSecurityPolicy::GetInstance(); | |
| 1131 DCHECK(policy); | |
| 1132 | |
| 1133 // Read-only permisisons. | |
| 1134 policy->GrantReadFile(render_frame_host()->GetProcess()->GetID(), | |
| 1135 volume->mount_path()); | |
| 1136 policy->GrantReadFileSystem(render_frame_host()->GetProcess()->GetID(), | |
| 1137 file_system_id); | |
| 1138 | |
| 1139 // Additional write permissions. | |
| 1140 if (writable) { | |
| 1141 policy->GrantCreateReadWriteFile(render_frame_host()->GetProcess()->GetID(), | |
| 1142 volume->mount_path()); | |
| 1143 policy->GrantCopyInto(render_frame_host()->GetProcess()->GetID(), | |
| 1144 volume->mount_path()); | |
| 1145 policy->GrantWriteFileSystem(render_frame_host()->GetProcess()->GetID(), | |
| 1146 file_system_id); | |
| 1147 policy->GrantDeleteFromFileSystem( | |
| 1148 render_frame_host()->GetProcess()->GetID(), file_system_id); | |
| 1149 policy->GrantCreateFileForFileSystem( | |
| 1150 render_frame_host()->GetProcess()->GetID(), file_system_id); | |
| 1151 } | |
| 1152 | |
| 1153 std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue()); | |
| 1154 dict->SetString("file_system_id", file_system_id); | |
| 1155 dict->SetString("file_system_path", register_name); | |
| 1156 | |
| 1157 Respond(OneArgument(std::move(dict))); | |
| 1158 } | 379 } |
| 1159 | 380 |
| 1160 FileSystemGetVolumeListFunction::FileSystemGetVolumeListFunction() | 381 void ChromeFileSystemDelegate::GetVolumeList( |
| 1161 : chrome_details_(this) {} | 382 content::BrowserContext* browser_context, |
| 383 const VolumeListCallback& success_callback, |
| 384 const ErrorCallback& error_callback) { |
| 385 std::vector<file_system::Volume> result_volume_list; |
| 386 FillVolumeList(browser_context, &result_volume_list); |
| 1162 | 387 |
| 1163 FileSystemGetVolumeListFunction::~FileSystemGetVolumeListFunction() {} | 388 success_callback.Run(result_volume_list); |
| 389 } |
| 1164 | 390 |
| 1165 ExtensionFunction::ResponseAction FileSystemGetVolumeListFunction::Run() { | 391 #endif // defined(OS_CHROMEOS) |
| 1166 // Only kiosk apps in kiosk sessions can use this API. | |
| 1167 // Additionally it is enabled for whitelisted component extensions and apps. | |
| 1168 file_system_api::ConsentProviderDelegate consent_provider_delegate( | |
| 1169 chrome_details_.GetProfile(), render_frame_host()); | |
| 1170 file_system_api::ConsentProvider consent_provider(&consent_provider_delegate); | |
| 1171 | 392 |
| 1172 if (!consent_provider.IsGrantable(*extension())) | 393 SavedFilesServiceInterface* ChromeFileSystemDelegate::GetSavedFilesService( |
| 1173 return RespondNow(Error(kNotSupportedOnNonKioskSessionError)); | 394 content::BrowserContext* browser_context) { |
| 1174 std::vector<api::file_system::Volume> result_volume_list; | 395 return apps::SavedFilesService::Get(browser_context); |
| 1175 FillVolumeList(chrome_details_.GetProfile(), &result_volume_list); | |
| 1176 | |
| 1177 return RespondNow(ArgumentList( | |
| 1178 api::file_system::GetVolumeList::Results::Create(result_volume_list))); | |
| 1179 } | 396 } |
| 1180 #endif | |
| 1181 | 397 |
| 1182 } // namespace extensions | 398 } // namespace extensions |
| OLD | NEW |