OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "chrome/browser/extensions/api/file_system/file_system_api.h" | |
6 | |
7 #include <stddef.h> | |
8 | |
9 #include <memory> | |
10 #include <set> | |
11 #include <utility> | |
12 #include <vector> | |
13 | |
14 #include "apps/saved_files_service.h" | |
15 #include "base/bind.h" | |
16 #include "base/callback.h" | |
17 #include "base/files/file_path.h" | |
18 #include "base/files/file_util.h" | |
19 #include "base/macros.h" | |
20 #include "base/memory/ptr_util.h" | |
21 #include "base/path_service.h" | |
22 #include "base/stl_util.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" | |
32 #include "chrome/browser/platform_util.h" | |
33 #include "chrome/browser/profiles/profile.h" | |
34 #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" | |
37 #include "chrome/common/extensions/api/file_system.h" | |
38 #include "chrome/grit/generated_resources.h" | |
39 #include "content/public/browser/browser_thread.h" | |
40 #include "content/public/browser/child_process_security_policy.h" | |
41 #include "content/public/browser/render_frame_host.h" | |
42 #include "content/public/browser/render_process_host.h" | |
43 #include "content/public/browser/storage_partition.h" | |
44 #include "content/public/browser/web_contents.h" | |
45 #include "extensions/browser/api/file_handlers/app_file_handler_util.h" | |
46 #include "extensions/browser/api/file_system/saved_file_entry.h" | |
47 #include "extensions/browser/app_window/app_window.h" | |
48 #include "extensions/browser/app_window/app_window_registry.h" | |
49 #include "extensions/browser/extension_prefs.h" | |
50 #include "extensions/browser/extension_system.h" | |
51 #include "extensions/browser/extension_util.h" | |
52 #include "extensions/browser/granted_file_entry.h" | |
53 #include "extensions/browser/path_util.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" | |
58 #include "storage/browser/fileapi/file_system_operation_runner.h" | |
59 #include "storage/browser/fileapi/isolated_context.h" | |
60 #include "storage/common/fileapi/file_system_types.h" | |
61 #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" | |
65 #include "ui/shell_dialogs/select_file_policy.h" | |
66 | |
67 #if defined(OS_MACOSX) | |
68 #include <CoreFoundation/CoreFoundation.h> | |
69 #include "base/mac/foundation_util.h" | |
70 #endif | |
71 | |
72 #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" | |
76 #include "extensions/browser/event_router.h" | |
77 #include "extensions/browser/extension_registry.h" | |
78 #include "extensions/common/constants.h" | |
79 #include "url/url_constants.h" | |
80 #endif | |
81 | |
82 using apps::SavedFilesService; | |
83 using storage::IsolatedContext; | |
84 | |
85 const char kInvalidCallingPage[] = | |
86 "Invalid calling page. " | |
87 "This function can't be called from a background page."; | |
88 const char kUserCancelled[] = "User cancelled"; | |
89 const char kWritableFileErrorFormat[] = "Error opening %s"; | |
90 const char kRequiresFileSystemWriteError[] = | |
91 "Operation requires fileSystem.write permission"; | |
92 const char kRequiresFileSystemDirectoryError[] = | |
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."; | |
105 const char kSecurityError[] = "Security error."; | |
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. | |
228 void FillVolumeList(Profile* profile, | |
229 std::vector<api::file_system::Volume>* result) { | |
230 file_manager::VolumeManager* const volume_manager = | |
231 file_manager::VolumeManager::Get(profile); | |
232 DCHECK(volume_manager); | |
233 | |
234 const auto& volume_list = volume_manager->GetVolumeList(); | |
235 // Convert volume_list to result_volume_list. | |
236 for (const auto& volume : volume_list) { | |
237 api::file_system::Volume result_volume; | |
238 result_volume.volume_id = volume->volume_id(); | |
239 result_volume.writable = !volume->is_read_only(); | |
240 result->push_back(std::move(result_volume)); | |
241 } | |
242 } | |
243 #endif | |
244 | |
245 // Creates and shows a SelectFileDialog, or returns false if the dialog could | |
246 // not be created. | |
247 bool ShowSelectFileDialog( | |
248 scoped_refptr<UIThreadExtensionFunction> extension_function, | |
249 ui::SelectFileDialog::Type type, | |
250 const base::FilePath& default_path, | |
251 const ui::SelectFileDialog::FileTypeInfo* file_types, | |
252 FileEntryPicker::FilesSelectedCallback files_selected_callback, | |
253 base::OnceClosure file_selection_canceled_callback) { | |
254 // TODO(asargent/benwells) - As a short term remediation for | |
255 // crbug.com/179010 we're adding the ability for a whitelisted extension to | |
256 // 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 | |
258 // to being platform-app only. | |
259 content::WebContents* const web_contents = | |
260 extension_function->extension()->is_platform_app() | |
261 ? GetWebContentsForRenderFrameHost( | |
262 extension_function->browser_context(), | |
263 extension_function->render_frame_host()) | |
264 : extension_function->GetAssociatedWebContents(); | |
265 if (!web_contents) | |
266 return false; | |
267 | |
268 // The file picker will hold a reference to the UIThreadExtensionFunction | |
269 // instance, preventing its destruction (and subsequent sending of the | |
270 // 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 | |
272 // the function instance. | |
273 new FileEntryPicker(web_contents, default_path, *file_types, type, | |
274 std::move(files_selected_callback), | |
275 std::move(file_selection_canceled_callback)); | |
276 return true; | |
277 } | |
278 | |
279 } // namespace | |
280 | |
281 namespace file_system_api { | |
282 | |
283 base::FilePath GetLastChooseEntryDirectory(const ExtensionPrefs* prefs, | |
284 const std::string& extension_id) { | |
285 base::FilePath path; | |
286 std::string string_path; | |
287 if (prefs->ReadPrefAsString(extension_id, kLastChooseEntryDirectory, | |
288 &string_path)) { | |
289 path = base::FilePath::FromUTF8Unsafe(string_path); | |
290 } | |
291 return path; | |
292 } | |
293 | |
294 void SetLastChooseEntryDirectory(ExtensionPrefs* prefs, | |
295 const std::string& extension_id, | |
296 const base::FilePath& path) { | |
297 prefs->UpdateExtensionPref(extension_id, kLastChooseEntryDirectory, | |
298 base::CreateFilePathValue(path)); | |
299 } | |
300 | |
301 #if defined(OS_CHROMEOS) | |
302 void DispatchVolumeListChangeEvent(Profile* profile) { | |
303 DCHECK(profile); | |
304 EventRouter* const event_router = EventRouter::Get(profile); | |
305 if (!event_router) // Possible on shutdown. | |
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. | |
1006 // Additionally it is enabled for whitelisted component extensions and apps. | |
1007 file_system_api::ConsentProviderDelegate consent_provider_delegate( | |
1008 chrome_details_.GetProfile(), render_frame_host()); | |
1009 file_system_api::ConsentProvider consent_provider(&consent_provider_delegate); | |
1010 | |
1011 if (!consent_provider.IsGrantable(*extension())) | |
1012 return RespondNow(Error(kNotSupportedOnNonKioskSessionError)); | |
1013 | |
1014 using file_manager::VolumeManager; | |
1015 using file_manager::Volume; | |
1016 VolumeManager* const volume_manager = | |
1017 VolumeManager::Get(chrome_details_.GetProfile()); | |
1018 DCHECK(volume_manager); | |
1019 | |
1020 const bool writable = | |
1021 params->options.writable.get() && *params->options.writable.get(); | |
1022 if (writable && | |
1023 !app_file_handler_util::HasFileSystemWritePermission(extension_.get())) { | |
1024 return RespondNow(Error(kRequiresFileSystemWriteError)); | |
1025 } | |
1026 | |
1027 base::WeakPtr<file_manager::Volume> volume = | |
1028 volume_manager->FindVolumeById(params->options.volume_id); | |
1029 if (!volume.get()) | |
1030 return RespondNow(Error(kVolumeNotFoundError)); | |
1031 | |
1032 const GURL site = | |
1033 util::GetSiteForExtensionId(extension_id(), chrome_details_.GetProfile()); | |
1034 scoped_refptr<storage::FileSystemContext> file_system_context = | |
1035 content::BrowserContext::GetStoragePartitionForSite( | |
1036 chrome_details_.GetProfile(), site) | |
1037 ->GetFileSystemContext(); | |
1038 storage::ExternalFileSystemBackend* const backend = | |
1039 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); | |
1096 | |
1097 base::FilePath virtual_path; | |
1098 if (!backend->GetVirtualPath(volume->mount_path(), &virtual_path)) { | |
1099 Respond(Error(kSecurityError)); | |
1100 return; | |
1101 } | |
1102 | |
1103 storage::IsolatedContext* const isolated_context = | |
1104 storage::IsolatedContext::GetInstance(); | |
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; | |
1124 } | |
1125 | |
1126 backend->GrantFileAccessToExtension(extension_->id(), virtual_path); | |
1127 | |
1128 // Grant file permissions to the renderer hosting component. | |
1129 content::ChildProcessSecurityPolicy* policy = | |
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 } | |
1159 | |
1160 FileSystemGetVolumeListFunction::FileSystemGetVolumeListFunction() | |
1161 : chrome_details_(this) {} | |
1162 | |
1163 FileSystemGetVolumeListFunction::~FileSystemGetVolumeListFunction() {} | |
1164 | |
1165 ExtensionFunction::ResponseAction FileSystemGetVolumeListFunction::Run() { | |
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 | |
1172 if (!consent_provider.IsGrantable(*extension())) | |
1173 return RespondNow(Error(kNotSupportedOnNonKioskSessionError)); | |
1174 std::vector<api::file_system::Volume> result_volume_list; | |
1175 FillVolumeList(chrome_details_.GetProfile(), &result_volume_list); | |
1176 | |
1177 return RespondNow(ArgumentList( | |
1178 api::file_system::GetVolumeList::Results::Create(result_volume_list))); | |
1179 } | |
1180 #endif | |
1181 | |
1182 } // namespace extensions | |
OLD | NEW |