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