Allow only one bookmark to be added for multiple fast starring
[chromium-blink-merge.git] / chrome / browser / extensions / api / file_system / file_system_api.cc
blob3f033d4fb6f1dba3b1e4ea0a6eb30b33d551baa7
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 = *iter;
155 base::StringToLowerASCII(&accept_type);
156 net::GetExtensionsForMimeType(accept_type, &inner);
157 if (inner.empty())
158 continue;
160 if (valid_type)
161 description_id = 0; // We already have an accept type with label; if
162 // we find another, give up and use the default.
163 else if (accept_type == "image/*")
164 description_id = IDS_IMAGE_FILES;
165 else if (accept_type == "audio/*")
166 description_id = IDS_AUDIO_FILES;
167 else if (accept_type == "video/*")
168 description_id = IDS_VIDEO_FILES;
170 extension_set.insert(inner.begin(), inner.end());
171 valid_type = true;
175 if (accept_option.extensions.get()) {
176 std::vector<std::string>* list = accept_option.extensions.get();
177 for (std::vector<std::string>::const_iterator iter = list->begin();
178 iter != list->end(); ++iter) {
179 std::string extension = *iter;
180 base::StringToLowerASCII(&extension);
181 #if defined(OS_WIN)
182 extension_set.insert(base::UTF8ToWide(*iter));
183 #else
184 extension_set.insert(*iter);
185 #endif
189 extensions->assign(extension_set.begin(), extension_set.end());
190 if (extensions->empty())
191 return false;
193 if (accept_option.description.get())
194 *description = base::UTF8ToUTF16(*accept_option.description.get());
195 else if (description_id)
196 *description = l10n_util::GetStringUTF16(description_id);
198 return true;
201 // Key for the path of the directory of the file last chosen by the user in
202 // response to a chrome.fileSystem.chooseEntry() call.
203 const char kLastChooseEntryDirectory[] = "last_choose_file_directory";
205 const int kGraylistedPaths[] = {
206 base::DIR_HOME,
207 #if defined(OS_WIN)
208 base::DIR_PROGRAM_FILES,
209 base::DIR_PROGRAM_FILESX86,
210 base::DIR_WINDOWS,
211 #endif
214 typedef base::Callback<void(scoped_ptr<base::File::Info>)> FileInfoOptCallback;
216 // Passes optional file info to the UI thread depending on |result| and |info|.
217 void PassFileInfoToUIThread(const FileInfoOptCallback& callback,
218 base::File::Error result,
219 const base::File::Info& info) {
220 DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
221 scoped_ptr<base::File::Info> file_info(
222 result == base::File::FILE_OK ? new base::File::Info(info) : NULL);
223 content::BrowserThread::PostTask(
224 content::BrowserThread::UI,
225 FROM_HERE,
226 base::Bind(callback, base::Passed(&file_info)));
229 // Gets a WebContents instance handle for a platform app hosted in
230 // |render_frame_host|. If not found, then returns NULL.
231 content::WebContents* GetWebContentsForRenderFrameHost(
232 Profile* profile,
233 content::RenderFrameHost* render_frame_host) {
234 content::WebContents* web_contents =
235 content::WebContents::FromRenderFrameHost(render_frame_host);
236 // Check if there is an app window associated with the web contents; if not,
237 // return null.
238 return AppWindowRegistry::Get(profile)
239 ->GetAppWindowForWebContents(web_contents)
240 ? web_contents
241 : nullptr;
244 #if defined(OS_CHROMEOS)
245 // Gets a WebContents instance handle for a current window of a platform app
246 // with |app_id|. If not found, then returns NULL.
247 content::WebContents* GetWebContentsForAppId(Profile* profile,
248 const std::string& app_id) {
249 AppWindowRegistry* const registry = AppWindowRegistry::Get(profile);
250 DCHECK(registry);
251 AppWindow* const app_window = registry->GetCurrentAppWindowForApp(app_id);
252 return app_window ? app_window->web_contents() : nullptr;
255 // Fills a list of volumes mounted in the system.
256 void FillVolumeList(Profile* profile,
257 std::vector<linked_ptr<api::file_system::Volume>>* result) {
258 using file_manager::VolumeManager;
259 VolumeManager* const volume_manager = VolumeManager::Get(profile);
260 DCHECK(volume_manager);
262 using api::file_system::Volume;
263 const auto& volume_list = volume_manager->GetVolumeList();
264 // Convert volume_list to result_volume_list.
265 for (const auto& volume : volume_list) {
266 const linked_ptr<Volume> result_volume(new Volume);
267 result_volume->volume_id = volume->volume_id();
268 result_volume->writable = !volume->is_read_only();
269 result->push_back(result_volume);
272 #endif
274 } // namespace
276 namespace file_system_api {
278 base::FilePath GetLastChooseEntryDirectory(const ExtensionPrefs* prefs,
279 const std::string& extension_id) {
280 base::FilePath path;
281 std::string string_path;
282 if (prefs->ReadPrefAsString(extension_id,
283 kLastChooseEntryDirectory,
284 &string_path)) {
285 path = base::FilePath::FromUTF8Unsafe(string_path);
287 return path;
290 void SetLastChooseEntryDirectory(ExtensionPrefs* prefs,
291 const std::string& extension_id,
292 const base::FilePath& path) {
293 prefs->UpdateExtensionPref(extension_id,
294 kLastChooseEntryDirectory,
295 base::CreateFilePathValue(path));
298 std::vector<base::FilePath> GetGrayListedDirectories() {
299 std::vector<base::FilePath> graylisted_directories;
300 for (size_t i = 0; i < arraysize(kGraylistedPaths); ++i) {
301 base::FilePath graylisted_path;
302 if (PathService::Get(kGraylistedPaths[i], &graylisted_path))
303 graylisted_directories.push_back(graylisted_path);
305 return graylisted_directories;
308 #if defined(OS_CHROMEOS)
309 void DispatchVolumeListChangeEvent(Profile* profile) {
310 DCHECK(profile);
311 EventRouter* const event_router = EventRouter::Get(profile);
312 if (!event_router) // Possible on shutdown.
313 return;
315 ExtensionRegistry* const registry = ExtensionRegistry::Get(profile);
316 if (!registry) // Possible on shutdown.
317 return;
319 ConsentProviderDelegate consent_provider_delegate(profile, nullptr);
320 ConsentProvider consent_provider(&consent_provider_delegate);
321 api::file_system::VolumeListChangedEvent event_args;
322 FillVolumeList(profile, &event_args.volumes);
323 for (const auto& extension : registry->enabled_extensions()) {
324 if (!consent_provider.IsGrantable(*extension.get()))
325 continue;
326 event_router->DispatchEventToExtension(
327 extension->id(),
328 make_scoped_ptr(new Event(
329 events::FILE_SYSTEM_ON_VOLUME_LIST_CHANGED,
330 api::file_system::OnVolumeListChanged::kEventName,
331 api::file_system::OnVolumeListChanged::Create(event_args))));
335 ConsentProvider::ConsentProvider(DelegateInterface* delegate)
336 : delegate_(delegate) {
337 DCHECK(delegate_);
340 ConsentProvider::~ConsentProvider() {
343 void ConsentProvider::RequestConsent(
344 const Extension& extension,
345 const base::WeakPtr<file_manager::Volume>& volume,
346 bool writable,
347 const ConsentCallback& callback) {
348 DCHECK(IsGrantable(extension));
350 // If a whitelisted component, then no need to ask or inform the user.
351 if (extension.location() == Manifest::COMPONENT &&
352 delegate_->IsWhitelistedComponent(extension)) {
353 base::ThreadTaskRunnerHandle::Get()->PostTask(
354 FROM_HERE, base::Bind(callback, CONSENT_GRANTED));
355 return;
358 // If auto-launched kiosk app, then no need to ask user either, but show the
359 // notification.
360 if (delegate_->IsAutoLaunched(extension)) {
361 delegate_->ShowNotification(extension, volume, writable);
362 base::ThreadTaskRunnerHandle::Get()->PostTask(
363 FROM_HERE, base::Bind(callback, CONSENT_GRANTED));
364 return;
367 // If it's a kiosk app running in manual-launch kiosk session, then show
368 // the confirmation dialog.
369 if (KioskModeInfo::IsKioskOnly(&extension) &&
370 user_manager::UserManager::Get()->IsLoggedInAsKioskApp()) {
371 delegate_->ShowDialog(extension, volume, writable,
372 base::Bind(&ConsentProvider::DialogResultToConsent,
373 base::Unretained(this), callback));
374 return;
377 NOTREACHED() << "Cannot request consent for non-grantable extensions.";
380 bool ConsentProvider::IsGrantable(const Extension& extension) {
381 const bool is_whitelisted_component =
382 delegate_->IsWhitelistedComponent(extension);
384 const bool is_running_in_kiosk_session =
385 KioskModeInfo::IsKioskOnly(&extension) &&
386 user_manager::UserManager::Get()->IsLoggedInAsKioskApp();
388 return is_whitelisted_component || is_running_in_kiosk_session;
391 void ConsentProvider::DialogResultToConsent(const ConsentCallback& callback,
392 ui::DialogButton button) {
393 switch (button) {
394 case ui::DIALOG_BUTTON_NONE:
395 callback.Run(CONSENT_IMPOSSIBLE);
396 break;
397 case ui::DIALOG_BUTTON_OK:
398 callback.Run(CONSENT_GRANTED);
399 break;
400 case ui::DIALOG_BUTTON_CANCEL:
401 callback.Run(CONSENT_REJECTED);
402 break;
406 ConsentProviderDelegate::ConsentProviderDelegate(Profile* profile,
407 content::RenderFrameHost* host)
408 : profile_(profile), host_(host) {
409 DCHECK(profile_);
412 ConsentProviderDelegate::~ConsentProviderDelegate() {
415 // static
416 void ConsentProviderDelegate::SetAutoDialogButtonForTest(
417 ui::DialogButton button) {
418 g_auto_dialog_button_for_test = button;
421 void ConsentProviderDelegate::ShowDialog(
422 const Extension& extension,
423 const base::WeakPtr<file_manager::Volume>& volume,
424 bool writable,
425 const file_system_api::ConsentProvider::ShowDialogCallback& callback) {
426 DCHECK(host_);
427 content::WebContents* const foreground_contents =
428 GetWebContentsForRenderFrameHost(profile_, host_);
429 // If there is no web contents handle, then the method is most probably
430 // executed from a background page. Find an app window to host the dialog.
431 content::WebContents* const web_contents =
432 foreground_contents ? foreground_contents
433 : GetWebContentsForAppId(profile_, extension.id());
434 if (!web_contents) {
435 base::ThreadTaskRunnerHandle::Get()->PostTask(
436 FROM_HERE, base::Bind(callback, ui::DIALOG_BUTTON_NONE));
437 return;
440 // Short circuit the user consent dialog for tests. This is far from a pretty
441 // code design.
442 if (g_auto_dialog_button_for_test != ui::DIALOG_BUTTON_NONE) {
443 base::ThreadTaskRunnerHandle::Get()->PostTask(
444 FROM_HERE,
445 base::Bind(callback, g_auto_dialog_button_for_test /* result */));
446 return;
449 RequestFileSystemDialogView::ShowDialog(web_contents, extension, volume,
450 writable, base::Bind(callback));
453 void ConsentProviderDelegate::ShowNotification(
454 const Extension& extension,
455 const base::WeakPtr<file_manager::Volume>& volume,
456 bool writable) {
457 RequestFileSystemNotification::ShowAutoGrantedNotification(
458 profile_, extension, volume, writable);
461 bool ConsentProviderDelegate::IsAutoLaunched(const Extension& extension) {
462 chromeos::KioskAppManager::App app_info;
463 return chromeos::KioskAppManager::Get()->GetApp(extension.id(), &app_info) &&
464 app_info.was_auto_launched_with_zero_delay;
467 bool ConsentProviderDelegate::IsWhitelistedComponent(
468 const Extension& extension) {
469 for (const auto& whitelisted_id : kRequestFileSystemComponentWhitelist) {
470 if (extension.id().compare(whitelisted_id) == 0)
471 return true;
473 return false;
476 #endif
478 } // namespace file_system_api
480 #if defined(OS_CHROMEOS)
481 using file_system_api::ConsentProvider;
482 #endif
484 bool FileSystemGetDisplayPathFunction::RunSync() {
485 std::string filesystem_name;
486 std::string filesystem_path;
487 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &filesystem_name));
488 EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &filesystem_path));
490 base::FilePath file_path;
491 if (!app_file_handler_util::ValidateFileEntryAndGetPath(
492 filesystem_name, filesystem_path,
493 render_frame_host()->GetProcess()->GetID(), &file_path, &error_))
494 return false;
496 file_path = path_util::PrettifyPath(file_path);
497 SetResult(new base::StringValue(file_path.value()));
498 return true;
501 FileSystemEntryFunction::FileSystemEntryFunction()
502 : multiple_(false),
503 is_directory_(false),
504 response_(NULL) {}
506 void FileSystemEntryFunction::PrepareFilesForWritableApp(
507 const std::vector<base::FilePath>& paths) {
508 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
509 app_file_handler_util::PrepareFilesForWritableApp(
510 paths,
511 GetProfile(),
512 is_directory_,
513 base::Bind(&FileSystemEntryFunction::RegisterFileSystemsAndSendResponse,
514 this,
515 paths),
516 base::Bind(&FileSystemEntryFunction::HandleWritableFileError, this));
519 void FileSystemEntryFunction::RegisterFileSystemsAndSendResponse(
520 const std::vector<base::FilePath>& paths) {
521 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
522 if (!render_frame_host())
523 return;
525 CreateResponse();
526 for (std::vector<base::FilePath>::const_iterator it = paths.begin();
527 it != paths.end(); ++it) {
528 AddEntryToResponse(*it, "");
530 SendResponse(true);
533 void FileSystemEntryFunction::CreateResponse() {
534 DCHECK(!response_);
535 response_ = new base::DictionaryValue();
536 base::ListValue* list = new base::ListValue();
537 response_->Set("entries", list);
538 response_->SetBoolean("multiple", multiple_);
539 SetResult(response_);
542 void FileSystemEntryFunction::AddEntryToResponse(
543 const base::FilePath& path,
544 const std::string& id_override) {
545 DCHECK(response_);
546 GrantedFileEntry file_entry = app_file_handler_util::CreateFileEntry(
547 GetProfile(),
548 extension(),
549 render_frame_host()->GetProcess()->GetID(),
550 path,
551 is_directory_);
552 base::ListValue* entries;
553 bool success = response_->GetList("entries", &entries);
554 DCHECK(success);
556 base::DictionaryValue* entry = new base::DictionaryValue();
557 entry->SetString("fileSystemId", file_entry.filesystem_id);
558 entry->SetString("baseName", file_entry.registered_name);
559 if (id_override.empty())
560 entry->SetString("id", file_entry.id);
561 else
562 entry->SetString("id", id_override);
563 entry->SetBoolean("isDirectory", is_directory_);
564 entries->Append(entry);
567 void FileSystemEntryFunction::HandleWritableFileError(
568 const base::FilePath& error_path) {
569 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
570 error_ = base::StringPrintf(kWritableFileErrorFormat,
571 error_path.BaseName().AsUTF8Unsafe().c_str());
572 SendResponse(false);
575 bool FileSystemGetWritableEntryFunction::RunAsync() {
576 std::string filesystem_name;
577 std::string filesystem_path;
578 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &filesystem_name));
579 EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &filesystem_path));
581 if (!app_file_handler_util::HasFileSystemWritePermission(extension_.get())) {
582 error_ = kRequiresFileSystemWriteError;
583 return false;
586 if (!app_file_handler_util::ValidateFileEntryAndGetPath(
587 filesystem_name, filesystem_path,
588 render_frame_host()->GetProcess()->GetID(), &path_, &error_))
589 return false;
591 content::BrowserThread::PostTaskAndReply(
592 content::BrowserThread::FILE,
593 FROM_HERE,
594 base::Bind(
595 &FileSystemGetWritableEntryFunction::SetIsDirectoryOnFileThread,
596 this),
597 base::Bind(
598 &FileSystemGetWritableEntryFunction::CheckPermissionAndSendResponse,
599 this));
600 return true;
603 void FileSystemGetWritableEntryFunction::CheckPermissionAndSendResponse() {
604 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
605 if (is_directory_ &&
606 !extension_->permissions_data()->HasAPIPermission(
607 APIPermission::kFileSystemDirectory)) {
608 error_ = kRequiresFileSystemDirectoryError;
609 SendResponse(false);
611 std::vector<base::FilePath> paths;
612 paths.push_back(path_);
613 PrepareFilesForWritableApp(paths);
616 void FileSystemGetWritableEntryFunction::SetIsDirectoryOnFileThread() {
617 DCHECK_CURRENTLY_ON(content::BrowserThread::FILE);
618 if (base::DirectoryExists(path_)) {
619 is_directory_ = true;
623 bool FileSystemIsWritableEntryFunction::RunSync() {
624 std::string filesystem_name;
625 std::string filesystem_path;
626 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &filesystem_name));
627 EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &filesystem_path));
629 std::string filesystem_id;
630 if (!storage::CrackIsolatedFileSystemName(filesystem_name, &filesystem_id)) {
631 error_ = app_file_handler_util::kInvalidParameters;
632 return false;
635 content::ChildProcessSecurityPolicy* policy =
636 content::ChildProcessSecurityPolicy::GetInstance();
637 int renderer_id = render_frame_host()->GetProcess()->GetID();
638 bool is_writable = policy->CanReadWriteFileSystem(renderer_id,
639 filesystem_id);
641 SetResult(new base::FundamentalValue(is_writable));
642 return true;
645 // Handles showing a dialog to the user to ask for the filename for a file to
646 // save or open.
647 class FileSystemChooseEntryFunction::FilePicker
648 : public ui::SelectFileDialog::Listener {
649 public:
650 FilePicker(FileSystemChooseEntryFunction* function,
651 content::WebContents* web_contents,
652 const base::FilePath& suggested_name,
653 const ui::SelectFileDialog::FileTypeInfo& file_type_info,
654 ui::SelectFileDialog::Type picker_type)
655 : function_(function) {
656 select_file_dialog_ = ui::SelectFileDialog::Create(
657 this, new ChromeSelectFilePolicy(web_contents));
658 gfx::NativeWindow owning_window = web_contents ?
659 platform_util::GetTopLevel(web_contents->GetNativeView()) :
660 NULL;
662 if (g_skip_picker_for_test) {
663 if (g_use_suggested_path_for_test) {
664 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
665 base::Bind(
666 &FileSystemChooseEntryFunction::FilePicker::FileSelected,
667 base::Unretained(this), suggested_name, 1,
668 static_cast<void*>(NULL)));
669 } else if (g_path_to_be_picked_for_test) {
670 content::BrowserThread::PostTask(
671 content::BrowserThread::UI, FROM_HERE,
672 base::Bind(
673 &FileSystemChooseEntryFunction::FilePicker::FileSelected,
674 base::Unretained(this), *g_path_to_be_picked_for_test, 1,
675 static_cast<void*>(NULL)));
676 } else if (g_paths_to_be_picked_for_test) {
677 content::BrowserThread::PostTask(
678 content::BrowserThread::UI,
679 FROM_HERE,
680 base::Bind(
681 &FileSystemChooseEntryFunction::FilePicker::MultiFilesSelected,
682 base::Unretained(this),
683 *g_paths_to_be_picked_for_test,
684 static_cast<void*>(NULL)));
685 } else {
686 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
687 base::Bind(
688 &FileSystemChooseEntryFunction::FilePicker::
689 FileSelectionCanceled,
690 base::Unretained(this), static_cast<void*>(NULL)));
692 return;
695 select_file_dialog_->SelectFile(picker_type,
696 base::string16(),
697 suggested_name,
698 &file_type_info,
700 base::FilePath::StringType(),
701 owning_window,
702 NULL);
705 ~FilePicker() override {}
707 private:
708 // ui::SelectFileDialog::Listener implementation.
709 void FileSelected(const base::FilePath& path,
710 int index,
711 void* params) override {
712 std::vector<base::FilePath> paths;
713 paths.push_back(path);
714 MultiFilesSelected(paths, params);
717 void FileSelectedWithExtraInfo(const ui::SelectedFileInfo& file,
718 int index,
719 void* params) override {
720 // Normally, file.local_path is used because it is a native path to the
721 // local read-only cached file in the case of remote file system like
722 // Chrome OS's Google Drive integration. Here, however, |file.file_path| is
723 // necessary because we need to create a FileEntry denoting the remote file,
724 // not its cache. On other platforms than Chrome OS, they are the same.
726 // TODO(kinaba): remove this, once after the file picker implements proper
727 // switch of the path treatment depending on the |support_drive| flag.
728 FileSelected(file.file_path, index, params);
731 void MultiFilesSelected(const std::vector<base::FilePath>& files,
732 void* params) override {
733 function_->FilesSelected(files);
734 delete this;
737 void MultiFilesSelectedWithExtraInfo(
738 const std::vector<ui::SelectedFileInfo>& files,
739 void* params) override {
740 std::vector<base::FilePath> paths;
741 for (std::vector<ui::SelectedFileInfo>::const_iterator it = files.begin();
742 it != files.end(); ++it) {
743 paths.push_back(it->file_path);
745 MultiFilesSelected(paths, params);
748 void FileSelectionCanceled(void* params) override {
749 function_->FileSelectionCanceled();
750 delete this;
753 scoped_refptr<ui::SelectFileDialog> select_file_dialog_;
754 scoped_refptr<FileSystemChooseEntryFunction> function_;
756 DISALLOW_COPY_AND_ASSIGN(FilePicker);
759 void FileSystemChooseEntryFunction::ShowPicker(
760 const ui::SelectFileDialog::FileTypeInfo& file_type_info,
761 ui::SelectFileDialog::Type picker_type) {
762 // TODO(asargent/benwells) - As a short term remediation for crbug.com/179010
763 // we're adding the ability for a whitelisted extension to use this API since
764 // chrome.fileBrowserHandler.selectFile is ChromeOS-only. Eventually we'd
765 // like a better solution and likely this code will go back to being
766 // platform-app only.
767 content::WebContents* const web_contents =
768 extension_->is_platform_app()
769 ? GetWebContentsForRenderFrameHost(GetProfile(), render_frame_host())
770 : GetAssociatedWebContents();
771 if (!web_contents) {
772 error_ = kInvalidCallingPage;
773 SendResponse(false);
774 return;
777 // The file picker will hold a reference to this function instance, preventing
778 // its destruction (and subsequent sending of the function response) until the
779 // user has selected a file or cancelled the picker. At that point, the picker
780 // will delete itself, which will also free the function instance.
781 new FilePicker(
782 this, web_contents, initial_path_, file_type_info, picker_type);
785 // static
786 void FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathForTest(
787 base::FilePath* path) {
788 g_skip_picker_for_test = true;
789 g_use_suggested_path_for_test = false;
790 g_path_to_be_picked_for_test = path;
791 g_paths_to_be_picked_for_test = NULL;
794 void FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathsForTest(
795 std::vector<base::FilePath>* paths) {
796 g_skip_picker_for_test = true;
797 g_use_suggested_path_for_test = false;
798 g_paths_to_be_picked_for_test = paths;
801 // static
802 void FileSystemChooseEntryFunction::SkipPickerAndSelectSuggestedPathForTest() {
803 g_skip_picker_for_test = true;
804 g_use_suggested_path_for_test = true;
805 g_path_to_be_picked_for_test = NULL;
806 g_paths_to_be_picked_for_test = NULL;
809 // static
810 void FileSystemChooseEntryFunction::SkipPickerAndAlwaysCancelForTest() {
811 g_skip_picker_for_test = true;
812 g_use_suggested_path_for_test = false;
813 g_path_to_be_picked_for_test = NULL;
814 g_paths_to_be_picked_for_test = NULL;
817 // static
818 void FileSystemChooseEntryFunction::StopSkippingPickerForTest() {
819 g_skip_picker_for_test = false;
822 // static
823 void FileSystemChooseEntryFunction::SkipDirectoryConfirmationForTest() {
824 g_skip_directory_confirmation_for_test = true;
825 g_allow_directory_access_for_test = true;
828 // static
829 void FileSystemChooseEntryFunction::AutoCancelDirectoryConfirmationForTest() {
830 g_skip_directory_confirmation_for_test = true;
831 g_allow_directory_access_for_test = false;
834 // static
835 void FileSystemChooseEntryFunction::StopSkippingDirectoryConfirmationForTest() {
836 g_skip_directory_confirmation_for_test = false;
839 // static
840 void FileSystemChooseEntryFunction::RegisterTempExternalFileSystemForTest(
841 const std::string& name, const base::FilePath& path) {
842 // For testing on Chrome OS, where to deal with remote and local paths
843 // smoothly, all accessed paths need to be registered in the list of
844 // external mount points.
845 storage::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
846 name,
847 storage::kFileSystemTypeNativeLocal,
848 storage::FileSystemMountOption(),
849 path);
852 void FileSystemChooseEntryFunction::SetInitialPathOnFileThread(
853 const base::FilePath& suggested_name,
854 const base::FilePath& previous_path) {
855 DCHECK_CURRENTLY_ON(content::BrowserThread::FILE);
856 if (!previous_path.empty() && base::DirectoryExists(previous_path)) {
857 initial_path_ = previous_path.Append(suggested_name);
858 } else {
859 base::FilePath documents_dir;
860 if (PathService::Get(chrome::DIR_USER_DOCUMENTS, &documents_dir)) {
861 initial_path_ = documents_dir.Append(suggested_name);
862 } else {
863 initial_path_ = suggested_name;
868 void FileSystemChooseEntryFunction::FilesSelected(
869 const std::vector<base::FilePath>& paths) {
870 DCHECK(!paths.empty());
871 base::FilePath last_choose_directory;
872 if (is_directory_) {
873 last_choose_directory = paths[0];
874 } else {
875 last_choose_directory = paths[0].DirName();
877 file_system_api::SetLastChooseEntryDirectory(
878 ExtensionPrefs::Get(GetProfile()),
879 extension()->id(),
880 last_choose_directory);
881 if (is_directory_) {
882 // Get the WebContents for the app window to be the parent window of the
883 // confirmation dialog if necessary.
884 content::WebContents* const web_contents =
885 GetWebContentsForRenderFrameHost(GetProfile(), render_frame_host());
886 if (!web_contents) {
887 error_ = kInvalidCallingPage;
888 SendResponse(false);
889 return;
892 DCHECK_EQ(paths.size(), 1u);
893 bool non_native_path = false;
894 #if defined(OS_CHROMEOS)
895 non_native_path =
896 file_manager::util::IsUnderNonNativeLocalPath(GetProfile(), paths[0]);
897 #endif
899 content::BrowserThread::PostTask(
900 content::BrowserThread::FILE,
901 FROM_HERE,
902 base::Bind(
903 &FileSystemChooseEntryFunction::ConfirmDirectoryAccessOnFileThread,
904 this,
905 non_native_path,
906 paths,
907 web_contents));
908 return;
911 OnDirectoryAccessConfirmed(paths);
914 void FileSystemChooseEntryFunction::FileSelectionCanceled() {
915 error_ = kUserCancelled;
916 SendResponse(false);
919 void FileSystemChooseEntryFunction::ConfirmDirectoryAccessOnFileThread(
920 bool non_native_path,
921 const std::vector<base::FilePath>& paths,
922 content::WebContents* web_contents) {
923 const base::FilePath check_path =
924 non_native_path ? paths[0] : base::MakeAbsoluteFilePath(paths[0]);
925 if (check_path.empty()) {
926 content::BrowserThread::PostTask(
927 content::BrowserThread::UI,
928 FROM_HERE,
929 base::Bind(&FileSystemChooseEntryFunction::FileSelectionCanceled,
930 this));
931 return;
934 for (size_t i = 0; i < arraysize(kGraylistedPaths); i++) {
935 base::FilePath graylisted_path;
936 if (PathService::Get(kGraylistedPaths[i], &graylisted_path) &&
937 (check_path == graylisted_path ||
938 check_path.IsParent(graylisted_path))) {
939 if (g_skip_directory_confirmation_for_test) {
940 if (g_allow_directory_access_for_test) {
941 break;
942 } else {
943 content::BrowserThread::PostTask(
944 content::BrowserThread::UI,
945 FROM_HERE,
946 base::Bind(&FileSystemChooseEntryFunction::FileSelectionCanceled,
947 this));
949 return;
952 content::BrowserThread::PostTask(
953 content::BrowserThread::UI,
954 FROM_HERE,
955 base::Bind(
956 CreateDirectoryAccessConfirmationDialog,
957 app_file_handler_util::HasFileSystemWritePermission(
958 extension_.get()),
959 base::UTF8ToUTF16(extension_->name()),
960 web_contents,
961 base::Bind(
962 &FileSystemChooseEntryFunction::OnDirectoryAccessConfirmed,
963 this,
964 paths),
965 base::Bind(&FileSystemChooseEntryFunction::FileSelectionCanceled,
966 this)));
967 return;
971 content::BrowserThread::PostTask(
972 content::BrowserThread::UI,
973 FROM_HERE,
974 base::Bind(&FileSystemChooseEntryFunction::OnDirectoryAccessConfirmed,
975 this, paths));
978 void FileSystemChooseEntryFunction::OnDirectoryAccessConfirmed(
979 const std::vector<base::FilePath>& paths) {
980 if (app_file_handler_util::HasFileSystemWritePermission(extension_.get())) {
981 PrepareFilesForWritableApp(paths);
982 return;
985 // Don't need to check the file, it's for reading.
986 RegisterFileSystemsAndSendResponse(paths);
989 void FileSystemChooseEntryFunction::BuildFileTypeInfo(
990 ui::SelectFileDialog::FileTypeInfo* file_type_info,
991 const base::FilePath::StringType& suggested_extension,
992 const AcceptOptions* accepts,
993 const bool* acceptsAllTypes) {
994 file_type_info->include_all_files = true;
995 if (acceptsAllTypes)
996 file_type_info->include_all_files = *acceptsAllTypes;
998 bool need_suggestion = !file_type_info->include_all_files &&
999 !suggested_extension.empty();
1001 if (accepts) {
1002 typedef file_system::AcceptOption AcceptOption;
1003 for (std::vector<linked_ptr<AcceptOption> >::const_iterator iter =
1004 accepts->begin(); iter != accepts->end(); ++iter) {
1005 base::string16 description;
1006 std::vector<base::FilePath::StringType> extensions;
1008 if (!GetFileTypesFromAcceptOption(**iter, &extensions, &description))
1009 continue; // No extensions were found.
1011 file_type_info->extensions.push_back(extensions);
1012 file_type_info->extension_description_overrides.push_back(description);
1014 // If we still need to find suggested_extension, hunt for it inside the
1015 // extensions returned from GetFileTypesFromAcceptOption.
1016 if (need_suggestion && std::find(extensions.begin(),
1017 extensions.end(), suggested_extension) != extensions.end()) {
1018 need_suggestion = false;
1023 // If there's nothing in our accepted extension list or we couldn't find the
1024 // suggested extension required, then default to accepting all types.
1025 if (file_type_info->extensions.empty() || need_suggestion)
1026 file_type_info->include_all_files = true;
1029 void FileSystemChooseEntryFunction::BuildSuggestion(
1030 const std::string *opt_name,
1031 base::FilePath* suggested_name,
1032 base::FilePath::StringType* suggested_extension) {
1033 if (opt_name) {
1034 *suggested_name = base::FilePath::FromUTF8Unsafe(*opt_name);
1036 // Don't allow any path components; shorten to the base name. This should
1037 // result in a relative path, but in some cases may not. Clear the
1038 // suggestion for safety if this is the case.
1039 *suggested_name = suggested_name->BaseName();
1040 if (suggested_name->IsAbsolute())
1041 *suggested_name = base::FilePath();
1043 *suggested_extension = suggested_name->Extension();
1044 if (!suggested_extension->empty())
1045 suggested_extension->erase(suggested_extension->begin()); // drop the .
1049 bool FileSystemChooseEntryFunction::RunAsync() {
1050 scoped_ptr<ChooseEntry::Params> params(ChooseEntry::Params::Create(*args_));
1051 EXTENSION_FUNCTION_VALIDATE(params.get());
1053 base::FilePath suggested_name;
1054 ui::SelectFileDialog::FileTypeInfo file_type_info;
1055 ui::SelectFileDialog::Type picker_type =
1056 ui::SelectFileDialog::SELECT_OPEN_FILE;
1058 file_system::ChooseEntryOptions* options = params->options.get();
1059 if (options) {
1060 multiple_ = options->accepts_multiple;
1061 if (multiple_)
1062 picker_type = ui::SelectFileDialog::SELECT_OPEN_MULTI_FILE;
1064 if (options->type == file_system::CHOOSE_ENTRY_TYPE_OPENWRITABLEFILE &&
1065 !app_file_handler_util::HasFileSystemWritePermission(
1066 extension_.get())) {
1067 error_ = kRequiresFileSystemWriteError;
1068 return false;
1069 } else if (options->type == file_system::CHOOSE_ENTRY_TYPE_SAVEFILE) {
1070 if (!app_file_handler_util::HasFileSystemWritePermission(
1071 extension_.get())) {
1072 error_ = kRequiresFileSystemWriteError;
1073 return false;
1075 if (multiple_) {
1076 error_ = kMultipleUnsupportedError;
1077 return false;
1079 picker_type = ui::SelectFileDialog::SELECT_SAVEAS_FILE;
1080 } else if (options->type == file_system::CHOOSE_ENTRY_TYPE_OPENDIRECTORY) {
1081 is_directory_ = true;
1082 if (!extension_->permissions_data()->HasAPIPermission(
1083 APIPermission::kFileSystemDirectory)) {
1084 error_ = kRequiresFileSystemDirectoryError;
1085 return false;
1087 if (multiple_) {
1088 error_ = kMultipleUnsupportedError;
1089 return false;
1091 picker_type = ui::SelectFileDialog::SELECT_FOLDER;
1094 base::FilePath::StringType suggested_extension;
1095 BuildSuggestion(options->suggested_name.get(), &suggested_name,
1096 &suggested_extension);
1098 BuildFileTypeInfo(&file_type_info, suggested_extension,
1099 options->accepts.get(), options->accepts_all_types.get());
1102 file_type_info.support_drive = true;
1104 base::FilePath previous_path = file_system_api::GetLastChooseEntryDirectory(
1105 ExtensionPrefs::Get(GetProfile()), extension()->id());
1107 content::BrowserThread::PostTaskAndReply(
1108 content::BrowserThread::FILE,
1109 FROM_HERE,
1110 base::Bind(&FileSystemChooseEntryFunction::SetInitialPathOnFileThread,
1111 this,
1112 suggested_name,
1113 previous_path),
1114 base::Bind(&FileSystemChooseEntryFunction::ShowPicker,
1115 this,
1116 file_type_info,
1117 picker_type));
1118 return true;
1121 bool FileSystemRetainEntryFunction::RunAsync() {
1122 std::string entry_id;
1123 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &entry_id));
1124 SavedFilesService* saved_files_service = SavedFilesService::Get(GetProfile());
1125 // Add the file to the retain list if it is not already on there.
1126 if (!saved_files_service->IsRegistered(extension_->id(), entry_id)) {
1127 std::string filesystem_name;
1128 std::string filesystem_path;
1129 base::FilePath path;
1130 EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &filesystem_name));
1131 EXTENSION_FUNCTION_VALIDATE(args_->GetString(2, &filesystem_path));
1132 if (!app_file_handler_util::ValidateFileEntryAndGetPath(
1133 filesystem_name, filesystem_path,
1134 render_frame_host()->GetProcess()->GetID(), &path, &error_)) {
1135 return false;
1138 std::string filesystem_id;
1139 if (!storage::CrackIsolatedFileSystemName(filesystem_name, &filesystem_id))
1140 return false;
1142 const GURL site = util::GetSiteForExtensionId(extension_id(), GetProfile());
1143 storage::FileSystemContext* const context =
1144 content::BrowserContext::GetStoragePartitionForSite(GetProfile(), site)
1145 ->GetFileSystemContext();
1146 const storage::FileSystemURL url = context->CreateCrackedFileSystemURL(
1147 site,
1148 storage::kFileSystemTypeIsolated,
1149 IsolatedContext::GetInstance()
1150 ->CreateVirtualRootPath(filesystem_id)
1151 .Append(base::FilePath::FromUTF8Unsafe(filesystem_path)));
1153 content::BrowserThread::PostTask(
1154 content::BrowserThread::IO,
1155 FROM_HERE,
1156 base::Bind(
1157 base::IgnoreResult(
1158 &storage::FileSystemOperationRunner::GetMetadata),
1159 context->operation_runner()->AsWeakPtr(),
1160 url,
1161 base::Bind(
1162 &PassFileInfoToUIThread,
1163 base::Bind(&FileSystemRetainEntryFunction::RetainFileEntry,
1164 this,
1165 entry_id,
1166 path))));
1167 return true;
1170 saved_files_service->EnqueueFileEntry(extension_->id(), entry_id);
1171 SendResponse(true);
1172 return true;
1175 void FileSystemRetainEntryFunction::RetainFileEntry(
1176 const std::string& entry_id,
1177 const base::FilePath& path,
1178 scoped_ptr<base::File::Info> file_info) {
1179 if (!file_info) {
1180 SendResponse(false);
1181 return;
1184 SavedFilesService* saved_files_service = SavedFilesService::Get(GetProfile());
1185 saved_files_service->RegisterFileEntry(
1186 extension_->id(), entry_id, path, file_info->is_directory);
1187 saved_files_service->EnqueueFileEntry(extension_->id(), entry_id);
1188 SendResponse(true);
1191 bool FileSystemIsRestorableFunction::RunSync() {
1192 std::string entry_id;
1193 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &entry_id));
1194 SetResult(new base::FundamentalValue(SavedFilesService::Get(
1195 GetProfile())->IsRegistered(extension_->id(), entry_id)));
1196 return true;
1199 bool FileSystemRestoreEntryFunction::RunAsync() {
1200 std::string entry_id;
1201 bool needs_new_entry;
1202 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &entry_id));
1203 EXTENSION_FUNCTION_VALIDATE(args_->GetBoolean(1, &needs_new_entry));
1204 const SavedFileEntry* file_entry = SavedFilesService::Get(
1205 GetProfile())->GetFileEntry(extension_->id(), entry_id);
1206 if (!file_entry) {
1207 error_ = kUnknownIdError;
1208 return false;
1211 SavedFilesService::Get(GetProfile())
1212 ->EnqueueFileEntry(extension_->id(), entry_id);
1214 // Only create a new file entry if the renderer requests one.
1215 // |needs_new_entry| will be false if the renderer already has an Entry for
1216 // |entry_id|.
1217 if (needs_new_entry) {
1218 is_directory_ = file_entry->is_directory;
1219 CreateResponse();
1220 AddEntryToResponse(file_entry->path, file_entry->id);
1222 SendResponse(true);
1223 return true;
1226 bool FileSystemObserveDirectoryFunction::RunSync() {
1227 NOTIMPLEMENTED();
1228 error_ = kUnknownIdError;
1229 return false;
1232 bool FileSystemUnobserveEntryFunction::RunSync() {
1233 NOTIMPLEMENTED();
1234 error_ = kUnknownIdError;
1235 return false;
1238 bool FileSystemGetObservedEntriesFunction::RunSync() {
1239 NOTIMPLEMENTED();
1240 error_ = kUnknownIdError;
1241 return false;
1244 #if !defined(OS_CHROMEOS)
1245 ExtensionFunction::ResponseAction FileSystemRequestFileSystemFunction::Run() {
1246 using api::file_system::RequestFileSystem::Params;
1247 const scoped_ptr<Params> params(Params::Create(*args_));
1248 EXTENSION_FUNCTION_VALIDATE(params);
1250 NOTIMPLEMENTED();
1251 return RespondNow(Error(kNotSupportedOnCurrentPlatformError));
1254 ExtensionFunction::ResponseAction FileSystemGetVolumeListFunction::Run() {
1255 NOTIMPLEMENTED();
1256 return RespondNow(Error(kNotSupportedOnCurrentPlatformError));
1258 #else
1260 FileSystemRequestFileSystemFunction::FileSystemRequestFileSystemFunction()
1261 : chrome_details_(this) {
1264 FileSystemRequestFileSystemFunction::~FileSystemRequestFileSystemFunction() {
1267 ExtensionFunction::ResponseAction FileSystemRequestFileSystemFunction::Run() {
1268 using api::file_system::RequestFileSystem::Params;
1269 const scoped_ptr<Params> params(Params::Create(*args_));
1270 EXTENSION_FUNCTION_VALIDATE(params);
1272 // Only kiosk apps in kiosk sessions can use this API.
1273 // Additionally it is enabled for whitelisted component extensions and apps.
1274 file_system_api::ConsentProviderDelegate consent_provider_delegate(
1275 chrome_details_.GetProfile(), render_frame_host());
1276 file_system_api::ConsentProvider consent_provider(&consent_provider_delegate);
1278 if (!consent_provider.IsGrantable(*extension()))
1279 return RespondNow(Error(kNotSupportedOnNonKioskSessionError));
1281 using file_manager::VolumeManager;
1282 using file_manager::Volume;
1283 VolumeManager* const volume_manager =
1284 VolumeManager::Get(chrome_details_.GetProfile());
1285 DCHECK(volume_manager);
1287 const bool writable =
1288 params->options.writable.get() && *params->options.writable.get();
1289 if (writable &&
1290 !app_file_handler_util::HasFileSystemWritePermission(extension_.get())) {
1291 return RespondNow(Error(kRequiresFileSystemWriteError));
1294 base::WeakPtr<file_manager::Volume> volume =
1295 volume_manager->FindVolumeById(params->options.volume_id);
1296 if (!volume.get())
1297 return RespondNow(Error(kVolumeNotFoundError));
1299 const GURL site =
1300 util::GetSiteForExtensionId(extension_id(), chrome_details_.GetProfile());
1301 scoped_refptr<storage::FileSystemContext> file_system_context =
1302 content::BrowserContext::GetStoragePartitionForSite(
1303 chrome_details_.GetProfile(), site)->GetFileSystemContext();
1304 storage::ExternalFileSystemBackend* const backend =
1305 file_system_context->external_backend();
1306 DCHECK(backend);
1308 base::FilePath virtual_path;
1309 if (!backend->GetVirtualPath(volume->mount_path(), &virtual_path))
1310 return RespondNow(Error(kSecurityError));
1312 if (writable && (volume->is_read_only()))
1313 return RespondNow(Error(kSecurityError));
1315 consent_provider.RequestConsent(
1316 *extension(), volume, writable,
1317 base::Bind(&FileSystemRequestFileSystemFunction::OnConsentReceived, this,
1318 volume, writable));
1319 return RespondLater();
1322 void FileSystemRequestFileSystemFunction::OnConsentReceived(
1323 const base::WeakPtr<file_manager::Volume>& volume,
1324 bool writable,
1325 ConsentProvider::Consent result) {
1326 using file_manager::VolumeManager;
1327 using file_manager::Volume;
1329 switch (result) {
1330 case ConsentProvider::CONSENT_REJECTED:
1331 SetError(kSecurityError);
1332 SendResponse(false);
1333 return;
1335 case ConsentProvider::CONSENT_IMPOSSIBLE:
1336 SetError(kConsentImpossible);
1337 SendResponse(false);
1338 return;
1340 case ConsentProvider::CONSENT_GRANTED:
1341 break;
1344 if (!volume.get()) {
1345 SetError(kVolumeNotFoundError);
1346 SendResponse(false);
1347 return;
1350 const GURL site =
1351 util::GetSiteForExtensionId(extension_id(), chrome_details_.GetProfile());
1352 scoped_refptr<storage::FileSystemContext> file_system_context =
1353 content::BrowserContext::GetStoragePartitionForSite(
1354 chrome_details_.GetProfile(), site)->GetFileSystemContext();
1355 storage::ExternalFileSystemBackend* const backend =
1356 file_system_context->external_backend();
1357 DCHECK(backend);
1359 base::FilePath virtual_path;
1360 if (!backend->GetVirtualPath(volume->mount_path(), &virtual_path)) {
1361 SetError(kSecurityError);
1362 SendResponse(false);
1363 return;
1366 storage::IsolatedContext* const isolated_context =
1367 storage::IsolatedContext::GetInstance();
1368 DCHECK(isolated_context);
1370 const storage::FileSystemURL original_url =
1371 file_system_context->CreateCrackedFileSystemURL(
1372 GURL(std::string(kExtensionScheme) + url::kStandardSchemeSeparator +
1373 extension_id()),
1374 storage::kFileSystemTypeExternal, virtual_path);
1376 // Set a fixed register name, as the automatic one would leak the mount point
1377 // directory.
1378 std::string register_name = "fs";
1379 const std::string file_system_id =
1380 isolated_context->RegisterFileSystemForPath(
1381 storage::kFileSystemTypeNativeForPlatformApp,
1382 std::string() /* file_system_id */, original_url.path(),
1383 &register_name);
1384 if (file_system_id.empty()) {
1385 SetError(kSecurityError);
1386 SendResponse(false);
1387 return;
1390 backend->GrantFileAccessToExtension(extension_->id(), virtual_path);
1392 // Grant file permissions to the renderer hosting component.
1393 content::ChildProcessSecurityPolicy* policy =
1394 content::ChildProcessSecurityPolicy::GetInstance();
1395 DCHECK(policy);
1397 // Read-only permisisons.
1398 policy->GrantReadFile(render_frame_host()->GetProcess()->GetID(),
1399 volume->mount_path());
1400 policy->GrantReadFileSystem(render_frame_host()->GetProcess()->GetID(),
1401 file_system_id);
1403 // Additional write permissions.
1404 if (writable) {
1405 policy->GrantCreateReadWriteFile(render_frame_host()->GetProcess()->GetID(),
1406 volume->mount_path());
1407 policy->GrantCopyInto(render_frame_host()->GetProcess()->GetID(),
1408 volume->mount_path());
1409 policy->GrantWriteFileSystem(render_frame_host()->GetProcess()->GetID(),
1410 file_system_id);
1411 policy->GrantDeleteFromFileSystem(
1412 render_frame_host()->GetProcess()->GetID(), file_system_id);
1413 policy->GrantCreateFileForFileSystem(
1414 render_frame_host()->GetProcess()->GetID(), file_system_id);
1417 base::DictionaryValue* const dict = new base::DictionaryValue();
1418 dict->SetString("file_system_id", file_system_id);
1419 dict->SetString("file_system_path", register_name);
1421 SetResult(dict);
1422 SendResponse(true);
1425 FileSystemGetVolumeListFunction::FileSystemGetVolumeListFunction()
1426 : chrome_details_(this) {
1429 FileSystemGetVolumeListFunction::~FileSystemGetVolumeListFunction() {
1432 ExtensionFunction::ResponseAction FileSystemGetVolumeListFunction::Run() {
1433 // Only kiosk apps in kiosk sessions can use this API.
1434 // Additionally it is enabled for whitelisted component extensions and apps.
1435 file_system_api::ConsentProviderDelegate consent_provider_delegate(
1436 chrome_details_.GetProfile(), render_frame_host());
1437 file_system_api::ConsentProvider consent_provider(&consent_provider_delegate);
1439 if (!consent_provider.IsGrantable(*extension()))
1440 return RespondNow(Error(kNotSupportedOnNonKioskSessionError));
1441 using api::file_system::Volume;
1442 std::vector<linked_ptr<Volume>> result_volume_list;
1443 FillVolumeList(chrome_details_.GetProfile(), &result_volume_list);
1445 return RespondNow(
1446 ArgumentList(api::file_system::GetVolumeList::Results::Create(
1447 result_volume_list).Pass()));
1449 #endif
1451 } // namespace extensions