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 "apps/app_window.h"
8 #include "apps/app_window_registry.h"
9 #include "apps/saved_files_service.h"
10 #include "base/bind.h"
11 #include "base/file_util.h"
12 #include "base/files/file_path.h"
13 #include "base/logging.h"
14 #include "base/path_service.h"
15 #include "base/strings/string_util.h"
16 #include "base/strings/stringprintf.h"
17 #include "base/strings/sys_string_conversions.h"
18 #include "base/strings/utf_string_conversions.h"
19 #include "base/value_conversions.h"
20 #include "base/values.h"
21 #include "chrome/browser/extensions/api/file_handlers/app_file_handler_util.h"
22 #include "chrome/browser/extensions/extension_service.h"
23 #include "chrome/browser/platform_util.h"
24 #include "chrome/browser/profiles/profile.h"
25 #include "chrome/browser/ui/apps/directory_access_confirmation_dialog.h"
26 #include "chrome/browser/ui/chrome_select_file_policy.h"
27 #include "chrome/common/chrome_paths.h"
28 #include "chrome/common/extensions/api/file_system.h"
29 #include "content/public/browser/browser_thread.h"
30 #include "content/public/browser/child_process_security_policy.h"
31 #include "content/public/browser/render_process_host.h"
32 #include "content/public/browser/render_view_host.h"
33 #include "content/public/browser/web_contents.h"
34 #include "content/public/browser/web_contents_view.h"
35 #include "extensions/browser/extension_system.h"
36 #include "extensions/common/permissions/api_permission.h"
37 #include "grit/generated_resources.h"
38 #include "net/base/mime_util.h"
39 #include "ui/base/l10n/l10n_util.h"
40 #include "ui/shell_dialogs/select_file_dialog.h"
41 #include "ui/shell_dialogs/selected_file_info.h"
42 #include "webkit/browser/fileapi/external_mount_points.h"
43 #include "webkit/browser/fileapi/isolated_context.h"
44 #include "webkit/common/fileapi/file_system_types.h"
45 #include "webkit/common/fileapi/file_system_util.h"
47 #if defined(OS_MACOSX)
48 #include <CoreFoundation/CoreFoundation.h>
49 #include "base/mac/foundation_util.h"
52 #if defined(OS_CHROMEOS)
53 #include "chrome/browser/chromeos/drive/file_system_util.h"
56 using apps::SavedFileEntry;
57 using apps::SavedFilesService;
58 using apps::AppWindow;
59 using fileapi::IsolatedContext;
61 const char kInvalidCallingPage[] = "Invalid calling page. This function can't "
62 "be called from a background page.";
63 const char kUserCancelled[] = "User cancelled";
64 const char kWritableFileErrorFormat[] = "Error opening %s";
65 const char kRequiresFileSystemWriteError[] =
66 "Operation requires fileSystem.write permission";
67 const char kRequiresFileSystemDirectoryError[] =
68 "Operation requires fileSystem.directory permission";
69 const char kMultipleUnsupportedError[] =
70 "acceptsMultiple: true is not supported for 'saveFile'";
71 const char kUnknownIdError[] = "Unknown id";
73 namespace file_system = extensions::api::file_system;
74 namespace ChooseEntry = file_system::ChooseEntry;
78 #if defined(OS_MACOSX)
79 // Retrieves the localized display name for the base name of the given path.
80 // If the path is not localized, this will just return the base name.
81 std::string GetDisplayBaseName(const base::FilePath& path) {
82 base::ScopedCFTypeRef<CFURLRef> url(CFURLCreateFromFileSystemRepresentation(
83 NULL, (const UInt8*)path.value().c_str(), path.value().length(), true));
85 return path.BaseName().value();
88 if (LSCopyDisplayNameForURL(url, &str) != noErr)
89 return path.BaseName().value();
91 std::string result(base::SysCFStringRefToUTF8(str));
96 // Prettifies |source_path| for OS X, by localizing every component of the
97 // path. Additionally, if the path is inside the user's home directory, then
98 // replace the home directory component with "~".
99 base::FilePath PrettifyPath(const base::FilePath& source_path) {
100 base::FilePath home_path;
101 PathService::Get(base::DIR_HOME, &home_path);
102 DCHECK(source_path.IsAbsolute());
104 // Break down the incoming path into components, and grab the display name
105 // for every component. This will match app bundles, ".localized" folders,
106 // and localized subfolders of the user's home directory.
107 // Don't grab the display name of the first component, i.e., "/", as it'll
108 // show up as the HDD name.
109 std::vector<base::FilePath::StringType> components;
110 source_path.GetComponents(&components);
111 base::FilePath display_path = base::FilePath(components[0]);
112 base::FilePath actual_path = display_path;
113 for (std::vector<base::FilePath::StringType>::iterator i =
114 components.begin() + 1; i != components.end(); ++i) {
115 actual_path = actual_path.Append(*i);
116 if (actual_path == home_path) {
117 display_path = base::FilePath("~");
118 home_path = base::FilePath();
121 std::string display = GetDisplayBaseName(actual_path);
122 display_path = display_path.Append(display);
124 DCHECK_EQ(actual_path.value(), source_path.value());
127 #else // defined(OS_MACOSX)
128 // Prettifies |source_path|, by replacing the user's home directory with "~"
130 base::FilePath PrettifyPath(const base::FilePath& source_path) {
131 #if defined(OS_WIN) || defined(OS_POSIX)
133 int home_key = base::DIR_PROFILE;
134 #elif defined(OS_POSIX)
135 int home_key = base::DIR_HOME;
137 base::FilePath home_path;
138 base::FilePath display_path = base::FilePath::FromUTF8Unsafe("~");
139 if (PathService::Get(home_key, &home_path)
140 && home_path.AppendRelativePath(source_path, &display_path))
145 #endif // defined(OS_MACOSX)
147 bool g_skip_picker_for_test = false;
148 bool g_use_suggested_path_for_test = false;
149 base::FilePath* g_path_to_be_picked_for_test;
150 std::vector<base::FilePath>* g_paths_to_be_picked_for_test;
151 bool g_skip_directory_confirmation_for_test = false;
152 bool g_allow_directory_access_for_test = false;
154 // Expand the mime-types and extensions provided in an AcceptOption, returning
155 // them within the passed extension vector. Returns false if no valid types
157 bool GetFileTypesFromAcceptOption(
158 const file_system::AcceptOption& accept_option,
159 std::vector<base::FilePath::StringType>* extensions,
160 base::string16* description) {
161 std::set<base::FilePath::StringType> extension_set;
162 int description_id = 0;
164 if (accept_option.mime_types.get()) {
165 std::vector<std::string>* list = accept_option.mime_types.get();
166 bool valid_type = false;
167 for (std::vector<std::string>::const_iterator iter = list->begin();
168 iter != list->end(); ++iter) {
169 std::vector<base::FilePath::StringType> inner;
170 std::string accept_type = *iter;
171 StringToLowerASCII(&accept_type);
172 net::GetExtensionsForMimeType(accept_type, &inner);
177 description_id = 0; // We already have an accept type with label; if
178 // we find another, give up and use the default.
179 else if (accept_type == "image/*")
180 description_id = IDS_IMAGE_FILES;
181 else if (accept_type == "audio/*")
182 description_id = IDS_AUDIO_FILES;
183 else if (accept_type == "video/*")
184 description_id = IDS_VIDEO_FILES;
186 extension_set.insert(inner.begin(), inner.end());
191 if (accept_option.extensions.get()) {
192 std::vector<std::string>* list = accept_option.extensions.get();
193 for (std::vector<std::string>::const_iterator iter = list->begin();
194 iter != list->end(); ++iter) {
195 std::string extension = *iter;
196 StringToLowerASCII(&extension);
198 extension_set.insert(base::UTF8ToWide(*iter));
200 extension_set.insert(*iter);
205 extensions->assign(extension_set.begin(), extension_set.end());
206 if (extensions->empty())
209 if (accept_option.description.get())
210 *description = base::UTF8ToUTF16(*accept_option.description.get());
211 else if (description_id)
212 *description = l10n_util::GetStringUTF16(description_id);
217 // Key for the path of the directory of the file last chosen by the user in
218 // response to a chrome.fileSystem.chooseEntry() call.
219 const char kLastChooseEntryDirectory[] = "last_choose_file_directory";
221 const int kGraylistedPaths[] = {
224 base::DIR_PROGRAM_FILES,
225 base::DIR_PROGRAM_FILESX86,
227 #elif defined(OS_POSIX)
234 namespace extensions {
236 namespace file_system_api {
238 base::FilePath GetLastChooseEntryDirectory(const ExtensionPrefs* prefs,
239 const std::string& extension_id) {
241 std::string string_path;
242 if (prefs->ReadPrefAsString(extension_id,
243 kLastChooseEntryDirectory,
245 path = base::FilePath::FromUTF8Unsafe(string_path);
250 void SetLastChooseEntryDirectory(ExtensionPrefs* prefs,
251 const std::string& extension_id,
252 const base::FilePath& path) {
253 prefs->UpdateExtensionPref(extension_id,
254 kLastChooseEntryDirectory,
255 base::CreateFilePathValue(path));
258 } // namespace file_system_api
260 bool FileSystemGetDisplayPathFunction::RunImpl() {
261 std::string filesystem_name;
262 std::string filesystem_path;
263 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &filesystem_name));
264 EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &filesystem_path));
266 base::FilePath file_path;
267 if (!app_file_handler_util::ValidateFileEntryAndGetPath(filesystem_name,
274 file_path = PrettifyPath(file_path);
275 SetResult(new base::StringValue(file_path.value()));
279 FileSystemEntryFunction::FileSystemEntryFunction()
281 is_directory_(false),
284 void FileSystemEntryFunction::CheckWritableFiles(
285 const std::vector<base::FilePath>& paths) {
286 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
287 app_file_handler_util::CheckWritableFiles(
291 base::Bind(&FileSystemEntryFunction::RegisterFileSystemsAndSendResponse,
294 base::Bind(&FileSystemEntryFunction::HandleWritableFileError, this));
297 void FileSystemEntryFunction::RegisterFileSystemsAndSendResponse(
298 const std::vector<base::FilePath>& paths) {
299 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
300 if (!render_view_host_)
304 for (std::vector<base::FilePath>::const_iterator it = paths.begin();
305 it != paths.end(); ++it) {
306 AddEntryToResponse(*it, "");
311 void FileSystemEntryFunction::CreateResponse() {
313 response_ = new base::DictionaryValue();
314 base::ListValue* list = new base::ListValue();
315 response_->Set("entries", list);
316 response_->SetBoolean("multiple", multiple_);
317 SetResult(response_);
320 void FileSystemEntryFunction::AddEntryToResponse(
321 const base::FilePath& path,
322 const std::string& id_override) {
324 extensions::app_file_handler_util::GrantedFileEntry file_entry =
325 extensions::app_file_handler_util::CreateFileEntry(
328 render_view_host_->GetProcess()->GetID(),
331 base::ListValue* entries;
332 bool success = response_->GetList("entries", &entries);
335 base::DictionaryValue* entry = new base::DictionaryValue();
336 entry->SetString("fileSystemId", file_entry.filesystem_id);
337 entry->SetString("baseName", file_entry.registered_name);
338 if (id_override.empty())
339 entry->SetString("id", file_entry.id);
341 entry->SetString("id", id_override);
342 entry->SetBoolean("isDirectory", is_directory_);
343 entries->Append(entry);
346 void FileSystemEntryFunction::HandleWritableFileError(
347 const base::FilePath& error_path) {
348 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
349 error_ = base::StringPrintf(kWritableFileErrorFormat,
350 error_path.BaseName().AsUTF8Unsafe().c_str());
354 bool FileSystemGetWritableEntryFunction::RunImpl() {
355 std::string filesystem_name;
356 std::string filesystem_path;
357 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &filesystem_name));
358 EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &filesystem_path));
360 if (!app_file_handler_util::HasFileSystemWritePermission(extension_)) {
361 error_ = kRequiresFileSystemWriteError;
365 if (!app_file_handler_util::ValidateFileEntryAndGetPath(filesystem_name,
372 content::BrowserThread::PostTaskAndReply(
373 content::BrowserThread::FILE,
376 &FileSystemGetWritableEntryFunction::SetIsDirectoryOnFileThread,
379 &FileSystemGetWritableEntryFunction::CheckPermissionAndSendResponse,
384 void FileSystemGetWritableEntryFunction::CheckPermissionAndSendResponse() {
385 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
387 !extension_->HasAPIPermission(APIPermission::kFileSystemDirectory)) {
388 error_ = kRequiresFileSystemDirectoryError;
391 std::vector<base::FilePath> paths;
392 paths.push_back(path_);
393 CheckWritableFiles(paths);
396 void FileSystemGetWritableEntryFunction::SetIsDirectoryOnFileThread() {
397 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
398 if (base::DirectoryExists(path_)) {
399 is_directory_ = true;
403 bool FileSystemIsWritableEntryFunction::RunImpl() {
404 std::string filesystem_name;
405 std::string filesystem_path;
406 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &filesystem_name));
407 EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &filesystem_path));
409 std::string filesystem_id;
410 if (!fileapi::CrackIsolatedFileSystemName(filesystem_name, &filesystem_id)) {
411 error_ = app_file_handler_util::kInvalidParameters;
415 content::ChildProcessSecurityPolicy* policy =
416 content::ChildProcessSecurityPolicy::GetInstance();
417 int renderer_id = render_view_host_->GetProcess()->GetID();
418 bool is_writable = policy->CanReadWriteFileSystem(renderer_id,
421 SetResult(new base::FundamentalValue(is_writable));
425 // Handles showing a dialog to the user to ask for the filename for a file to
427 class FileSystemChooseEntryFunction::FilePicker
428 : public ui::SelectFileDialog::Listener {
430 FilePicker(FileSystemChooseEntryFunction* function,
431 content::WebContents* web_contents,
432 const base::FilePath& suggested_name,
433 const ui::SelectFileDialog::FileTypeInfo& file_type_info,
434 ui::SelectFileDialog::Type picker_type)
435 : function_(function) {
436 select_file_dialog_ = ui::SelectFileDialog::Create(
437 this, new ChromeSelectFilePolicy(web_contents));
438 gfx::NativeWindow owning_window = web_contents ?
439 platform_util::GetTopLevel(web_contents->GetView()->GetNativeView()) :
442 if (g_skip_picker_for_test) {
443 if (g_use_suggested_path_for_test) {
444 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
446 &FileSystemChooseEntryFunction::FilePicker::FileSelected,
447 base::Unretained(this), suggested_name, 1,
448 static_cast<void*>(NULL)));
449 } else if (g_path_to_be_picked_for_test) {
450 content::BrowserThread::PostTask(
451 content::BrowserThread::UI, FROM_HERE,
453 &FileSystemChooseEntryFunction::FilePicker::FileSelected,
454 base::Unretained(this), *g_path_to_be_picked_for_test, 1,
455 static_cast<void*>(NULL)));
456 } else if (g_paths_to_be_picked_for_test) {
457 content::BrowserThread::PostTask(
458 content::BrowserThread::UI,
461 &FileSystemChooseEntryFunction::FilePicker::MultiFilesSelected,
462 base::Unretained(this),
463 *g_paths_to_be_picked_for_test,
464 static_cast<void*>(NULL)));
466 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
468 &FileSystemChooseEntryFunction::FilePicker::
469 FileSelectionCanceled,
470 base::Unretained(this), static_cast<void*>(NULL)));
475 select_file_dialog_->SelectFile(picker_type,
480 base::FilePath::StringType(),
485 virtual ~FilePicker() {}
488 // ui::SelectFileDialog::Listener implementation.
489 virtual void FileSelected(const base::FilePath& path,
491 void* params) OVERRIDE {
492 std::vector<base::FilePath> paths;
493 paths.push_back(path);
494 MultiFilesSelected(paths, params);
497 virtual void FileSelectedWithExtraInfo(const ui::SelectedFileInfo& file,
499 void* params) OVERRIDE {
500 // Normally, file.local_path is used because it is a native path to the
501 // local read-only cached file in the case of remote file system like
502 // Chrome OS's Google Drive integration. Here, however, |file.file_path| is
503 // necessary because we need to create a FileEntry denoting the remote file,
504 // not its cache. On other platforms than Chrome OS, they are the same.
506 // TODO(kinaba): remove this, once after the file picker implements proper
507 // switch of the path treatment depending on the |support_drive| flag.
508 FileSelected(file.file_path, index, params);
511 virtual void MultiFilesSelected(const std::vector<base::FilePath>& files,
512 void* params) OVERRIDE {
513 function_->FilesSelected(files);
517 virtual void MultiFilesSelectedWithExtraInfo(
518 const std::vector<ui::SelectedFileInfo>& files,
519 void* params) OVERRIDE {
520 std::vector<base::FilePath> paths;
521 for (std::vector<ui::SelectedFileInfo>::const_iterator it = files.begin();
522 it != files.end(); ++it) {
523 paths.push_back(it->file_path);
525 MultiFilesSelected(paths, params);
528 virtual void FileSelectionCanceled(void* params) OVERRIDE {
529 function_->FileSelectionCanceled();
533 scoped_refptr<ui::SelectFileDialog> select_file_dialog_;
534 scoped_refptr<FileSystemChooseEntryFunction> function_;
536 DISALLOW_COPY_AND_ASSIGN(FilePicker);
539 void FileSystemChooseEntryFunction::ShowPicker(
540 const ui::SelectFileDialog::FileTypeInfo& file_type_info,
541 ui::SelectFileDialog::Type picker_type) {
542 // TODO(asargent/benwells) - As a short term remediation for crbug.com/179010
543 // we're adding the ability for a whitelisted extension to use this API since
544 // chrome.fileBrowserHandler.selectFile is ChromeOS-only. Eventually we'd
545 // like a better solution and likely this code will go back to being
546 // platform-app only.
547 content::WebContents* web_contents = NULL;
548 if (extension_->is_platform_app()) {
549 apps::AppWindowRegistry* registry =
550 apps::AppWindowRegistry::Get(GetProfile());
552 AppWindow* app_window =
553 registry->GetAppWindowForRenderViewHost(render_view_host());
555 error_ = kInvalidCallingPage;
559 web_contents = app_window->web_contents();
561 web_contents = GetAssociatedWebContents();
563 // The file picker will hold a reference to this function instance, preventing
564 // its destruction (and subsequent sending of the function response) until the
565 // user has selected a file or cancelled the picker. At that point, the picker
566 // will delete itself, which will also free the function instance.
568 this, web_contents, initial_path_, file_type_info, picker_type);
572 void FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathForTest(
573 base::FilePath* path) {
574 g_skip_picker_for_test = true;
575 g_use_suggested_path_for_test = false;
576 g_path_to_be_picked_for_test = path;
577 g_paths_to_be_picked_for_test = NULL;
580 void FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathsForTest(
581 std::vector<base::FilePath>* paths) {
582 g_skip_picker_for_test = true;
583 g_use_suggested_path_for_test = false;
584 g_paths_to_be_picked_for_test = paths;
588 void FileSystemChooseEntryFunction::SkipPickerAndSelectSuggestedPathForTest() {
589 g_skip_picker_for_test = true;
590 g_use_suggested_path_for_test = true;
591 g_path_to_be_picked_for_test = NULL;
592 g_paths_to_be_picked_for_test = NULL;
596 void FileSystemChooseEntryFunction::SkipPickerAndAlwaysCancelForTest() {
597 g_skip_picker_for_test = true;
598 g_use_suggested_path_for_test = false;
599 g_path_to_be_picked_for_test = NULL;
600 g_paths_to_be_picked_for_test = NULL;
604 void FileSystemChooseEntryFunction::StopSkippingPickerForTest() {
605 g_skip_picker_for_test = false;
609 void FileSystemChooseEntryFunction::SkipDirectoryConfirmationForTest() {
610 g_skip_directory_confirmation_for_test = true;
611 g_allow_directory_access_for_test = true;
615 void FileSystemChooseEntryFunction::AutoCancelDirectoryConfirmationForTest() {
616 g_skip_directory_confirmation_for_test = true;
617 g_allow_directory_access_for_test = false;
621 void FileSystemChooseEntryFunction::StopSkippingDirectoryConfirmationForTest() {
622 g_skip_directory_confirmation_for_test = false;
626 void FileSystemChooseEntryFunction::RegisterTempExternalFileSystemForTest(
627 const std::string& name, const base::FilePath& path) {
628 // For testing on Chrome OS, where to deal with remote and local paths
629 // smoothly, all accessed paths need to be registered in the list of
630 // external mount points.
631 fileapi::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
633 fileapi::kFileSystemTypeNativeLocal,
634 fileapi::FileSystemMountOption(),
638 void FileSystemChooseEntryFunction::SetInitialPathOnFileThread(
639 const base::FilePath& suggested_name,
640 const base::FilePath& previous_path) {
641 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
642 if (!previous_path.empty() && base::DirectoryExists(previous_path)) {
643 initial_path_ = previous_path.Append(suggested_name);
645 base::FilePath documents_dir;
646 if (PathService::Get(chrome::DIR_USER_DOCUMENTS, &documents_dir)) {
647 initial_path_ = documents_dir.Append(suggested_name);
649 initial_path_ = suggested_name;
654 void FileSystemChooseEntryFunction::FilesSelected(
655 const std::vector<base::FilePath>& paths) {
656 DCHECK(!paths.empty());
657 base::FilePath last_choose_directory;
659 last_choose_directory = paths[0];
661 last_choose_directory = paths[0].DirName();
663 file_system_api::SetLastChooseEntryDirectory(
664 ExtensionPrefs::Get(GetProfile()),
665 GetExtension()->id(),
666 last_choose_directory);
668 // Get the WebContents for the app window to be the parent window of the
669 // confirmation dialog if necessary.
670 apps::AppWindowRegistry* registry =
671 apps::AppWindowRegistry::Get(GetProfile());
673 AppWindow* app_window =
674 registry->GetAppWindowForRenderViewHost(render_view_host());
676 error_ = kInvalidCallingPage;
680 content::WebContents* web_contents = app_window->web_contents();
682 content::BrowserThread::PostTask(
683 content::BrowserThread::FILE,
686 &FileSystemChooseEntryFunction::ConfirmDirectoryAccessOnFileThread,
693 OnDirectoryAccessConfirmed(paths);
696 void FileSystemChooseEntryFunction::FileSelectionCanceled() {
697 error_ = kUserCancelled;
701 void FileSystemChooseEntryFunction::ConfirmDirectoryAccessOnFileThread(
702 const std::vector<base::FilePath>& paths,
703 content::WebContents* web_contents) {
704 DCHECK_EQ(paths.size(), 1u);
705 #if defined(OS_CHROMEOS)
706 const base::FilePath path =
707 drive::util::IsUnderDriveMountPoint(paths[0]) ? paths[0] :
708 base::MakeAbsoluteFilePath(paths[0]);
710 const base::FilePath path = base::MakeAbsoluteFilePath(paths[0]);
713 content::BrowserThread::PostTask(
714 content::BrowserThread::UI,
716 base::Bind(&FileSystemChooseEntryFunction::FileSelectionCanceled,
721 for (size_t i = 0; i < arraysize(kGraylistedPaths); i++) {
722 base::FilePath graylisted_path;
723 if (PathService::Get(kGraylistedPaths[i], &graylisted_path) &&
724 (path == graylisted_path || path.IsParent(graylisted_path))) {
725 if (g_skip_directory_confirmation_for_test) {
726 if (g_allow_directory_access_for_test) {
729 content::BrowserThread::PostTask(
730 content::BrowserThread::UI,
732 base::Bind(&FileSystemChooseEntryFunction::FileSelectionCanceled,
738 content::BrowserThread::PostTask(
739 content::BrowserThread::UI,
742 CreateDirectoryAccessConfirmationDialog,
743 app_file_handler_util::HasFileSystemWritePermission(extension_),
744 base::UTF8ToUTF16(extension_->name()),
747 &FileSystemChooseEntryFunction::OnDirectoryAccessConfirmed,
750 base::Bind(&FileSystemChooseEntryFunction::FileSelectionCanceled,
756 content::BrowserThread::PostTask(
757 content::BrowserThread::UI,
759 base::Bind(&FileSystemChooseEntryFunction::OnDirectoryAccessConfirmed,
763 void FileSystemChooseEntryFunction::OnDirectoryAccessConfirmed(
764 const std::vector<base::FilePath>& paths) {
765 if (app_file_handler_util::HasFileSystemWritePermission(extension_)) {
766 CheckWritableFiles(paths);
770 // Don't need to check the file, it's for reading.
771 RegisterFileSystemsAndSendResponse(paths);
774 void FileSystemChooseEntryFunction::BuildFileTypeInfo(
775 ui::SelectFileDialog::FileTypeInfo* file_type_info,
776 const base::FilePath::StringType& suggested_extension,
777 const AcceptOptions* accepts,
778 const bool* acceptsAllTypes) {
779 file_type_info->include_all_files = true;
781 file_type_info->include_all_files = *acceptsAllTypes;
783 bool need_suggestion = !file_type_info->include_all_files &&
784 !suggested_extension.empty();
787 typedef file_system::AcceptOption AcceptOption;
788 for (std::vector<linked_ptr<AcceptOption> >::const_iterator iter =
789 accepts->begin(); iter != accepts->end(); ++iter) {
790 base::string16 description;
791 std::vector<base::FilePath::StringType> extensions;
793 if (!GetFileTypesFromAcceptOption(**iter, &extensions, &description))
794 continue; // No extensions were found.
796 file_type_info->extensions.push_back(extensions);
797 file_type_info->extension_description_overrides.push_back(description);
799 // If we still need to find suggested_extension, hunt for it inside the
800 // extensions returned from GetFileTypesFromAcceptOption.
801 if (need_suggestion && std::find(extensions.begin(),
802 extensions.end(), suggested_extension) != extensions.end()) {
803 need_suggestion = false;
808 // If there's nothing in our accepted extension list or we couldn't find the
809 // suggested extension required, then default to accepting all types.
810 if (file_type_info->extensions.empty() || need_suggestion)
811 file_type_info->include_all_files = true;
814 void FileSystemChooseEntryFunction::BuildSuggestion(
815 const std::string *opt_name,
816 base::FilePath* suggested_name,
817 base::FilePath::StringType* suggested_extension) {
819 *suggested_name = base::FilePath::FromUTF8Unsafe(*opt_name);
821 // Don't allow any path components; shorten to the base name. This should
822 // result in a relative path, but in some cases may not. Clear the
823 // suggestion for safety if this is the case.
824 *suggested_name = suggested_name->BaseName();
825 if (suggested_name->IsAbsolute())
826 *suggested_name = base::FilePath();
828 *suggested_extension = suggested_name->Extension();
829 if (!suggested_extension->empty())
830 suggested_extension->erase(suggested_extension->begin()); // drop the .
834 bool FileSystemChooseEntryFunction::RunImpl() {
835 scoped_ptr<ChooseEntry::Params> params(ChooseEntry::Params::Create(*args_));
836 EXTENSION_FUNCTION_VALIDATE(params.get());
838 base::FilePath suggested_name;
839 ui::SelectFileDialog::FileTypeInfo file_type_info;
840 ui::SelectFileDialog::Type picker_type =
841 ui::SelectFileDialog::SELECT_OPEN_FILE;
843 file_system::ChooseEntryOptions* options = params->options.get();
845 multiple_ = options->accepts_multiple;
847 picker_type = ui::SelectFileDialog::SELECT_OPEN_MULTI_FILE;
849 if (options->type == file_system::CHOOSE_ENTRY_TYPE_OPENWRITABLEFILE &&
850 !app_file_handler_util::HasFileSystemWritePermission(extension_)) {
851 error_ = kRequiresFileSystemWriteError;
853 } else if (options->type == file_system::CHOOSE_ENTRY_TYPE_SAVEFILE) {
854 if (!app_file_handler_util::HasFileSystemWritePermission(extension_)) {
855 error_ = kRequiresFileSystemWriteError;
859 error_ = kMultipleUnsupportedError;
862 picker_type = ui::SelectFileDialog::SELECT_SAVEAS_FILE;
863 } else if (options->type == file_system::CHOOSE_ENTRY_TYPE_OPENDIRECTORY) {
864 is_directory_ = true;
865 if (!extension_->HasAPIPermission(APIPermission::kFileSystemDirectory)) {
866 error_ = kRequiresFileSystemDirectoryError;
870 error_ = kMultipleUnsupportedError;
873 picker_type = ui::SelectFileDialog::SELECT_FOLDER;
876 base::FilePath::StringType suggested_extension;
877 BuildSuggestion(options->suggested_name.get(), &suggested_name,
878 &suggested_extension);
880 BuildFileTypeInfo(&file_type_info, suggested_extension,
881 options->accepts.get(), options->accepts_all_types.get());
884 file_type_info.support_drive = true;
886 base::FilePath previous_path = file_system_api::GetLastChooseEntryDirectory(
887 ExtensionPrefs::Get(GetProfile()), GetExtension()->id());
889 content::BrowserThread::PostTaskAndReply(
890 content::BrowserThread::FILE,
892 base::Bind(&FileSystemChooseEntryFunction::SetInitialPathOnFileThread,
896 base::Bind(&FileSystemChooseEntryFunction::ShowPicker,
903 bool FileSystemRetainEntryFunction::RunImpl() {
904 std::string entry_id;
905 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &entry_id));
906 SavedFilesService* saved_files_service = SavedFilesService::Get(GetProfile());
907 // Add the file to the retain list if it is not already on there.
908 if (!saved_files_service->IsRegistered(extension_->id(), entry_id)) {
909 std::string filesystem_name;
910 std::string filesystem_path;
911 EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &filesystem_name));
912 EXTENSION_FUNCTION_VALIDATE(args_->GetString(2, &filesystem_path));
913 if (!app_file_handler_util::ValidateFileEntryAndGetPath(filesystem_name,
921 content::BrowserThread::PostTaskAndReply(
922 content::BrowserThread::FILE,
924 base::Bind(&FileSystemRetainEntryFunction::SetIsDirectoryOnFileThread,
926 base::Bind(&FileSystemRetainEntryFunction::RetainFileEntry,
932 saved_files_service->EnqueueFileEntry(extension_->id(), entry_id);
937 void FileSystemRetainEntryFunction::RetainFileEntry(
938 const std::string& entry_id) {
939 SavedFilesService* saved_files_service = SavedFilesService::Get(GetProfile());
940 saved_files_service->RegisterFileEntry(
941 extension_->id(), entry_id, path_, is_directory_);
942 saved_files_service->EnqueueFileEntry(extension_->id(), entry_id);
946 void FileSystemRetainEntryFunction::SetIsDirectoryOnFileThread() {
947 is_directory_ = base::DirectoryExists(path_);
950 bool FileSystemIsRestorableFunction::RunImpl() {
951 std::string entry_id;
952 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &entry_id));
953 SetResult(new base::FundamentalValue(SavedFilesService::Get(
954 GetProfile())->IsRegistered(extension_->id(), entry_id)));
958 bool FileSystemRestoreEntryFunction::RunImpl() {
959 std::string entry_id;
960 bool needs_new_entry;
961 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &entry_id));
962 EXTENSION_FUNCTION_VALIDATE(args_->GetBoolean(1, &needs_new_entry));
963 const SavedFileEntry* file_entry = SavedFilesService::Get(
964 GetProfile())->GetFileEntry(extension_->id(), entry_id);
966 error_ = kUnknownIdError;
970 SavedFilesService::Get(GetProfile())
971 ->EnqueueFileEntry(extension_->id(), entry_id);
973 // Only create a new file entry if the renderer requests one.
974 // |needs_new_entry| will be false if the renderer already has an Entry for
976 if (needs_new_entry) {
977 is_directory_ = file_entry->is_directory;
979 AddEntryToResponse(file_entry->path, file_entry->id);
985 } // namespace extensions