Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / extensions / api / file_system / file_system_api.cc
blob58e9bfb5ba6ed19eef5e8b4194ea9deecca840f6
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.
5 #include "chrome/browser/extensions/api/file_system/file_system_api.h"
7 #include <set>
8 #include <vector>
10 #include "apps/saved_files_service.h"
11 #include "base/bind.h"
12 #include "base/files/file_path.h"
13 #include "base/files/file_util.h"
14 #include "base/logging.h"
15 #include "base/memory/linked_ptr.h"
16 #include "base/path_service.h"
17 #include "base/strings/string_util.h"
18 #include "base/strings/stringprintf.h"
19 #include "base/strings/sys_string_conversions.h"
20 #include "base/strings/utf_string_conversions.h"
21 #include "base/value_conversions.h"
22 #include "base/values.h"
23 #include "chrome/browser/extensions/api/file_handlers/app_file_handler_util.h"
24 #include "chrome/browser/extensions/extension_service.h"
25 #include "chrome/browser/extensions/extension_util.h"
26 #include "chrome/browser/extensions/path_util.h"
27 #include "chrome/browser/platform_util.h"
28 #include "chrome/browser/profiles/profile.h"
29 #include "chrome/browser/ui/apps/directory_access_confirmation_dialog.h"
30 #include "chrome/browser/ui/chrome_select_file_policy.h"
31 #include "chrome/common/chrome_paths.h"
32 #include "chrome/common/extensions/api/file_system.h"
33 #include "chrome/grit/generated_resources.h"
34 #include "content/public/browser/browser_thread.h"
35 #include "content/public/browser/child_process_security_policy.h"
36 #include "content/public/browser/render_frame_host.h"
37 #include "content/public/browser/render_process_host.h"
38 #include "content/public/browser/storage_partition.h"
39 #include "content/public/browser/web_contents.h"
40 #include "extensions/browser/app_window/app_window.h"
41 #include "extensions/browser/app_window/app_window_registry.h"
42 #include "extensions/browser/extension_prefs.h"
43 #include "extensions/browser/extension_system.h"
44 #include "extensions/browser/granted_file_entry.h"
45 #include "extensions/common/permissions/api_permission.h"
46 #include "extensions/common/permissions/permissions_data.h"
47 #include "net/base/mime_util.h"
48 #include "storage/browser/fileapi/external_mount_points.h"
49 #include "storage/browser/fileapi/file_system_operation_runner.h"
50 #include "storage/browser/fileapi/isolated_context.h"
51 #include "storage/common/fileapi/file_system_types.h"
52 #include "storage/common/fileapi/file_system_util.h"
53 #include "ui/base/l10n/l10n_util.h"
54 #include "ui/shell_dialogs/select_file_dialog.h"
55 #include "ui/shell_dialogs/selected_file_info.h"
57 #if defined(OS_MACOSX)
58 #include <CoreFoundation/CoreFoundation.h>
59 #include "base/mac/foundation_util.h"
60 #endif
62 #if defined(OS_CHROMEOS)
63 #include "base/prefs/testing_pref_service.h"
64 #include "base/strings/string16.h"
65 #include "base/thread_task_runner_handle.h"
66 #include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h"
67 #include "chrome/browser/chromeos/file_manager/app_id.h"
68 #include "chrome/browser/chromeos/file_manager/filesystem_api_util.h"
69 #include "chrome/browser/extensions/api/file_system/request_file_system_dialog_view.h"
70 #include "chrome/browser/extensions/api/file_system/request_file_system_notification.h"
71 #include "chrome/browser/ui/simple_message_box.h"
72 #include "components/user_manager/user_manager.h"
73 #include "extensions/browser/event_router.h"
74 #include "extensions/browser/extension_registry.h"
75 #include "extensions/common/constants.h"
76 #include "extensions/common/manifest_handlers/kiosk_mode_info.h"
77 #include "url/url_constants.h"
78 #endif
80 using apps::SavedFileEntry;
81 using apps::SavedFilesService;
82 using storage::IsolatedContext;
84 const char kInvalidCallingPage[] = "Invalid calling page. This function can't "
85 "be called from a background page.";
86 const char kUserCancelled[] = "User cancelled";
87 const char kWritableFileErrorFormat[] = "Error opening %s";
88 const char kRequiresFileSystemWriteError[] =
89 "Operation requires fileSystem.write permission";
90 const char kRequiresFileSystemDirectoryError[] =
91 "Operation requires fileSystem.directory permission";
92 const char kMultipleUnsupportedError[] =
93 "acceptsMultiple: true is not supported for 'saveFile'";
94 const char kUnknownIdError[] = "Unknown id";
96 #if !defined(OS_CHROMEOS)
97 const char kNotSupportedOnCurrentPlatformError[] =
98 "Operation not supported on the current platform.";
99 #else
100 const char kNotSupportedOnNonKioskSessionError[] =
101 "Operation only supported for kiosk apps running in a kiosk session.";
102 const char kVolumeNotFoundError[] = "Volume not found.";
103 const char kSecurityError[] = "Security error.";
104 const char kConsentImpossible[] =
105 "Impossible to ask for user consent as there is no app window visible.";
107 // List of whitelisted component apps and extensions by their ids for
108 // chrome.fileSystem.requestFileSystem.
109 const char* const kRequestFileSystemComponentWhitelist[] = {
110 file_manager::kFileManagerAppId,
111 file_manager::kVideoPlayerAppId,
112 file_manager::kGalleryAppId,
113 file_manager::kAudioPlayerAppId,
114 file_manager::kImageLoaderExtensionId,
115 // TODO(mtomasz): Remove this extension id, and add it only for tests.
116 "pkplfbidichfdicaijlchgnapepdginl" // Testing extensions.
118 #endif
120 namespace extensions {
122 namespace file_system = api::file_system;
123 namespace ChooseEntry = file_system::ChooseEntry;
125 namespace {
127 bool g_skip_picker_for_test = false;
128 bool g_use_suggested_path_for_test = false;
129 base::FilePath* g_path_to_be_picked_for_test;
130 std::vector<base::FilePath>* g_paths_to_be_picked_for_test;
131 bool g_skip_directory_confirmation_for_test = false;
132 bool g_allow_directory_access_for_test = false;
134 #if defined(OS_CHROMEOS)
135 ui::DialogButton g_auto_dialog_button_for_test = ui::DIALOG_BUTTON_NONE;
136 #endif
138 // Expand the mime-types and extensions provided in an AcceptOption, returning
139 // them within the passed extension vector. Returns false if no valid types
140 // were found.
141 bool GetFileTypesFromAcceptOption(
142 const file_system::AcceptOption& accept_option,
143 std::vector<base::FilePath::StringType>* extensions,
144 base::string16* description) {
145 std::set<base::FilePath::StringType> extension_set;
146 int description_id = 0;
148 if (accept_option.mime_types.get()) {
149 std::vector<std::string>* list = accept_option.mime_types.get();
150 bool valid_type = false;
151 for (std::vector<std::string>::const_iterator iter = list->begin();
152 iter != list->end(); ++iter) {
153 std::vector<base::FilePath::StringType> inner;
154 std::string accept_type = base::ToLowerASCII(*iter);
155 net::GetExtensionsForMimeType(accept_type, &inner);
156 if (inner.empty())
157 continue;
159 if (valid_type)
160 description_id = 0; // We already have an accept type with label; if
161 // we find another, give up and use the default.
162 else if (accept_type == "image/*")
163 description_id = IDS_IMAGE_FILES;
164 else if (accept_type == "audio/*")
165 description_id = IDS_AUDIO_FILES;
166 else if (accept_type == "video/*")
167 description_id = IDS_VIDEO_FILES;
169 extension_set.insert(inner.begin(), inner.end());
170 valid_type = true;
174 if (accept_option.extensions.get()) {
175 std::vector<std::string>* list = accept_option.extensions.get();
176 for (std::vector<std::string>::const_iterator iter = list->begin();
177 iter != list->end(); ++iter) {
178 std::string extension = base::ToLowerASCII(*iter);
179 #if defined(OS_WIN)
180 extension_set.insert(base::UTF8ToWide(*iter));
181 #else
182 extension_set.insert(*iter);
183 #endif
187 extensions->assign(extension_set.begin(), extension_set.end());
188 if (extensions->empty())
189 return false;
191 if (accept_option.description.get())
192 *description = base::UTF8ToUTF16(*accept_option.description.get());
193 else if (description_id)
194 *description = l10n_util::GetStringUTF16(description_id);
196 return true;
199 // Key for the path of the directory of the file last chosen by the user in
200 // response to a chrome.fileSystem.chooseEntry() call.
201 const char kLastChooseEntryDirectory[] = "last_choose_file_directory";
203 const int kGraylistedPaths[] = {
204 base::DIR_HOME,
205 #if defined(OS_WIN)
206 base::DIR_PROGRAM_FILES,
207 base::DIR_PROGRAM_FILESX86,
208 base::DIR_WINDOWS,
209 #endif
212 typedef base::Callback<void(scoped_ptr<base::File::Info>)> FileInfoOptCallback;
214 // Passes optional file info to the UI thread depending on |result| and |info|.
215 void PassFileInfoToUIThread(const FileInfoOptCallback& callback,
216 base::File::Error result,
217 const base::File::Info& info) {
218 DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
219 scoped_ptr<base::File::Info> file_info(
220 result == base::File::FILE_OK ? new base::File::Info(info) : NULL);
221 content::BrowserThread::PostTask(
222 content::BrowserThread::UI,
223 FROM_HERE,
224 base::Bind(callback, base::Passed(&file_info)));
227 // Gets a WebContents instance handle for a platform app hosted in
228 // |render_frame_host|. If not found, then returns NULL.
229 content::WebContents* GetWebContentsForRenderFrameHost(
230 Profile* profile,
231 content::RenderFrameHost* render_frame_host) {
232 content::WebContents* web_contents =
233 content::WebContents::FromRenderFrameHost(render_frame_host);
234 // Check if there is an app window associated with the web contents; if not,
235 // return null.
236 return AppWindowRegistry::Get(profile)
237 ->GetAppWindowForWebContents(web_contents)
238 ? web_contents
239 : nullptr;
242 #if defined(OS_CHROMEOS)
243 // Gets a WebContents instance handle for a current window of a platform app
244 // with |app_id|. If not found, then returns NULL.
245 content::WebContents* GetWebContentsForAppId(Profile* profile,
246 const std::string& app_id) {
247 AppWindowRegistry* const registry = AppWindowRegistry::Get(profile);
248 DCHECK(registry);
249 AppWindow* const app_window = registry->GetCurrentAppWindowForApp(app_id);
250 return app_window ? app_window->web_contents() : nullptr;
253 // Fills a list of volumes mounted in the system.
254 void FillVolumeList(Profile* profile,
255 std::vector<linked_ptr<api::file_system::Volume>>* result) {
256 using file_manager::VolumeManager;
257 VolumeManager* const volume_manager = VolumeManager::Get(profile);
258 DCHECK(volume_manager);
260 using api::file_system::Volume;
261 const auto& volume_list = volume_manager->GetVolumeList();
262 // Convert volume_list to result_volume_list.
263 for (const auto& volume : volume_list) {
264 const linked_ptr<Volume> result_volume(new Volume);
265 result_volume->volume_id = volume->volume_id();
266 result_volume->writable = !volume->is_read_only();
267 result->push_back(result_volume);
270 #endif
272 } // namespace
274 namespace file_system_api {
276 base::FilePath GetLastChooseEntryDirectory(const ExtensionPrefs* prefs,
277 const std::string& extension_id) {
278 base::FilePath path;
279 std::string string_path;
280 if (prefs->ReadPrefAsString(extension_id,
281 kLastChooseEntryDirectory,
282 &string_path)) {
283 path = base::FilePath::FromUTF8Unsafe(string_path);
285 return path;
288 void SetLastChooseEntryDirectory(ExtensionPrefs* prefs,
289 const std::string& extension_id,
290 const base::FilePath& path) {
291 prefs->UpdateExtensionPref(extension_id,
292 kLastChooseEntryDirectory,
293 base::CreateFilePathValue(path));
296 std::vector<base::FilePath> GetGrayListedDirectories() {
297 std::vector<base::FilePath> graylisted_directories;
298 for (size_t i = 0; i < arraysize(kGraylistedPaths); ++i) {
299 base::FilePath graylisted_path;
300 if (PathService::Get(kGraylistedPaths[i], &graylisted_path))
301 graylisted_directories.push_back(graylisted_path);
303 return graylisted_directories;
306 #if defined(OS_CHROMEOS)
307 void DispatchVolumeListChangeEvent(Profile* profile) {
308 DCHECK(profile);
309 EventRouter* const event_router = EventRouter::Get(profile);
310 if (!event_router) // Possible on shutdown.
311 return;
313 ExtensionRegistry* const registry = ExtensionRegistry::Get(profile);
314 if (!registry) // Possible on shutdown.
315 return;
317 ConsentProviderDelegate consent_provider_delegate(profile, nullptr);
318 ConsentProvider consent_provider(&consent_provider_delegate);
319 api::file_system::VolumeListChangedEvent event_args;
320 FillVolumeList(profile, &event_args.volumes);
321 for (const auto& extension : registry->enabled_extensions()) {
322 if (!consent_provider.IsGrantable(*extension.get()))
323 continue;
324 event_router->DispatchEventToExtension(
325 extension->id(),
326 make_scoped_ptr(new Event(
327 events::FILE_SYSTEM_ON_VOLUME_LIST_CHANGED,
328 api::file_system::OnVolumeListChanged::kEventName,
329 api::file_system::OnVolumeListChanged::Create(event_args))));
333 ConsentProvider::ConsentProvider(DelegateInterface* delegate)
334 : delegate_(delegate) {
335 DCHECK(delegate_);
338 ConsentProvider::~ConsentProvider() {
341 void ConsentProvider::RequestConsent(
342 const Extension& extension,
343 const base::WeakPtr<file_manager::Volume>& volume,
344 bool writable,
345 const ConsentCallback& callback) {
346 DCHECK(IsGrantable(extension));
348 // If a whitelisted component, then no need to ask or inform the user.
349 if (extension.location() == Manifest::COMPONENT &&
350 delegate_->IsWhitelistedComponent(extension)) {
351 base::ThreadTaskRunnerHandle::Get()->PostTask(
352 FROM_HERE, base::Bind(callback, CONSENT_GRANTED));
353 return;
356 // If auto-launched kiosk app, then no need to ask user either, but show the
357 // notification.
358 if (delegate_->IsAutoLaunched(extension)) {
359 delegate_->ShowNotification(extension, volume, writable);
360 base::ThreadTaskRunnerHandle::Get()->PostTask(
361 FROM_HERE, base::Bind(callback, CONSENT_GRANTED));
362 return;
365 // If it's a kiosk app running in manual-launch kiosk session, then show
366 // the confirmation dialog.
367 if (KioskModeInfo::IsKioskOnly(&extension) &&
368 user_manager::UserManager::Get()->IsLoggedInAsKioskApp()) {
369 delegate_->ShowDialog(extension, volume, writable,
370 base::Bind(&ConsentProvider::DialogResultToConsent,
371 base::Unretained(this), callback));
372 return;
375 NOTREACHED() << "Cannot request consent for non-grantable extensions.";
378 bool ConsentProvider::IsGrantable(const Extension& extension) {
379 const bool is_whitelisted_component =
380 delegate_->IsWhitelistedComponent(extension);
382 const bool is_running_in_kiosk_session =
383 KioskModeInfo::IsKioskOnly(&extension) &&
384 user_manager::UserManager::Get()->IsLoggedInAsKioskApp();
386 return is_whitelisted_component || is_running_in_kiosk_session;
389 void ConsentProvider::DialogResultToConsent(const ConsentCallback& callback,
390 ui::DialogButton button) {
391 switch (button) {
392 case ui::DIALOG_BUTTON_NONE:
393 callback.Run(CONSENT_IMPOSSIBLE);
394 break;
395 case ui::DIALOG_BUTTON_OK:
396 callback.Run(CONSENT_GRANTED);
397 break;
398 case ui::DIALOG_BUTTON_CANCEL:
399 callback.Run(CONSENT_REJECTED);
400 break;
404 ConsentProviderDelegate::ConsentProviderDelegate(Profile* profile,
405 content::RenderFrameHost* host)
406 : profile_(profile), host_(host) {
407 DCHECK(profile_);
410 ConsentProviderDelegate::~ConsentProviderDelegate() {
413 // static
414 void ConsentProviderDelegate::SetAutoDialogButtonForTest(
415 ui::DialogButton button) {
416 g_auto_dialog_button_for_test = button;
419 void ConsentProviderDelegate::ShowDialog(
420 const Extension& extension,
421 const base::WeakPtr<file_manager::Volume>& volume,
422 bool writable,
423 const file_system_api::ConsentProvider::ShowDialogCallback& callback) {
424 DCHECK(host_);
425 content::WebContents* const foreground_contents =
426 GetWebContentsForRenderFrameHost(profile_, host_);
427 // If there is no web contents handle, then the method is most probably
428 // executed from a background page. Find an app window to host the dialog.
429 content::WebContents* const web_contents =
430 foreground_contents ? foreground_contents
431 : GetWebContentsForAppId(profile_, extension.id());
432 if (!web_contents) {
433 base::ThreadTaskRunnerHandle::Get()->PostTask(
434 FROM_HERE, base::Bind(callback, ui::DIALOG_BUTTON_NONE));
435 return;
438 // Short circuit the user consent dialog for tests. This is far from a pretty
439 // code design.
440 if (g_auto_dialog_button_for_test != ui::DIALOG_BUTTON_NONE) {
441 base::ThreadTaskRunnerHandle::Get()->PostTask(
442 FROM_HERE,
443 base::Bind(callback, g_auto_dialog_button_for_test /* result */));
444 return;
447 RequestFileSystemDialogView::ShowDialog(web_contents, extension, volume,
448 writable, base::Bind(callback));
451 void ConsentProviderDelegate::ShowNotification(
452 const Extension& extension,
453 const base::WeakPtr<file_manager::Volume>& volume,
454 bool writable) {
455 RequestFileSystemNotification::ShowAutoGrantedNotification(
456 profile_, extension, volume, writable);
459 bool ConsentProviderDelegate::IsAutoLaunched(const Extension& extension) {
460 chromeos::KioskAppManager::App app_info;
461 return chromeos::KioskAppManager::Get()->GetApp(extension.id(), &app_info) &&
462 app_info.was_auto_launched_with_zero_delay;
465 bool ConsentProviderDelegate::IsWhitelistedComponent(
466 const Extension& extension) {
467 for (const auto& whitelisted_id : kRequestFileSystemComponentWhitelist) {
468 if (extension.id().compare(whitelisted_id) == 0)
469 return true;
471 return false;
474 #endif
476 } // namespace file_system_api
478 #if defined(OS_CHROMEOS)
479 using file_system_api::ConsentProvider;
480 #endif
482 bool FileSystemGetDisplayPathFunction::RunSync() {
483 std::string filesystem_name;
484 std::string filesystem_path;
485 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &filesystem_name));
486 EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &filesystem_path));
488 base::FilePath file_path;
489 if (!app_file_handler_util::ValidateFileEntryAndGetPath(
490 filesystem_name, filesystem_path,
491 render_frame_host()->GetProcess()->GetID(), &file_path, &error_))
492 return false;
494 file_path = path_util::PrettifyPath(file_path);
495 SetResult(new base::StringValue(file_path.value()));
496 return true;
499 FileSystemEntryFunction::FileSystemEntryFunction()
500 : multiple_(false),
501 is_directory_(false),
502 response_(NULL) {}
504 void FileSystemEntryFunction::PrepareFilesForWritableApp(
505 const std::vector<base::FilePath>& paths) {
506 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
507 app_file_handler_util::PrepareFilesForWritableApp(
508 paths,
509 GetProfile(),
510 is_directory_,
511 base::Bind(&FileSystemEntryFunction::RegisterFileSystemsAndSendResponse,
512 this,
513 paths),
514 base::Bind(&FileSystemEntryFunction::HandleWritableFileError, this));
517 void FileSystemEntryFunction::RegisterFileSystemsAndSendResponse(
518 const std::vector<base::FilePath>& paths) {
519 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
520 if (!render_frame_host())
521 return;
523 CreateResponse();
524 for (std::vector<base::FilePath>::const_iterator it = paths.begin();
525 it != paths.end(); ++it) {
526 AddEntryToResponse(*it, "");
528 SendResponse(true);
531 void FileSystemEntryFunction::CreateResponse() {
532 DCHECK(!response_);
533 response_ = new base::DictionaryValue();
534 base::ListValue* list = new base::ListValue();
535 response_->Set("entries", list);
536 response_->SetBoolean("multiple", multiple_);
537 SetResult(response_);
540 void FileSystemEntryFunction::AddEntryToResponse(
541 const base::FilePath& path,
542 const std::string& id_override) {
543 DCHECK(response_);
544 GrantedFileEntry file_entry = app_file_handler_util::CreateFileEntry(
545 GetProfile(),
546 extension(),
547 render_frame_host()->GetProcess()->GetID(),
548 path,
549 is_directory_);
550 base::ListValue* entries;
551 bool success = response_->GetList("entries", &entries);
552 DCHECK(success);
554 base::DictionaryValue* entry = new base::DictionaryValue();
555 entry->SetString("fileSystemId", file_entry.filesystem_id);
556 entry->SetString("baseName", file_entry.registered_name);
557 if (id_override.empty())
558 entry->SetString("id", file_entry.id);
559 else
560 entry->SetString("id", id_override);
561 entry->SetBoolean("isDirectory", is_directory_);
562 entries->Append(entry);
565 void FileSystemEntryFunction::HandleWritableFileError(
566 const base::FilePath& error_path) {
567 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
568 error_ = base::StringPrintf(kWritableFileErrorFormat,
569 error_path.BaseName().AsUTF8Unsafe().c_str());
570 SendResponse(false);
573 bool FileSystemGetWritableEntryFunction::RunAsync() {
574 std::string filesystem_name;
575 std::string filesystem_path;
576 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &filesystem_name));
577 EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &filesystem_path));
579 if (!app_file_handler_util::HasFileSystemWritePermission(extension_.get())) {
580 error_ = kRequiresFileSystemWriteError;
581 return false;
584 if (!app_file_handler_util::ValidateFileEntryAndGetPath(
585 filesystem_name, filesystem_path,
586 render_frame_host()->GetProcess()->GetID(), &path_, &error_))
587 return false;
589 content::BrowserThread::PostTaskAndReply(
590 content::BrowserThread::FILE,
591 FROM_HERE,
592 base::Bind(
593 &FileSystemGetWritableEntryFunction::SetIsDirectoryOnFileThread,
594 this),
595 base::Bind(
596 &FileSystemGetWritableEntryFunction::CheckPermissionAndSendResponse,
597 this));
598 return true;
601 void FileSystemGetWritableEntryFunction::CheckPermissionAndSendResponse() {
602 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
603 if (is_directory_ &&
604 !extension_->permissions_data()->HasAPIPermission(
605 APIPermission::kFileSystemDirectory)) {
606 error_ = kRequiresFileSystemDirectoryError;
607 SendResponse(false);
609 std::vector<base::FilePath> paths;
610 paths.push_back(path_);
611 PrepareFilesForWritableApp(paths);
614 void FileSystemGetWritableEntryFunction::SetIsDirectoryOnFileThread() {
615 DCHECK_CURRENTLY_ON(content::BrowserThread::FILE);
616 if (base::DirectoryExists(path_)) {
617 is_directory_ = true;
621 bool FileSystemIsWritableEntryFunction::RunSync() {
622 std::string filesystem_name;
623 std::string filesystem_path;
624 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &filesystem_name));
625 EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &filesystem_path));
627 std::string filesystem_id;
628 if (!storage::CrackIsolatedFileSystemName(filesystem_name, &filesystem_id)) {
629 error_ = app_file_handler_util::kInvalidParameters;
630 return false;
633 content::ChildProcessSecurityPolicy* policy =
634 content::ChildProcessSecurityPolicy::GetInstance();
635 int renderer_id = render_frame_host()->GetProcess()->GetID();
636 bool is_writable = policy->CanReadWriteFileSystem(renderer_id,
637 filesystem_id);
639 SetResult(new base::FundamentalValue(is_writable));
640 return true;
643 // Handles showing a dialog to the user to ask for the filename for a file to
644 // save or open.
645 class FileSystemChooseEntryFunction::FilePicker
646 : public ui::SelectFileDialog::Listener {
647 public:
648 FilePicker(FileSystemChooseEntryFunction* function,
649 content::WebContents* web_contents,
650 const base::FilePath& suggested_name,
651 const ui::SelectFileDialog::FileTypeInfo& file_type_info,
652 ui::SelectFileDialog::Type picker_type)
653 : function_(function) {
654 select_file_dialog_ = ui::SelectFileDialog::Create(
655 this, new ChromeSelectFilePolicy(web_contents));
656 gfx::NativeWindow owning_window = web_contents ?
657 platform_util::GetTopLevel(web_contents->GetNativeView()) :
658 NULL;
660 if (g_skip_picker_for_test) {
661 if (g_use_suggested_path_for_test) {
662 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
663 base::Bind(
664 &FileSystemChooseEntryFunction::FilePicker::FileSelected,
665 base::Unretained(this), suggested_name, 1,
666 static_cast<void*>(NULL)));
667 } else if (g_path_to_be_picked_for_test) {
668 content::BrowserThread::PostTask(
669 content::BrowserThread::UI, FROM_HERE,
670 base::Bind(
671 &FileSystemChooseEntryFunction::FilePicker::FileSelected,
672 base::Unretained(this), *g_path_to_be_picked_for_test, 1,
673 static_cast<void*>(NULL)));
674 } else if (g_paths_to_be_picked_for_test) {
675 content::BrowserThread::PostTask(
676 content::BrowserThread::UI,
677 FROM_HERE,
678 base::Bind(
679 &FileSystemChooseEntryFunction::FilePicker::MultiFilesSelected,
680 base::Unretained(this),
681 *g_paths_to_be_picked_for_test,
682 static_cast<void*>(NULL)));
683 } else {
684 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
685 base::Bind(
686 &FileSystemChooseEntryFunction::FilePicker::
687 FileSelectionCanceled,
688 base::Unretained(this), static_cast<void*>(NULL)));
690 return;
693 select_file_dialog_->SelectFile(picker_type,
694 base::string16(),
695 suggested_name,
696 &file_type_info,
698 base::FilePath::StringType(),
699 owning_window,
700 NULL);
703 ~FilePicker() override {}
705 private:
706 // ui::SelectFileDialog::Listener implementation.
707 void FileSelected(const base::FilePath& path,
708 int index,
709 void* params) override {
710 std::vector<base::FilePath> paths;
711 paths.push_back(path);
712 MultiFilesSelected(paths, params);
715 void FileSelectedWithExtraInfo(const ui::SelectedFileInfo& file,
716 int index,
717 void* params) override {
718 // Normally, file.local_path is used because it is a native path to the
719 // local read-only cached file in the case of remote file system like
720 // Chrome OS's Google Drive integration. Here, however, |file.file_path| is
721 // necessary because we need to create a FileEntry denoting the remote file,
722 // not its cache. On other platforms than Chrome OS, they are the same.
724 // TODO(kinaba): remove this, once after the file picker implements proper
725 // switch of the path treatment depending on the |support_drive| flag.
726 FileSelected(file.file_path, index, params);
729 void MultiFilesSelected(const std::vector<base::FilePath>& files,
730 void* params) override {
731 function_->FilesSelected(files);
732 delete this;
735 void MultiFilesSelectedWithExtraInfo(
736 const std::vector<ui::SelectedFileInfo>& files,
737 void* params) override {
738 std::vector<base::FilePath> paths;
739 for (std::vector<ui::SelectedFileInfo>::const_iterator it = files.begin();
740 it != files.end(); ++it) {
741 paths.push_back(it->file_path);
743 MultiFilesSelected(paths, params);
746 void FileSelectionCanceled(void* params) override {
747 function_->FileSelectionCanceled();
748 delete this;
751 scoped_refptr<ui::SelectFileDialog> select_file_dialog_;
752 scoped_refptr<FileSystemChooseEntryFunction> function_;
754 DISALLOW_COPY_AND_ASSIGN(FilePicker);
757 void FileSystemChooseEntryFunction::ShowPicker(
758 const ui::SelectFileDialog::FileTypeInfo& file_type_info,
759 ui::SelectFileDialog::Type picker_type) {
760 // TODO(asargent/benwells) - As a short term remediation for crbug.com/179010
761 // we're adding the ability for a whitelisted extension to use this API since
762 // chrome.fileBrowserHandler.selectFile is ChromeOS-only. Eventually we'd
763 // like a better solution and likely this code will go back to being
764 // platform-app only.
765 content::WebContents* const web_contents =
766 extension_->is_platform_app()
767 ? GetWebContentsForRenderFrameHost(GetProfile(), render_frame_host())
768 : GetAssociatedWebContents();
769 if (!web_contents) {
770 error_ = kInvalidCallingPage;
771 SendResponse(false);
772 return;
775 // The file picker will hold a reference to this function instance, preventing
776 // its destruction (and subsequent sending of the function response) until the
777 // user has selected a file or cancelled the picker. At that point, the picker
778 // will delete itself, which will also free the function instance.
779 new FilePicker(
780 this, web_contents, initial_path_, file_type_info, picker_type);
783 // static
784 void FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathForTest(
785 base::FilePath* path) {
786 g_skip_picker_for_test = true;
787 g_use_suggested_path_for_test = false;
788 g_path_to_be_picked_for_test = path;
789 g_paths_to_be_picked_for_test = NULL;
792 void FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathsForTest(
793 std::vector<base::FilePath>* paths) {
794 g_skip_picker_for_test = true;
795 g_use_suggested_path_for_test = false;
796 g_paths_to_be_picked_for_test = paths;
799 // static
800 void FileSystemChooseEntryFunction::SkipPickerAndSelectSuggestedPathForTest() {
801 g_skip_picker_for_test = true;
802 g_use_suggested_path_for_test = true;
803 g_path_to_be_picked_for_test = NULL;
804 g_paths_to_be_picked_for_test = NULL;
807 // static
808 void FileSystemChooseEntryFunction::SkipPickerAndAlwaysCancelForTest() {
809 g_skip_picker_for_test = true;
810 g_use_suggested_path_for_test = false;
811 g_path_to_be_picked_for_test = NULL;
812 g_paths_to_be_picked_for_test = NULL;
815 // static
816 void FileSystemChooseEntryFunction::StopSkippingPickerForTest() {
817 g_skip_picker_for_test = false;
820 // static
821 void FileSystemChooseEntryFunction::SkipDirectoryConfirmationForTest() {
822 g_skip_directory_confirmation_for_test = true;
823 g_allow_directory_access_for_test = true;
826 // static
827 void FileSystemChooseEntryFunction::AutoCancelDirectoryConfirmationForTest() {
828 g_skip_directory_confirmation_for_test = true;
829 g_allow_directory_access_for_test = false;
832 // static
833 void FileSystemChooseEntryFunction::StopSkippingDirectoryConfirmationForTest() {
834 g_skip_directory_confirmation_for_test = false;
837 // static
838 void FileSystemChooseEntryFunction::RegisterTempExternalFileSystemForTest(
839 const std::string& name, const base::FilePath& path) {
840 // For testing on Chrome OS, where to deal with remote and local paths
841 // smoothly, all accessed paths need to be registered in the list of
842 // external mount points.
843 storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
844 name,
845 storage::kFileSystemTypeNativeLocal,
846 storage::FileSystemMountOption(),
847 path);
850 void FileSystemChooseEntryFunction::SetInitialPathOnFileThread(
851 const base::FilePath& suggested_name,
852 const base::FilePath& previous_path) {
853 DCHECK_CURRENTLY_ON(content::BrowserThread::FILE);
854 if (!previous_path.empty() && base::DirectoryExists(previous_path)) {
855 initial_path_ = previous_path.Append(suggested_name);
856 } else {
857 base::FilePath documents_dir;
858 if (PathService::Get(chrome::DIR_USER_DOCUMENTS, &documents_dir)) {
859 initial_path_ = documents_dir.Append(suggested_name);
860 } else {
861 initial_path_ = suggested_name;
866 void FileSystemChooseEntryFunction::FilesSelected(
867 const std::vector<base::FilePath>& paths) {
868 DCHECK(!paths.empty());
869 base::FilePath last_choose_directory;
870 if (is_directory_) {
871 last_choose_directory = paths[0];
872 } else {
873 last_choose_directory = paths[0].DirName();
875 file_system_api::SetLastChooseEntryDirectory(
876 ExtensionPrefs::Get(GetProfile()),
877 extension()->id(),
878 last_choose_directory);
879 if (is_directory_) {
880 // Get the WebContents for the app window to be the parent window of the
881 // confirmation dialog if necessary.
882 content::WebContents* const web_contents =
883 GetWebContentsForRenderFrameHost(GetProfile(), render_frame_host());
884 if (!web_contents) {
885 error_ = kInvalidCallingPage;
886 SendResponse(false);
887 return;
890 DCHECK_EQ(paths.size(), 1u);
891 bool non_native_path = false;
892 #if defined(OS_CHROMEOS)
893 non_native_path =
894 file_manager::util::IsUnderNonNativeLocalPath(GetProfile(), paths[0]);
895 #endif
897 content::BrowserThread::PostTask(
898 content::BrowserThread::FILE,
899 FROM_HERE,
900 base::Bind(
901 &FileSystemChooseEntryFunction::ConfirmDirectoryAccessOnFileThread,
902 this,
903 non_native_path,
904 paths,
905 web_contents));
906 return;
909 OnDirectoryAccessConfirmed(paths);
912 void FileSystemChooseEntryFunction::FileSelectionCanceled() {
913 error_ = kUserCancelled;
914 SendResponse(false);
917 void FileSystemChooseEntryFunction::ConfirmDirectoryAccessOnFileThread(
918 bool non_native_path,
919 const std::vector<base::FilePath>& paths,
920 content::WebContents* web_contents) {
921 const base::FilePath check_path =
922 non_native_path ? paths[0] : base::MakeAbsoluteFilePath(paths[0]);
923 if (check_path.empty()) {
924 content::BrowserThread::PostTask(
925 content::BrowserThread::UI,
926 FROM_HERE,
927 base::Bind(&FileSystemChooseEntryFunction::FileSelectionCanceled,
928 this));
929 return;
932 for (size_t i = 0; i < arraysize(kGraylistedPaths); i++) {
933 base::FilePath graylisted_path;
934 if (PathService::Get(kGraylistedPaths[i], &graylisted_path) &&
935 (check_path == graylisted_path ||
936 check_path.IsParent(graylisted_path))) {
937 if (g_skip_directory_confirmation_for_test) {
938 if (g_allow_directory_access_for_test) {
939 break;
940 } else {
941 content::BrowserThread::PostTask(
942 content::BrowserThread::UI,
943 FROM_HERE,
944 base::Bind(&FileSystemChooseEntryFunction::FileSelectionCanceled,
945 this));
947 return;
950 content::BrowserThread::PostTask(
951 content::BrowserThread::UI,
952 FROM_HERE,
953 base::Bind(
954 CreateDirectoryAccessConfirmationDialog,
955 app_file_handler_util::HasFileSystemWritePermission(
956 extension_.get()),
957 base::UTF8ToUTF16(extension_->name()),
958 web_contents,
959 base::Bind(
960 &FileSystemChooseEntryFunction::OnDirectoryAccessConfirmed,
961 this,
962 paths),
963 base::Bind(&FileSystemChooseEntryFunction::FileSelectionCanceled,
964 this)));
965 return;
969 content::BrowserThread::PostTask(
970 content::BrowserThread::UI,
971 FROM_HERE,
972 base::Bind(&FileSystemChooseEntryFunction::OnDirectoryAccessConfirmed,
973 this, paths));
976 void FileSystemChooseEntryFunction::OnDirectoryAccessConfirmed(
977 const std::vector<base::FilePath>& paths) {
978 if (app_file_handler_util::HasFileSystemWritePermission(extension_.get())) {
979 PrepareFilesForWritableApp(paths);
980 return;
983 // Don't need to check the file, it's for reading.
984 RegisterFileSystemsAndSendResponse(paths);
987 void FileSystemChooseEntryFunction::BuildFileTypeInfo(
988 ui::SelectFileDialog::FileTypeInfo* file_type_info,
989 const base::FilePath::StringType& suggested_extension,
990 const AcceptOptions* accepts,
991 const bool* acceptsAllTypes) {
992 file_type_info->include_all_files = true;
993 if (acceptsAllTypes)
994 file_type_info->include_all_files = *acceptsAllTypes;
996 bool need_suggestion = !file_type_info->include_all_files &&
997 !suggested_extension.empty();
999 if (accepts) {
1000 typedef file_system::AcceptOption AcceptOption;
1001 for (std::vector<linked_ptr<AcceptOption> >::const_iterator iter =
1002 accepts->begin(); iter != accepts->end(); ++iter) {
1003 base::string16 description;
1004 std::vector<base::FilePath::StringType> extensions;
1006 if (!GetFileTypesFromAcceptOption(**iter, &extensions, &description))
1007 continue; // No extensions were found.
1009 file_type_info->extensions.push_back(extensions);
1010 file_type_info->extension_description_overrides.push_back(description);
1012 // If we still need to find suggested_extension, hunt for it inside the
1013 // extensions returned from GetFileTypesFromAcceptOption.
1014 if (need_suggestion && std::find(extensions.begin(),
1015 extensions.end(), suggested_extension) != extensions.end()) {
1016 need_suggestion = false;
1021 // If there's nothing in our accepted extension list or we couldn't find the
1022 // suggested extension required, then default to accepting all types.
1023 if (file_type_info->extensions.empty() || need_suggestion)
1024 file_type_info->include_all_files = true;
1027 void FileSystemChooseEntryFunction::BuildSuggestion(
1028 const std::string *opt_name,
1029 base::FilePath* suggested_name,
1030 base::FilePath::StringType* suggested_extension) {
1031 if (opt_name) {
1032 *suggested_name = base::FilePath::FromUTF8Unsafe(*opt_name);
1034 // Don't allow any path components; shorten to the base name. This should
1035 // result in a relative path, but in some cases may not. Clear the
1036 // suggestion for safety if this is the case.
1037 *suggested_name = suggested_name->BaseName();
1038 if (suggested_name->IsAbsolute())
1039 *suggested_name = base::FilePath();
1041 *suggested_extension = suggested_name->Extension();
1042 if (!suggested_extension->empty())
1043 suggested_extension->erase(suggested_extension->begin()); // drop the .
1047 bool FileSystemChooseEntryFunction::RunAsync() {
1048 scoped_ptr<ChooseEntry::Params> params(ChooseEntry::Params::Create(*args_));
1049 EXTENSION_FUNCTION_VALIDATE(params.get());
1051 base::FilePath suggested_name;
1052 ui::SelectFileDialog::FileTypeInfo file_type_info;
1053 ui::SelectFileDialog::Type picker_type =
1054 ui::SelectFileDialog::SELECT_OPEN_FILE;
1056 file_system::ChooseEntryOptions* options = params->options.get();
1057 if (options) {
1058 multiple_ = options->accepts_multiple;
1059 if (multiple_)
1060 picker_type = ui::SelectFileDialog::SELECT_OPEN_MULTI_FILE;
1062 if (options->type == file_system::CHOOSE_ENTRY_TYPE_OPENWRITABLEFILE &&
1063 !app_file_handler_util::HasFileSystemWritePermission(
1064 extension_.get())) {
1065 error_ = kRequiresFileSystemWriteError;
1066 return false;
1067 } else if (options->type == file_system::CHOOSE_ENTRY_TYPE_SAVEFILE) {
1068 if (!app_file_handler_util::HasFileSystemWritePermission(
1069 extension_.get())) {
1070 error_ = kRequiresFileSystemWriteError;
1071 return false;
1073 if (multiple_) {
1074 error_ = kMultipleUnsupportedError;
1075 return false;
1077 picker_type = ui::SelectFileDialog::SELECT_SAVEAS_FILE;
1078 } else if (options->type == file_system::CHOOSE_ENTRY_TYPE_OPENDIRECTORY) {
1079 is_directory_ = true;
1080 if (!extension_->permissions_data()->HasAPIPermission(
1081 APIPermission::kFileSystemDirectory)) {
1082 error_ = kRequiresFileSystemDirectoryError;
1083 return false;
1085 if (multiple_) {
1086 error_ = kMultipleUnsupportedError;
1087 return false;
1089 picker_type = ui::SelectFileDialog::SELECT_FOLDER;
1092 base::FilePath::StringType suggested_extension;
1093 BuildSuggestion(options->suggested_name.get(), &suggested_name,
1094 &suggested_extension);
1096 BuildFileTypeInfo(&file_type_info, suggested_extension,
1097 options->accepts.get(), options->accepts_all_types.get());
1100 file_type_info.support_drive = true;
1102 base::FilePath previous_path = file_system_api::GetLastChooseEntryDirectory(
1103 ExtensionPrefs::Get(GetProfile()), extension()->id());
1105 content::BrowserThread::PostTaskAndReply(
1106 content::BrowserThread::FILE,
1107 FROM_HERE,
1108 base::Bind(&FileSystemChooseEntryFunction::SetInitialPathOnFileThread,
1109 this,
1110 suggested_name,
1111 previous_path),
1112 base::Bind(&FileSystemChooseEntryFunction::ShowPicker,
1113 this,
1114 file_type_info,
1115 picker_type));
1116 return true;
1119 bool FileSystemRetainEntryFunction::RunAsync() {
1120 std::string entry_id;
1121 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &entry_id));
1122 SavedFilesService* saved_files_service = SavedFilesService::Get(GetProfile());
1123 // Add the file to the retain list if it is not already on there.
1124 if (!saved_files_service->IsRegistered(extension_->id(), entry_id)) {
1125 std::string filesystem_name;
1126 std::string filesystem_path;
1127 base::FilePath path;
1128 EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &filesystem_name));
1129 EXTENSION_FUNCTION_VALIDATE(args_->GetString(2, &filesystem_path));
1130 if (!app_file_handler_util::ValidateFileEntryAndGetPath(
1131 filesystem_name, filesystem_path,
1132 render_frame_host()->GetProcess()->GetID(), &path, &error_)) {
1133 return false;
1136 std::string filesystem_id;
1137 if (!storage::CrackIsolatedFileSystemName(filesystem_name, &filesystem_id))
1138 return false;
1140 const GURL site = util::GetSiteForExtensionId(extension_id(), GetProfile());
1141 storage::FileSystemContext* const context =
1142 content::BrowserContext::GetStoragePartitionForSite(GetProfile(), site)
1143 ->GetFileSystemContext();
1144 const storage::FileSystemURL url = context->CreateCrackedFileSystemURL(
1145 site,
1146 storage::kFileSystemTypeIsolated,
1147 IsolatedContext::GetInstance()
1148 ->CreateVirtualRootPath(filesystem_id)
1149 .Append(base::FilePath::FromUTF8Unsafe(filesystem_path)));
1151 content::BrowserThread::PostTask(
1152 content::BrowserThread::IO,
1153 FROM_HERE,
1154 base::Bind(
1155 base::IgnoreResult(
1156 &storage::FileSystemOperationRunner::GetMetadata),
1157 context->operation_runner()->AsWeakPtr(),
1158 url,
1159 base::Bind(
1160 &PassFileInfoToUIThread,
1161 base::Bind(&FileSystemRetainEntryFunction::RetainFileEntry,
1162 this,
1163 entry_id,
1164 path))));
1165 return true;
1168 saved_files_service->EnqueueFileEntry(extension_->id(), entry_id);
1169 SendResponse(true);
1170 return true;
1173 void FileSystemRetainEntryFunction::RetainFileEntry(
1174 const std::string& entry_id,
1175 const base::FilePath& path,
1176 scoped_ptr<base::File::Info> file_info) {
1177 if (!file_info) {
1178 SendResponse(false);
1179 return;
1182 SavedFilesService* saved_files_service = SavedFilesService::Get(GetProfile());
1183 saved_files_service->RegisterFileEntry(
1184 extension_->id(), entry_id, path, file_info->is_directory);
1185 saved_files_service->EnqueueFileEntry(extension_->id(), entry_id);
1186 SendResponse(true);
1189 bool FileSystemIsRestorableFunction::RunSync() {
1190 std::string entry_id;
1191 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &entry_id));
1192 SetResult(new base::FundamentalValue(SavedFilesService::Get(
1193 GetProfile())->IsRegistered(extension_->id(), entry_id)));
1194 return true;
1197 bool FileSystemRestoreEntryFunction::RunAsync() {
1198 std::string entry_id;
1199 bool needs_new_entry;
1200 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &entry_id));
1201 EXTENSION_FUNCTION_VALIDATE(args_->GetBoolean(1, &needs_new_entry));
1202 const SavedFileEntry* file_entry = SavedFilesService::Get(
1203 GetProfile())->GetFileEntry(extension_->id(), entry_id);
1204 if (!file_entry) {
1205 error_ = kUnknownIdError;
1206 return false;
1209 SavedFilesService::Get(GetProfile())
1210 ->EnqueueFileEntry(extension_->id(), entry_id);
1212 // Only create a new file entry if the renderer requests one.
1213 // |needs_new_entry| will be false if the renderer already has an Entry for
1214 // |entry_id|.
1215 if (needs_new_entry) {
1216 is_directory_ = file_entry->is_directory;
1217 CreateResponse();
1218 AddEntryToResponse(file_entry->path, file_entry->id);
1220 SendResponse(true);
1221 return true;
1224 bool FileSystemObserveDirectoryFunction::RunSync() {
1225 NOTIMPLEMENTED();
1226 error_ = kUnknownIdError;
1227 return false;
1230 bool FileSystemUnobserveEntryFunction::RunSync() {
1231 NOTIMPLEMENTED();
1232 error_ = kUnknownIdError;
1233 return false;
1236 bool FileSystemGetObservedEntriesFunction::RunSync() {
1237 NOTIMPLEMENTED();
1238 error_ = kUnknownIdError;
1239 return false;
1242 #if !defined(OS_CHROMEOS)
1243 ExtensionFunction::ResponseAction FileSystemRequestFileSystemFunction::Run() {
1244 using api::file_system::RequestFileSystem::Params;
1245 const scoped_ptr<Params> params(Params::Create(*args_));
1246 EXTENSION_FUNCTION_VALIDATE(params);
1248 NOTIMPLEMENTED();
1249 return RespondNow(Error(kNotSupportedOnCurrentPlatformError));
1252 ExtensionFunction::ResponseAction FileSystemGetVolumeListFunction::Run() {
1253 NOTIMPLEMENTED();
1254 return RespondNow(Error(kNotSupportedOnCurrentPlatformError));
1256 #else
1258 FileSystemRequestFileSystemFunction::FileSystemRequestFileSystemFunction()
1259 : chrome_details_(this) {
1262 FileSystemRequestFileSystemFunction::~FileSystemRequestFileSystemFunction() {
1265 ExtensionFunction::ResponseAction FileSystemRequestFileSystemFunction::Run() {
1266 using api::file_system::RequestFileSystem::Params;
1267 const scoped_ptr<Params> params(Params::Create(*args_));
1268 EXTENSION_FUNCTION_VALIDATE(params);
1270 // Only kiosk apps in kiosk sessions can use this API.
1271 // Additionally it is enabled for whitelisted component extensions and apps.
1272 file_system_api::ConsentProviderDelegate consent_provider_delegate(
1273 chrome_details_.GetProfile(), render_frame_host());
1274 file_system_api::ConsentProvider consent_provider(&consent_provider_delegate);
1276 if (!consent_provider.IsGrantable(*extension()))
1277 return RespondNow(Error(kNotSupportedOnNonKioskSessionError));
1279 using file_manager::VolumeManager;
1280 using file_manager::Volume;
1281 VolumeManager* const volume_manager =
1282 VolumeManager::Get(chrome_details_.GetProfile());
1283 DCHECK(volume_manager);
1285 const bool writable =
1286 params->options.writable.get() && *params->options.writable.get();
1287 if (writable &&
1288 !app_file_handler_util::HasFileSystemWritePermission(extension_.get())) {
1289 return RespondNow(Error(kRequiresFileSystemWriteError));
1292 base::WeakPtr<file_manager::Volume> volume =
1293 volume_manager->FindVolumeById(params->options.volume_id);
1294 if (!volume.get())
1295 return RespondNow(Error(kVolumeNotFoundError));
1297 const GURL site =
1298 util::GetSiteForExtensionId(extension_id(), chrome_details_.GetProfile());
1299 scoped_refptr<storage::FileSystemContext> file_system_context =
1300 content::BrowserContext::GetStoragePartitionForSite(
1301 chrome_details_.GetProfile(), site)->GetFileSystemContext();
1302 storage::ExternalFileSystemBackend* const backend =
1303 file_system_context->external_backend();
1304 DCHECK(backend);
1306 base::FilePath virtual_path;
1307 if (!backend->GetVirtualPath(volume->mount_path(), &virtual_path))
1308 return RespondNow(Error(kSecurityError));
1310 if (writable && (volume->is_read_only()))
1311 return RespondNow(Error(kSecurityError));
1313 consent_provider.RequestConsent(
1314 *extension(), volume, writable,
1315 base::Bind(&FileSystemRequestFileSystemFunction::OnConsentReceived, this,
1316 volume, writable));
1317 return RespondLater();
1320 void FileSystemRequestFileSystemFunction::OnConsentReceived(
1321 const base::WeakPtr<file_manager::Volume>& volume,
1322 bool writable,
1323 ConsentProvider::Consent result) {
1324 using file_manager::VolumeManager;
1325 using file_manager::Volume;
1327 switch (result) {
1328 case ConsentProvider::CONSENT_REJECTED:
1329 SetError(kSecurityError);
1330 SendResponse(false);
1331 return;
1333 case ConsentProvider::CONSENT_IMPOSSIBLE:
1334 SetError(kConsentImpossible);
1335 SendResponse(false);
1336 return;
1338 case ConsentProvider::CONSENT_GRANTED:
1339 break;
1342 if (!volume.get()) {
1343 SetError(kVolumeNotFoundError);
1344 SendResponse(false);
1345 return;
1348 const GURL site =
1349 util::GetSiteForExtensionId(extension_id(), chrome_details_.GetProfile());
1350 scoped_refptr<storage::FileSystemContext> file_system_context =
1351 content::BrowserContext::GetStoragePartitionForSite(
1352 chrome_details_.GetProfile(), site)->GetFileSystemContext();
1353 storage::ExternalFileSystemBackend* const backend =
1354 file_system_context->external_backend();
1355 DCHECK(backend);
1357 base::FilePath virtual_path;
1358 if (!backend->GetVirtualPath(volume->mount_path(), &virtual_path)) {
1359 SetError(kSecurityError);
1360 SendResponse(false);
1361 return;
1364 storage::IsolatedContext* const isolated_context =
1365 storage::IsolatedContext::GetInstance();
1366 DCHECK(isolated_context);
1368 const storage::FileSystemURL original_url =
1369 file_system_context->CreateCrackedFileSystemURL(
1370 GURL(std::string(kExtensionScheme) + url::kStandardSchemeSeparator +
1371 extension_id()),
1372 storage::kFileSystemTypeExternal, virtual_path);
1374 // Set a fixed register name, as the automatic one would leak the mount point
1375 // directory.
1376 std::string register_name = "fs";
1377 const std::string file_system_id =
1378 isolated_context->RegisterFileSystemForPath(
1379 storage::kFileSystemTypeNativeForPlatformApp,
1380 std::string() /* file_system_id */, original_url.path(),
1381 &register_name);
1382 if (file_system_id.empty()) {
1383 SetError(kSecurityError);
1384 SendResponse(false);
1385 return;
1388 backend->GrantFileAccessToExtension(extension_->id(), virtual_path);
1390 // Grant file permissions to the renderer hosting component.
1391 content::ChildProcessSecurityPolicy* policy =
1392 content::ChildProcessSecurityPolicy::GetInstance();
1393 DCHECK(policy);
1395 // Read-only permisisons.
1396 policy->GrantReadFile(render_frame_host()->GetProcess()->GetID(),
1397 volume->mount_path());
1398 policy->GrantReadFileSystem(render_frame_host()->GetProcess()->GetID(),
1399 file_system_id);
1401 // Additional write permissions.
1402 if (writable) {
1403 policy->GrantCreateReadWriteFile(render_frame_host()->GetProcess()->GetID(),
1404 volume->mount_path());
1405 policy->GrantCopyInto(render_frame_host()->GetProcess()->GetID(),
1406 volume->mount_path());
1407 policy->GrantWriteFileSystem(render_frame_host()->GetProcess()->GetID(),
1408 file_system_id);
1409 policy->GrantDeleteFromFileSystem(
1410 render_frame_host()->GetProcess()->GetID(), file_system_id);
1411 policy->GrantCreateFileForFileSystem(
1412 render_frame_host()->GetProcess()->GetID(), file_system_id);
1415 base::DictionaryValue* const dict = new base::DictionaryValue();
1416 dict->SetString("file_system_id", file_system_id);
1417 dict->SetString("file_system_path", register_name);
1419 SetResult(dict);
1420 SendResponse(true);
1423 FileSystemGetVolumeListFunction::FileSystemGetVolumeListFunction()
1424 : chrome_details_(this) {
1427 FileSystemGetVolumeListFunction::~FileSystemGetVolumeListFunction() {
1430 ExtensionFunction::ResponseAction FileSystemGetVolumeListFunction::Run() {
1431 // Only kiosk apps in kiosk sessions can use this API.
1432 // Additionally it is enabled for whitelisted component extensions and apps.
1433 file_system_api::ConsentProviderDelegate consent_provider_delegate(
1434 chrome_details_.GetProfile(), render_frame_host());
1435 file_system_api::ConsentProvider consent_provider(&consent_provider_delegate);
1437 if (!consent_provider.IsGrantable(*extension()))
1438 return RespondNow(Error(kNotSupportedOnNonKioskSessionError));
1439 using api::file_system::Volume;
1440 std::vector<linked_ptr<Volume>> result_volume_list;
1441 FillVolumeList(chrome_details_.GetProfile(), &result_volume_list);
1443 return RespondNow(
1444 ArgumentList(api::file_system::GetVolumeList::Results::Create(
1445 result_volume_list).Pass()));
1447 #endif
1449 } // namespace extensions