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/saved_files_service.h"
8 #include "apps/shell_window.h"
9 #include "apps/shell_window_registry.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/extensions/extension_system.h"
24 #include "chrome/browser/platform_util.h"
25 #include "chrome/browser/profiles/profile.h"
26 #include "chrome/browser/ui/apps/directory_access_confirmation_dialog.h"
27 #include "chrome/browser/ui/chrome_select_file_policy.h"
28 #include "chrome/common/chrome_paths.h"
29 #include "chrome/common/extensions/api/file_system.h"
30 #include "content/public/browser/browser_thread.h"
31 #include "content/public/browser/child_process_security_policy.h"
32 #include "content/public/browser/render_process_host.h"
33 #include "content/public/browser/render_view_host.h"
34 #include "content/public/browser/web_contents.h"
35 #include "content/public/browser/web_contents_view.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 using apps::SavedFileEntry;
53 using apps::SavedFilesService;
54 using apps::ShellWindow;
55 using fileapi::IsolatedContext;
57 const char kInvalidParameters[] = "Invalid parameters";
58 const char kSecurityError[] = "Security error";
59 const char kInvalidCallingPage[] = "Invalid calling page. This function can't "
60 "be called from a background page.";
61 const char kUserCancelled[] = "User cancelled";
62 const char kWritableFileErrorFormat[] = "Error opening %s";
63 const char kRequiresFileSystemWriteError[] =
64 "Operation requires fileSystem.write permission";
65 const char kRequiresFileSystemDirectoryError[] =
66 "Operation requires fileSystem.directory permission";
67 const char kMultipleUnsupportedError[] =
68 "acceptsMultiple: true is not supported for 'saveFile'";
69 const char kUnknownIdError[] = "Unknown id";
71 namespace file_system = extensions::api::file_system;
72 namespace ChooseEntry = file_system::ChooseEntry;
76 #if defined(OS_MACOSX)
77 // Retrieves the localized display name for the base name of the given path.
78 // If the path is not localized, this will just return the base name.
79 std::string GetDisplayBaseName(const base::FilePath& path) {
80 base::ScopedCFTypeRef<CFURLRef> url(CFURLCreateFromFileSystemRepresentation(
81 NULL, (const UInt8*)path.value().c_str(), path.value().length(), true));
83 return path.BaseName().value();
86 if (LSCopyDisplayNameForURL(url, &str) != noErr)
87 return path.BaseName().value();
89 std::string result(base::SysCFStringRefToUTF8(str));
94 // Prettifies |source_path| for OS X, by localizing every component of the
95 // path. Additionally, if the path is inside the user's home directory, then
96 // replace the home directory component with "~".
97 base::FilePath PrettifyPath(const base::FilePath& source_path) {
98 base::FilePath home_path;
99 PathService::Get(base::DIR_HOME, &home_path);
100 DCHECK(source_path.IsAbsolute());
102 // Break down the incoming path into components, and grab the display name
103 // for every component. This will match app bundles, ".localized" folders,
104 // and localized subfolders of the user's home directory.
105 // Don't grab the display name of the first component, i.e., "/", as it'll
106 // show up as the HDD name.
107 std::vector<base::FilePath::StringType> components;
108 source_path.GetComponents(&components);
109 base::FilePath display_path = base::FilePath(components[0]);
110 base::FilePath actual_path = display_path;
111 for (std::vector<base::FilePath::StringType>::iterator i =
112 components.begin() + 1; i != components.end(); ++i) {
113 actual_path = actual_path.Append(*i);
114 if (actual_path == home_path) {
115 display_path = base::FilePath("~");
116 home_path = base::FilePath();
119 std::string display = GetDisplayBaseName(actual_path);
120 display_path = display_path.Append(display);
122 DCHECK_EQ(actual_path.value(), source_path.value());
125 #else // defined(OS_MACOSX)
126 // Prettifies |source_path|, by replacing the user's home directory with "~"
128 base::FilePath PrettifyPath(const base::FilePath& source_path) {
129 #if defined(OS_WIN) || defined(OS_POSIX)
131 int home_key = base::DIR_PROFILE;
132 #elif defined(OS_POSIX)
133 int home_key = base::DIR_HOME;
135 base::FilePath home_path;
136 base::FilePath display_path = base::FilePath::FromUTF8Unsafe("~");
137 if (PathService::Get(home_key, &home_path)
138 && home_path.AppendRelativePath(source_path, &display_path))
143 #endif // defined(OS_MACOSX)
145 bool g_skip_picker_for_test = false;
146 bool g_use_suggested_path_for_test = false;
147 base::FilePath* g_path_to_be_picked_for_test;
148 std::vector<base::FilePath>* g_paths_to_be_picked_for_test;
149 bool g_skip_directory_confirmation_for_test = false;
150 bool g_allow_directory_access_for_test = false;
152 bool ValidateFileEntryAndGetPath(
153 const std::string& filesystem_name,
154 const std::string& filesystem_path,
155 const content::RenderViewHost* render_view_host,
156 base::FilePath* file_path,
157 std::string* error) {
158 std::string filesystem_id;
159 if (!fileapi::CrackIsolatedFileSystemName(filesystem_name, &filesystem_id)) {
160 *error = kInvalidParameters;
164 // Only return the display path if the process has read access to the
166 content::ChildProcessSecurityPolicy* policy =
167 content::ChildProcessSecurityPolicy::GetInstance();
168 if (!policy->CanReadFileSystem(render_view_host->GetProcess()->GetID(),
170 *error = kSecurityError;
174 IsolatedContext* context = IsolatedContext::GetInstance();
175 base::FilePath relative_path =
176 base::FilePath::FromUTF8Unsafe(filesystem_path);
177 base::FilePath virtual_path = context->CreateVirtualRootPath(filesystem_id)
178 .Append(relative_path);
179 fileapi::FileSystemType type;
180 if (!context->CrackVirtualPath(
181 virtual_path, &filesystem_id, &type, file_path)) {
182 *error = kInvalidParameters;
186 // The file system API is only intended to operate on file entries that
187 // correspond to a native file, selected by the user so only allow file
188 // systems returned by the file system API or from a drag and drop operation.
189 if (type != fileapi::kFileSystemTypeNativeForPlatformApp &&
190 type != fileapi::kFileSystemTypeDragged) {
191 *error = kInvalidParameters;
198 // Expand the mime-types and extensions provided in an AcceptOption, returning
199 // them within the passed extension vector. Returns false if no valid types
201 bool GetFileTypesFromAcceptOption(
202 const file_system::AcceptOption& accept_option,
203 std::vector<base::FilePath::StringType>* extensions,
204 string16* description) {
205 std::set<base::FilePath::StringType> extension_set;
206 int description_id = 0;
208 if (accept_option.mime_types.get()) {
209 std::vector<std::string>* list = accept_option.mime_types.get();
210 bool valid_type = false;
211 for (std::vector<std::string>::const_iterator iter = list->begin();
212 iter != list->end(); ++iter) {
213 std::vector<base::FilePath::StringType> inner;
214 std::string accept_type = *iter;
215 StringToLowerASCII(&accept_type);
216 net::GetExtensionsForMimeType(accept_type, &inner);
221 description_id = 0; // We already have an accept type with label; if
222 // we find another, give up and use the default.
223 else if (accept_type == "image/*")
224 description_id = IDS_IMAGE_FILES;
225 else if (accept_type == "audio/*")
226 description_id = IDS_AUDIO_FILES;
227 else if (accept_type == "video/*")
228 description_id = IDS_VIDEO_FILES;
230 extension_set.insert(inner.begin(), inner.end());
235 if (accept_option.extensions.get()) {
236 std::vector<std::string>* list = accept_option.extensions.get();
237 for (std::vector<std::string>::const_iterator iter = list->begin();
238 iter != list->end(); ++iter) {
239 std::string extension = *iter;
240 StringToLowerASCII(&extension);
242 extension_set.insert(UTF8ToWide(*iter));
244 extension_set.insert(*iter);
249 extensions->assign(extension_set.begin(), extension_set.end());
250 if (extensions->empty())
253 if (accept_option.description.get())
254 *description = UTF8ToUTF16(*accept_option.description.get());
255 else if (description_id)
256 *description = l10n_util::GetStringUTF16(description_id);
261 // Key for the path of the directory of the file last chosen by the user in
262 // response to a chrome.fileSystem.chooseEntry() call.
263 const char kLastChooseEntryDirectory[] = "last_choose_file_directory";
265 const int kGraylistedPaths[] = {
268 base::DIR_PROGRAM_FILES,
269 base::DIR_PROGRAM_FILESX86,
271 #elif defined(OS_POSIX)
278 namespace extensions {
280 namespace file_system_api {
282 base::FilePath GetLastChooseEntryDirectory(const ExtensionPrefs* prefs,
283 const std::string& extension_id) {
285 std::string string_path;
286 if (prefs->ReadPrefAsString(extension_id,
287 kLastChooseEntryDirectory,
289 path = base::FilePath::FromUTF8Unsafe(string_path);
294 void SetLastChooseEntryDirectory(ExtensionPrefs* prefs,
295 const std::string& extension_id,
296 const base::FilePath& path) {
297 prefs->UpdateExtensionPref(extension_id,
298 kLastChooseEntryDirectory,
299 base::CreateFilePathValue(path));
302 } // namespace file_system_api
304 bool FileSystemGetDisplayPathFunction::RunImpl() {
305 std::string filesystem_name;
306 std::string filesystem_path;
307 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &filesystem_name));
308 EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &filesystem_path));
310 base::FilePath file_path;
311 if (!ValidateFileEntryAndGetPath(filesystem_name, filesystem_path,
312 render_view_host_, &file_path, &error_))
315 file_path = PrettifyPath(file_path);
316 SetResult(new base::StringValue(file_path.value()));
320 FileSystemEntryFunction::FileSystemEntryFunction()
322 is_directory_(false),
325 void FileSystemEntryFunction::CheckWritableFiles(
326 const std::vector<base::FilePath>& paths) {
327 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
328 app_file_handler_util::CheckWritableFiles(
332 base::Bind(&FileSystemEntryFunction::RegisterFileSystemsAndSendResponse,
335 base::Bind(&FileSystemEntryFunction::HandleWritableFileError, this));
338 void FileSystemEntryFunction::RegisterFileSystemsAndSendResponse(
339 const std::vector<base::FilePath>& paths) {
340 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
341 if (!render_view_host_)
345 for (std::vector<base::FilePath>::const_iterator it = paths.begin();
346 it != paths.end(); ++it) {
347 AddEntryToResponse(*it, "");
352 void FileSystemEntryFunction::CreateResponse() {
354 response_ = new base::DictionaryValue();
355 base::ListValue* list = new base::ListValue();
356 response_->Set("entries", list);
357 response_->SetBoolean("multiple", multiple_);
358 SetResult(response_);
361 void FileSystemEntryFunction::AddEntryToResponse(
362 const base::FilePath& path,
363 const std::string& id_override) {
365 extensions::app_file_handler_util::GrantedFileEntry file_entry =
366 extensions::app_file_handler_util::CreateFileEntry(
369 render_view_host_->GetProcess()->GetID(),
372 base::ListValue* entries;
373 bool success = response_->GetList("entries", &entries);
376 base::DictionaryValue* entry = new base::DictionaryValue();
377 entry->SetString("fileSystemId", file_entry.filesystem_id);
378 entry->SetString("baseName", file_entry.registered_name);
379 if (id_override.empty())
380 entry->SetString("id", file_entry.id);
382 entry->SetString("id", id_override);
383 entry->SetBoolean("isDirectory", is_directory_);
384 entries->Append(entry);
387 void FileSystemEntryFunction::HandleWritableFileError(
388 const base::FilePath& error_path) {
389 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
390 error_ = base::StringPrintf(kWritableFileErrorFormat,
391 error_path.BaseName().AsUTF8Unsafe().c_str());
395 bool FileSystemGetWritableEntryFunction::RunImpl() {
396 std::string filesystem_name;
397 std::string filesystem_path;
398 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &filesystem_name));
399 EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &filesystem_path));
401 if (!app_file_handler_util::HasFileSystemWritePermission(extension_)) {
402 error_ = kRequiresFileSystemWriteError;
406 if (!ValidateFileEntryAndGetPath(filesystem_name, filesystem_path,
407 render_view_host_, &path_, &error_))
410 content::BrowserThread::PostTaskAndReply(
411 content::BrowserThread::FILE,
414 &FileSystemGetWritableEntryFunction::SetIsDirectoryOnFileThread,
417 &FileSystemGetWritableEntryFunction::CheckPermissionAndSendResponse,
422 void FileSystemGetWritableEntryFunction::CheckPermissionAndSendResponse() {
423 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
425 !extension_->HasAPIPermission(APIPermission::kFileSystemDirectory)) {
426 error_ = kRequiresFileSystemDirectoryError;
429 std::vector<base::FilePath> paths;
430 paths.push_back(path_);
431 CheckWritableFiles(paths);
434 void FileSystemGetWritableEntryFunction::SetIsDirectoryOnFileThread() {
435 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
436 if (base::DirectoryExists(path_)) {
437 is_directory_ = true;
441 bool FileSystemIsWritableEntryFunction::RunImpl() {
442 std::string filesystem_name;
443 std::string filesystem_path;
444 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &filesystem_name));
445 EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &filesystem_path));
447 std::string filesystem_id;
448 if (!fileapi::CrackIsolatedFileSystemName(filesystem_name, &filesystem_id)) {
449 error_ = kInvalidParameters;
453 content::ChildProcessSecurityPolicy* policy =
454 content::ChildProcessSecurityPolicy::GetInstance();
455 int renderer_id = render_view_host_->GetProcess()->GetID();
456 bool is_writable = policy->CanReadWriteFileSystem(renderer_id,
459 SetResult(new base::FundamentalValue(is_writable));
463 // Handles showing a dialog to the user to ask for the filename for a file to
465 class FileSystemChooseEntryFunction::FilePicker
466 : public ui::SelectFileDialog::Listener {
468 FilePicker(FileSystemChooseEntryFunction* function,
469 content::WebContents* web_contents,
470 const base::FilePath& suggested_name,
471 const ui::SelectFileDialog::FileTypeInfo& file_type_info,
472 ui::SelectFileDialog::Type picker_type)
473 : function_(function) {
474 select_file_dialog_ = ui::SelectFileDialog::Create(
475 this, new ChromeSelectFilePolicy(web_contents));
476 gfx::NativeWindow owning_window = web_contents ?
477 platform_util::GetTopLevel(web_contents->GetView()->GetNativeView()) :
480 if (g_skip_picker_for_test) {
481 if (g_use_suggested_path_for_test) {
482 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
484 &FileSystemChooseEntryFunction::FilePicker::FileSelected,
485 base::Unretained(this), suggested_name, 1,
486 static_cast<void*>(NULL)));
487 } else if (g_path_to_be_picked_for_test) {
488 content::BrowserThread::PostTask(
489 content::BrowserThread::UI, FROM_HERE,
491 &FileSystemChooseEntryFunction::FilePicker::FileSelected,
492 base::Unretained(this), *g_path_to_be_picked_for_test, 1,
493 static_cast<void*>(NULL)));
494 } else if (g_paths_to_be_picked_for_test) {
495 content::BrowserThread::PostTask(
496 content::BrowserThread::UI,
499 &FileSystemChooseEntryFunction::FilePicker::MultiFilesSelected,
500 base::Unretained(this),
501 *g_paths_to_be_picked_for_test,
502 static_cast<void*>(NULL)));
504 content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
506 &FileSystemChooseEntryFunction::FilePicker::
507 FileSelectionCanceled,
508 base::Unretained(this), static_cast<void*>(NULL)));
513 select_file_dialog_->SelectFile(picker_type,
518 base::FilePath::StringType(),
523 virtual ~FilePicker() {}
526 // ui::SelectFileDialog::Listener implementation.
527 virtual void FileSelected(const base::FilePath& path,
529 void* params) OVERRIDE {
530 std::vector<base::FilePath> paths;
531 paths.push_back(path);
532 MultiFilesSelected(paths, params);
535 virtual void FileSelectedWithExtraInfo(const ui::SelectedFileInfo& file,
537 void* params) OVERRIDE {
538 // Normally, file.local_path is used because it is a native path to the
539 // local read-only cached file in the case of remote file system like
540 // Chrome OS's Google Drive integration. Here, however, |file.file_path| is
541 // necessary because we need to create a FileEntry denoting the remote file,
542 // not its cache. On other platforms than Chrome OS, they are the same.
544 // TODO(kinaba): remove this, once after the file picker implements proper
545 // switch of the path treatment depending on the |support_drive| flag.
546 FileSelected(file.file_path, index, params);
549 virtual void MultiFilesSelected(const std::vector<base::FilePath>& files,
550 void* params) OVERRIDE {
551 function_->FilesSelected(files);
555 virtual void MultiFilesSelectedWithExtraInfo(
556 const std::vector<ui::SelectedFileInfo>& files,
557 void* params) OVERRIDE {
558 std::vector<base::FilePath> paths;
559 for (std::vector<ui::SelectedFileInfo>::const_iterator it = files.begin();
560 it != files.end(); ++it) {
561 paths.push_back(it->file_path);
563 MultiFilesSelected(paths, params);
566 virtual void FileSelectionCanceled(void* params) OVERRIDE {
567 function_->FileSelectionCanceled();
571 scoped_refptr<ui::SelectFileDialog> select_file_dialog_;
572 scoped_refptr<FileSystemChooseEntryFunction> function_;
574 DISALLOW_COPY_AND_ASSIGN(FilePicker);
577 void FileSystemChooseEntryFunction::ShowPicker(
578 const ui::SelectFileDialog::FileTypeInfo& file_type_info,
579 ui::SelectFileDialog::Type picker_type) {
580 // TODO(asargent/benwells) - As a short term remediation for crbug.com/179010
581 // we're adding the ability for a whitelisted extension to use this API since
582 // chrome.fileBrowserHandler.selectFile is ChromeOS-only. Eventually we'd
583 // like a better solution and likely this code will go back to being
584 // platform-app only.
585 content::WebContents* web_contents = NULL;
586 if (extension_->is_platform_app()) {
587 apps::ShellWindowRegistry* registry =
588 apps::ShellWindowRegistry::Get(GetProfile());
590 ShellWindow* shell_window = registry->GetShellWindowForRenderViewHost(
593 error_ = kInvalidCallingPage;
597 web_contents = shell_window->web_contents();
599 web_contents = GetAssociatedWebContents();
601 // The file picker will hold a reference to this function instance, preventing
602 // its destruction (and subsequent sending of the function response) until the
603 // user has selected a file or cancelled the picker. At that point, the picker
604 // will delete itself, which will also free the function instance.
606 this, web_contents, initial_path_, file_type_info, picker_type);
610 void FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathForTest(
611 base::FilePath* path) {
612 g_skip_picker_for_test = true;
613 g_use_suggested_path_for_test = false;
614 g_path_to_be_picked_for_test = path;
615 g_paths_to_be_picked_for_test = NULL;
618 void FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathsForTest(
619 std::vector<base::FilePath>* paths) {
620 g_skip_picker_for_test = true;
621 g_use_suggested_path_for_test = false;
622 g_paths_to_be_picked_for_test = paths;
626 void FileSystemChooseEntryFunction::SkipPickerAndSelectSuggestedPathForTest() {
627 g_skip_picker_for_test = true;
628 g_use_suggested_path_for_test = true;
629 g_path_to_be_picked_for_test = NULL;
630 g_paths_to_be_picked_for_test = NULL;
634 void FileSystemChooseEntryFunction::SkipPickerAndAlwaysCancelForTest() {
635 g_skip_picker_for_test = true;
636 g_use_suggested_path_for_test = false;
637 g_path_to_be_picked_for_test = NULL;
638 g_paths_to_be_picked_for_test = NULL;
642 void FileSystemChooseEntryFunction::StopSkippingPickerForTest() {
643 g_skip_picker_for_test = false;
647 void FileSystemChooseEntryFunction::SkipDirectoryConfirmationForTest() {
648 g_skip_directory_confirmation_for_test = true;
649 g_allow_directory_access_for_test = true;
653 void FileSystemChooseEntryFunction::AutoCancelDirectoryConfirmationForTest() {
654 g_skip_directory_confirmation_for_test = true;
655 g_allow_directory_access_for_test = false;
659 void FileSystemChooseEntryFunction::StopSkippingDirectoryConfirmationForTest() {
660 g_skip_directory_confirmation_for_test = false;
664 void FileSystemChooseEntryFunction::RegisterTempExternalFileSystemForTest(
665 const std::string& name, const base::FilePath& path) {
666 // For testing on Chrome OS, where to deal with remote and local paths
667 // smoothly, all accessed paths need to be registered in the list of
668 // external mount points.
669 fileapi::ExternalMountPoints::GetSystemInstance()->RegisterFileSystem(
670 name, fileapi::kFileSystemTypeNativeLocal, path);
673 void FileSystemChooseEntryFunction::SetInitialPathOnFileThread(
674 const base::FilePath& suggested_name,
675 const base::FilePath& previous_path) {
676 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
677 if (!previous_path.empty() && base::DirectoryExists(previous_path)) {
678 initial_path_ = previous_path.Append(suggested_name);
680 base::FilePath documents_dir;
681 if (PathService::Get(chrome::DIR_USER_DOCUMENTS, &documents_dir)) {
682 initial_path_ = documents_dir.Append(suggested_name);
684 initial_path_ = suggested_name;
689 void FileSystemChooseEntryFunction::FilesSelected(
690 const std::vector<base::FilePath>& paths) {
691 DCHECK(!paths.empty());
692 base::FilePath last_choose_directory;
694 last_choose_directory = paths[0];
696 last_choose_directory = paths[0].DirName();
698 file_system_api::SetLastChooseEntryDirectory(
699 ExtensionPrefs::Get(GetProfile()),
700 GetExtension()->id(),
701 last_choose_directory);
703 // Get the WebContents for the app window to be the parent window of the
704 // confirmation dialog if necessary.
705 apps::ShellWindowRegistry* registry =
706 apps::ShellWindowRegistry::Get(GetProfile());
708 ShellWindow* shell_window = registry->GetShellWindowForRenderViewHost(
711 error_ = kInvalidCallingPage;
715 content::WebContents* web_contents = shell_window->web_contents();
717 content::BrowserThread::PostTask(
718 content::BrowserThread::FILE,
721 &FileSystemChooseEntryFunction::ConfirmDirectoryAccessOnFileThread,
728 OnDirectoryAccessConfirmed(paths);
731 void FileSystemChooseEntryFunction::FileSelectionCanceled() {
732 error_ = kUserCancelled;
736 void FileSystemChooseEntryFunction::ConfirmDirectoryAccessOnFileThread(
737 const std::vector<base::FilePath>& paths,
738 content::WebContents* web_contents) {
739 DCHECK_EQ(paths.size(), 1u);
740 const base::FilePath path = base::MakeAbsoluteFilePath(paths[0]);
742 content::BrowserThread::PostTask(
743 content::BrowserThread::UI,
745 base::Bind(&FileSystemChooseEntryFunction::FileSelectionCanceled,
750 for (size_t i = 0; i < arraysize(kGraylistedPaths); i++) {
751 base::FilePath graylisted_path;
752 if (PathService::Get(kGraylistedPaths[i], &graylisted_path) &&
753 (path == graylisted_path || path.IsParent(graylisted_path))) {
754 if (g_skip_directory_confirmation_for_test) {
755 if (g_allow_directory_access_for_test) {
758 content::BrowserThread::PostTask(
759 content::BrowserThread::UI,
761 base::Bind(&FileSystemChooseEntryFunction::FileSelectionCanceled,
767 content::BrowserThread::PostTask(
768 content::BrowserThread::UI,
771 CreateDirectoryAccessConfirmationDialog,
772 app_file_handler_util::HasFileSystemWritePermission(extension_),
773 UTF8ToUTF16(extension_->name()),
776 &FileSystemChooseEntryFunction::OnDirectoryAccessConfirmed,
779 base::Bind(&FileSystemChooseEntryFunction::FileSelectionCanceled,
785 content::BrowserThread::PostTask(
786 content::BrowserThread::UI,
788 base::Bind(&FileSystemChooseEntryFunction::OnDirectoryAccessConfirmed,
792 void FileSystemChooseEntryFunction::OnDirectoryAccessConfirmed(
793 const std::vector<base::FilePath>& paths) {
794 if (app_file_handler_util::HasFileSystemWritePermission(extension_)) {
795 CheckWritableFiles(paths);
799 // Don't need to check the file, it's for reading.
800 RegisterFileSystemsAndSendResponse(paths);
803 void FileSystemChooseEntryFunction::BuildFileTypeInfo(
804 ui::SelectFileDialog::FileTypeInfo* file_type_info,
805 const base::FilePath::StringType& suggested_extension,
806 const AcceptOptions* accepts,
807 const bool* acceptsAllTypes) {
808 file_type_info->include_all_files = true;
810 file_type_info->include_all_files = *acceptsAllTypes;
812 bool need_suggestion = !file_type_info->include_all_files &&
813 !suggested_extension.empty();
816 typedef file_system::AcceptOption AcceptOption;
817 for (std::vector<linked_ptr<AcceptOption> >::const_iterator iter =
818 accepts->begin(); iter != accepts->end(); ++iter) {
819 string16 description;
820 std::vector<base::FilePath::StringType> extensions;
822 if (!GetFileTypesFromAcceptOption(**iter, &extensions, &description))
823 continue; // No extensions were found.
825 file_type_info->extensions.push_back(extensions);
826 file_type_info->extension_description_overrides.push_back(description);
828 // If we still need to find suggested_extension, hunt for it inside the
829 // extensions returned from GetFileTypesFromAcceptOption.
830 if (need_suggestion && std::find(extensions.begin(),
831 extensions.end(), suggested_extension) != extensions.end()) {
832 need_suggestion = false;
837 // If there's nothing in our accepted extension list or we couldn't find the
838 // suggested extension required, then default to accepting all types.
839 if (file_type_info->extensions.empty() || need_suggestion)
840 file_type_info->include_all_files = true;
843 void FileSystemChooseEntryFunction::BuildSuggestion(
844 const std::string *opt_name,
845 base::FilePath* suggested_name,
846 base::FilePath::StringType* suggested_extension) {
848 *suggested_name = base::FilePath::FromUTF8Unsafe(*opt_name);
850 // Don't allow any path components; shorten to the base name. This should
851 // result in a relative path, but in some cases may not. Clear the
852 // suggestion for safety if this is the case.
853 *suggested_name = suggested_name->BaseName();
854 if (suggested_name->IsAbsolute())
855 *suggested_name = base::FilePath();
857 *suggested_extension = suggested_name->Extension();
858 if (!suggested_extension->empty())
859 suggested_extension->erase(suggested_extension->begin()); // drop the .
863 bool FileSystemChooseEntryFunction::RunImpl() {
864 scoped_ptr<ChooseEntry::Params> params(ChooseEntry::Params::Create(*args_));
865 EXTENSION_FUNCTION_VALIDATE(params.get());
867 base::FilePath suggested_name;
868 ui::SelectFileDialog::FileTypeInfo file_type_info;
869 ui::SelectFileDialog::Type picker_type =
870 ui::SelectFileDialog::SELECT_OPEN_FILE;
872 file_system::ChooseEntryOptions* options = params->options.get();
874 multiple_ = options->accepts_multiple;
876 picker_type = ui::SelectFileDialog::SELECT_OPEN_MULTI_FILE;
878 if (options->type == file_system::CHOOSE_ENTRY_TYPE_OPENWRITABLEFILE &&
879 !app_file_handler_util::HasFileSystemWritePermission(extension_)) {
880 error_ = kRequiresFileSystemWriteError;
882 } else if (options->type == file_system::CHOOSE_ENTRY_TYPE_SAVEFILE) {
883 if (!app_file_handler_util::HasFileSystemWritePermission(extension_)) {
884 error_ = kRequiresFileSystemWriteError;
888 error_ = kMultipleUnsupportedError;
891 picker_type = ui::SelectFileDialog::SELECT_SAVEAS_FILE;
892 } else if (options->type == file_system::CHOOSE_ENTRY_TYPE_OPENDIRECTORY) {
893 is_directory_ = true;
894 if (!extension_->HasAPIPermission(APIPermission::kFileSystemDirectory)) {
895 error_ = kRequiresFileSystemDirectoryError;
899 error_ = kMultipleUnsupportedError;
902 picker_type = ui::SelectFileDialog::SELECT_FOLDER;
905 base::FilePath::StringType suggested_extension;
906 BuildSuggestion(options->suggested_name.get(), &suggested_name,
907 &suggested_extension);
909 BuildFileTypeInfo(&file_type_info, suggested_extension,
910 options->accepts.get(), options->accepts_all_types.get());
913 file_type_info.support_drive = true;
915 base::FilePath previous_path = file_system_api::GetLastChooseEntryDirectory(
916 ExtensionPrefs::Get(GetProfile()), GetExtension()->id());
918 content::BrowserThread::PostTaskAndReply(
919 content::BrowserThread::FILE,
921 base::Bind(&FileSystemChooseEntryFunction::SetInitialPathOnFileThread,
925 base::Bind(&FileSystemChooseEntryFunction::ShowPicker,
932 bool FileSystemRetainEntryFunction::RunImpl() {
933 std::string entry_id;
934 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &entry_id));
935 SavedFilesService* saved_files_service = SavedFilesService::Get(GetProfile());
936 // Add the file to the retain list if it is not already on there.
937 if (!saved_files_service->IsRegistered(extension_->id(), entry_id)) {
938 std::string filesystem_name;
939 std::string filesystem_path;
940 EXTENSION_FUNCTION_VALIDATE(args_->GetString(1, &filesystem_name));
941 EXTENSION_FUNCTION_VALIDATE(args_->GetString(2, &filesystem_path));
942 if (!ValidateFileEntryAndGetPath(filesystem_name,
950 content::BrowserThread::PostTaskAndReply(
951 content::BrowserThread::FILE,
953 base::Bind(&FileSystemRetainEntryFunction::SetIsDirectoryOnFileThread,
955 base::Bind(&FileSystemRetainEntryFunction::RetainFileEntry,
961 saved_files_service->EnqueueFileEntry(extension_->id(), entry_id);
966 void FileSystemRetainEntryFunction::RetainFileEntry(
967 const std::string& entry_id) {
968 SavedFilesService* saved_files_service = SavedFilesService::Get(GetProfile());
969 saved_files_service->RegisterFileEntry(
970 extension_->id(), entry_id, path_, is_directory_);
971 saved_files_service->EnqueueFileEntry(extension_->id(), entry_id);
975 void FileSystemRetainEntryFunction::SetIsDirectoryOnFileThread() {
976 is_directory_ = base::DirectoryExists(path_);
979 bool FileSystemIsRestorableFunction::RunImpl() {
980 std::string entry_id;
981 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &entry_id));
982 SetResult(new base::FundamentalValue(SavedFilesService::Get(
983 GetProfile())->IsRegistered(extension_->id(), entry_id)));
987 bool FileSystemRestoreEntryFunction::RunImpl() {
988 std::string entry_id;
989 bool needs_new_entry;
990 EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &entry_id));
991 EXTENSION_FUNCTION_VALIDATE(args_->GetBoolean(1, &needs_new_entry));
992 const SavedFileEntry* file_entry = SavedFilesService::Get(
993 GetProfile())->GetFileEntry(extension_->id(), entry_id);
995 error_ = kUnknownIdError;
999 SavedFilesService::Get(GetProfile())
1000 ->EnqueueFileEntry(extension_->id(), entry_id);
1002 // Only create a new file entry if the renderer requests one.
1003 // |needs_new_entry| will be false if the renderer already has an Entry for
1005 if (needs_new_entry) {
1006 is_directory_ = file_entry->is_directory;
1008 AddEntryToResponse(file_entry->path, file_entry->id);
1014 } // namespace extensions