Chromium Code Reviews| Index: chrome/browser/extensions/api/file_system/file_system_api.cc |
| diff --git a/chrome/browser/extensions/api/file_system/file_system_api.cc b/chrome/browser/extensions/api/file_system/file_system_api.cc |
| index a44115786c68598dc064e3a41a658b71b044a143..4d0b498828a419f8792fed3d5c930a92478dff4a 100644 |
| --- a/chrome/browser/extensions/api/file_system/file_system_api.cc |
| +++ b/chrome/browser/extensions/api/file_system/file_system_api.cc |
| @@ -60,10 +60,13 @@ const char kSecurityError[] = "Security error"; |
| const char kInvalidCallingPage[] = "Invalid calling page. This function can't " |
| "be called from a background page."; |
| const char kUserCancelled[] = "User cancelled"; |
| -const char kWritableFileError[] = |
| +const char kWritableFileRestrictedLocationError[] = |
| "Cannot write to file in a restricted location"; |
| +const char kWritableFileErrorPrefix[] = "Error opening "; |
|
Matt Giuca
2013/07/19 02:28:11
I think this should be a full format string like "
Sam McNally
2013/07/19 04:22:14
Done.
|
| const char kRequiresFileSystemWriteError[] = |
| "Operation requires fileSystem.write permission"; |
| +const char kMultipleUnsupportedError[] = |
| + "acceptsMultiple: true is not supported for 'saveFile'"; |
| const char kUnknownIdError[] = "Unknown id"; |
| namespace file_system = extensions::api::file_system; |
| @@ -156,6 +159,7 @@ base::FilePath PrettifyPath(const base::FilePath& source_path) { |
| bool g_skip_picker_for_test = false; |
| bool g_use_suggested_path_for_test = false; |
| base::FilePath* g_path_to_be_picked_for_test; |
| +std::vector<base::FilePath>* g_paths_to_be_picked_for_test; |
| bool GetFileSystemAndPathOfFileEntry( |
| const std::string& filesystem_name, |
| @@ -210,13 +214,18 @@ bool GetFilePathOfFileEntry(const std::string& filesystem_name, |
| } |
| bool DoCheckWritableFile(const base::FilePath& path, |
| - const base::FilePath& extension_directory) { |
| + const base::FilePath& extension_directory, |
| + std::string* error_message) { |
| // Don't allow links. |
| - if (file_util::PathExists(path) && file_util::IsLink(path)) |
| + if (file_util::PathExists(path) && file_util::IsLink(path)) { |
| + *error_message = kWritableFileErrorPrefix + path.BaseName().AsUTF8Unsafe(); |
| return false; |
| + } |
| - if (extension_directory == path || extension_directory.IsParent(path)) |
| + if (extension_directory == path || extension_directory.IsParent(path)) { |
| + *error_message = kWritableFileRestrictedLocationError; |
| return false; |
| + } |
| bool is_whitelisted_path = false; |
| @@ -236,6 +245,7 @@ bool DoCheckWritableFile(const base::FilePath& path, |
| base::FilePath blacklisted_path; |
| if (PathService::Get(kBlacklistedPaths[i], &blacklisted_path) && |
| (blacklisted_path == path || blacklisted_path.IsParent(path))) { |
| + *error_message = kWritableFileRestrictedLocationError; |
| return false; |
| } |
| } |
| @@ -251,29 +261,117 @@ bool DoCheckWritableFile(const base::FilePath& path, |
| // Close the file so we don't keep a lock open. |
| if (file != base::kInvalidPlatformFileValue) |
| base::ClosePlatformFile(file); |
| - return error == base::PLATFORM_FILE_OK || |
| - error == base::PLATFORM_FILE_ERROR_EXISTS; |
| -} |
| + if (error != base::PLATFORM_FILE_OK && |
| + error != base::PLATFORM_FILE_ERROR_EXISTS) { |
| + *error_message = kWritableFileErrorPrefix + path.BaseName().AsUTF8Unsafe(); |
| + return false; |
| + } |
| -void CheckLocalWritableFile(const base::FilePath& path, |
| - const base::FilePath& extension_directory, |
| - const base::Closure& on_success, |
| - const base::Closure& on_failure) { |
| - DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE)); |
| - content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, |
| - DoCheckWritableFile(path, extension_directory) ? on_success : on_failure); |
| + return true; |
| } |
| +// Handles checking whether a list of paths are all OK for writing and calls a |
|
Matt Giuca
2013/07/19 02:28:11
"Checks whether".
What does "OK" mean? (Not read-
Sam McNally
2013/07/19 04:22:14
Done.
|
| +// provided on_success or on_failure callback when done. |
| +class WritableFileChecker |
| + : public base::RefCountedThreadSafe<WritableFileChecker> { |
| + public: |
| + WritableFileChecker( |
| + const std::vector<base::FilePath>& paths, |
| + Profile* profile, |
| + const base::FilePath& extension_path, |
| + const base::Closure& on_success, |
| + const base::Callback<void(const std::string&)>& on_failure) |
| + : outstanding_tasks_(1), |
| + extension_path_(extension_path), |
| + on_success_(on_success), |
| + on_failure_(on_failure) { |
| #if defined(OS_CHROMEOS) |
| -void CheckRemoteWritableFile(const base::Closure& on_success, |
| - const base::Closure& on_failure, |
| - drive::FileError error, |
| - const base::FilePath& path) { |
| - content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, |
| - error == drive::FILE_ERROR_OK ? on_success : on_failure); |
| -} |
| + if (drive::util::IsUnderDriveMountPoint(paths[0])) { |
| + outstanding_tasks_ = paths.size(); |
| + for (std::vector<base::FilePath>::const_iterator it = paths.begin(); |
| + it != paths.end(); ++it) { |
| + DCHECK(drive::util::IsUnderDriveMountPoint(*it)); |
| + drive::util::PrepareWritableFileAndRun( |
| + profile, |
| + *it, |
| + base::Bind(&WritableFileChecker::CheckRemoteWritableFile, this)); |
| + } |
| + return; |
| + } |
| +#endif |
| + content::BrowserThread::PostTask( |
| + content::BrowserThread::FILE, |
| + FROM_HERE, |
| + base::Bind(&WritableFileChecker::CheckLocalWritableFiles, this, paths)); |
| + } |
| + |
| + private: |
| + friend class base::RefCountedThreadSafe<WritableFileChecker>; |
| + virtual ~WritableFileChecker() {} |
| + |
| + void TaskDone() { |
| + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| + if (!--outstanding_tasks_) { |
| + if (error_.empty()) |
| + on_success_.Run(); |
| + else |
| + on_failure_.Run(error_); |
| + } |
| + } |
| + |
| + void Error(const std::string& message) { |
| + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| + DCHECK(!message.empty()); |
| + error_ = message; |
| + TaskDone(); |
| + } |
| + |
| + void CheckLocalWritableFiles(const std::vector<base::FilePath>& paths) { |
| + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE)); |
| + std::string error; |
| + for (std::vector<base::FilePath>::const_iterator it = paths.begin(); |
| + it != paths.end(); ++it) { |
| + if (!DoCheckWritableFile(*it, extension_path_, &error)) { |
| + content::BrowserThread::PostTask( |
| + content::BrowserThread::UI, |
| + FROM_HERE, |
| + base::Bind(&WritableFileChecker::Error, this, error)); |
| + return; |
| + } |
| + } |
| + content::BrowserThread::PostTask( |
| + content::BrowserThread::UI, |
| + FROM_HERE, |
| + base::Bind(&WritableFileChecker::TaskDone, this)); |
| + } |
| + |
| +#if defined(OS_CHROMEOS) |
| + void CheckRemoteWritableFile(drive::FileError error, |
| + const base::FilePath& path) { |
| + if (error == drive::FILE_ERROR_OK) { |
| + content::BrowserThread::PostTask( |
| + content::BrowserThread::UI, |
| + FROM_HERE, |
| + base::Bind(&WritableFileChecker::TaskDone, this)); |
| + } else { |
| + content::BrowserThread::PostTask( |
| + content::BrowserThread::UI, |
| + FROM_HERE, |
| + base::Bind( |
| + &WritableFileChecker::Error, |
| + this, |
| + kWritableFileErrorPrefix + path.BaseName().AsUTF8Unsafe())); |
| + } |
| + } |
| #endif |
| + int outstanding_tasks_; |
| + const base::FilePath extension_path_; |
| + std::string error_; |
| + base::Closure on_success_; |
| + base::Callback<void(const std::string&)> on_failure_; |
| +}; |
| + |
| // Expand the mime-types and extensions provided in an AcceptOption, returning |
| // them within the passed extension vector. Returns false if no valid types |
| // were found. |
| @@ -396,38 +494,57 @@ bool FileSystemEntryFunction::HasFileSystemWritePermission() { |
| } |
| void FileSystemEntryFunction::CheckWritableFile(const base::FilePath& path) { |
| + std::vector<base::FilePath> paths; |
| + paths.push_back(path); |
| + CheckWritableFiles(paths, false); |
| +} |
| + |
| +void FileSystemEntryFunction::CheckWritableFiles( |
| + const std::vector<base::FilePath>& paths, bool multiple) { |
| DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| - base::Closure on_success = |
| - base::Bind(&FileSystemEntryFunction::RegisterFileSystemAndSendResponse, |
| - this, path, WRITABLE); |
| - base::Closure on_failure = |
| - base::Bind(&FileSystemEntryFunction::HandleWritableFileError, this); |
| + scoped_refptr<WritableFileChecker> helper = new WritableFileChecker( |
| + paths, profile_, extension_->path(), |
| + base::Bind( |
| + &FileSystemEntryFunction::RegisterFileSystemsAndSendResponse, |
| + this, paths, WRITABLE, multiple), |
| + base::Bind(&FileSystemEntryFunction::HandleWritableFileError, this)); |
| +} |
| -#if defined(OS_CHROMEOS) |
| - if (drive::util::IsUnderDriveMountPoint(path)) { |
| - drive::util::PrepareWritableFileAndRun(profile_, path, |
| - base::Bind(&CheckRemoteWritableFile, on_success, on_failure)); |
| - return; |
| +void FileSystemEntryFunction::RegisterFileSystemsAndSendResponse( |
| + const std::vector<base::FilePath>& paths, |
| + EntryType entry_type, |
| + bool multiple) { |
| + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| + |
| + base::DictionaryValue* response = new base::DictionaryValue(); |
| + base::ListValue* list = new base::ListValue(); |
| + response->Set("entries", list); |
| + response->SetBoolean("multiple", multiple); |
| + SetResult(response); |
| + for (std::vector<base::FilePath>::const_iterator it = paths.begin(); |
| + it != paths.end(); ++it) { |
| + list->Append(BuildEntryDict(*it, entry_type)); |
| } |
| -#endif |
| - content::BrowserThread::PostTask(content::BrowserThread::FILE, FROM_HERE, |
| - base::Bind(&CheckLocalWritableFile, path, extension_->path(), on_success, |
| - on_failure)); |
| -} |
| -void FileSystemEntryFunction::RegisterFileSystemAndSendResponse( |
| - const base::FilePath& path, EntryType entry_type) { |
| - RegisterFileSystemAndSendResponseWithIdOverride(path, entry_type, ""); |
| + SendResponse(true); |
| } |
| void FileSystemEntryFunction::RegisterFileSystemAndSendResponseWithIdOverride( |
| const base::FilePath& path, EntryType entry_type, const std::string& id) { |
| DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| - fileapi::IsolatedContext* isolated_context = |
| - fileapi::IsolatedContext::GetInstance(); |
| - DCHECK(isolated_context); |
| + base::DictionaryValue* response = new base::DictionaryValue(); |
| + base::ListValue* list = new base::ListValue(); |
| + response->Set("entries", list); |
| + response->SetBoolean("multiple", false); |
| + SetResult(response); |
| + list->Append(BuildEntryDictWithIdOverride(path, entry_type, id)); |
| + SendResponse(true); |
| +} |
| + |
| +base::DictionaryValue* FileSystemEntryFunction::BuildEntryDictWithIdOverride( |
| + const base::FilePath& path, EntryType entry_type, const std::string& id) { |
| bool writable = entry_type == WRITABLE; |
| extensions::app_file_handler_util::GrantedFileEntry file_entry = |
| extensions::app_file_handler_util::CreateFileEntry(profile(), |
| @@ -435,20 +552,24 @@ void FileSystemEntryFunction::RegisterFileSystemAndSendResponseWithIdOverride( |
| writable); |
| base::DictionaryValue* dict = new base::DictionaryValue(); |
| - SetResult(dict); |
| dict->SetString("fileSystemId", file_entry.filesystem_id); |
| dict->SetString("baseName", file_entry.registered_name); |
| if (id.empty()) |
| dict->SetString("id", file_entry.id); |
| else |
| dict->SetString("id", id); |
| + return dict; |
| +} |
| - SendResponse(true); |
| +base::DictionaryValue* FileSystemEntryFunction::BuildEntryDict( |
| + const base::FilePath& path, EntryType entry_type) { |
| + return BuildEntryDictWithIdOverride(path, entry_type, ""); |
| } |
| -void FileSystemEntryFunction::HandleWritableFileError() { |
| +void FileSystemEntryFunction::HandleWritableFileError( |
| + const std::string& error) { |
| DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| - error_ = kWritableFileError; |
| + error_ = error; |
| SendResponse(false); |
| } |
| @@ -504,8 +625,10 @@ class FileSystemChooseEntryFunction::FilePicker |
| const base::FilePath& suggested_name, |
| const ui::SelectFileDialog::FileTypeInfo& file_type_info, |
| ui::SelectFileDialog::Type picker_type, |
| - EntryType entry_type) |
| + EntryType entry_type, |
| + bool multiple) |
| : entry_type_(entry_type), |
| + multiple_(multiple), |
| function_(function) { |
| select_file_dialog_ = ui::SelectFileDialog::Create( |
| this, new ChromeSelectFilePolicy(web_contents)); |
| @@ -521,11 +644,21 @@ class FileSystemChooseEntryFunction::FilePicker |
| base::Unretained(this), suggested_name, 1, |
| static_cast<void*>(NULL))); |
| } else if (g_path_to_be_picked_for_test) { |
| - content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, |
| + content::BrowserThread::PostTask( |
| + content::BrowserThread::UI, FROM_HERE, |
| base::Bind( |
| &FileSystemChooseEntryFunction::FilePicker::FileSelected, |
| base::Unretained(this), *g_path_to_be_picked_for_test, 1, |
| static_cast<void*>(NULL))); |
| + } else if (g_paths_to_be_picked_for_test) { |
| + content::BrowserThread::PostTask( |
| + content::BrowserThread::UI, |
| + FROM_HERE, |
| + base::Bind( |
| + &FileSystemChooseEntryFunction::FilePicker::MultiFilesSelected, |
| + base::Unretained(this), |
| + *g_paths_to_be_picked_for_test, |
| + static_cast<void*>(NULL))); |
| } else { |
| content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE, |
| base::Bind( |
| @@ -553,8 +686,9 @@ class FileSystemChooseEntryFunction::FilePicker |
| virtual void FileSelected(const base::FilePath& path, |
| int index, |
| void* params) OVERRIDE { |
| - function_->FileSelected(path, entry_type_); |
| - delete this; |
| + std::vector<base::FilePath> paths; |
| + paths.push_back(path); |
| + MultiFilesSelected(paths, params); |
| } |
| virtual void FileSelectedWithExtraInfo(const ui::SelectedFileInfo& file, |
| @@ -568,16 +702,33 @@ class FileSystemChooseEntryFunction::FilePicker |
| // |
| // TODO(kinaba): remove this, once after the file picker implements proper |
| // switch of the path treatment depending on the |support_drive| flag. |
| - function_->FileSelected(file.file_path, entry_type_); |
| + FileSelected(file.file_path, index, params); |
| + } |
| + |
| + virtual void MultiFilesSelected(const std::vector<base::FilePath>& files, |
| + void* params) OVERRIDE { |
| + function_->FilesSelected(files, entry_type_, multiple_); |
| delete this; |
| } |
| + virtual void MultiFilesSelectedWithExtraInfo( |
| + const std::vector<ui::SelectedFileInfo>& files, |
| + void* params) OVERRIDE { |
| + std::vector<base::FilePath> paths; |
| + for (std::vector<ui::SelectedFileInfo>::const_iterator it = files.begin(); |
| + it != files.end(); ++it) { |
| + paths.push_back(it->file_path); |
| + } |
| + MultiFilesSelected(paths, params); |
| + } |
| + |
| virtual void FileSelectionCanceled(void* params) OVERRIDE { |
| function_->FileSelectionCanceled(); |
| delete this; |
| } |
| EntryType entry_type_; |
| + bool multiple_; |
| scoped_refptr<ui::SelectFileDialog> select_file_dialog_; |
| scoped_refptr<FileSystemChooseEntryFunction> function_; |
| @@ -588,7 +739,8 @@ class FileSystemChooseEntryFunction::FilePicker |
| void FileSystemChooseEntryFunction::ShowPicker( |
| const ui::SelectFileDialog::FileTypeInfo& file_type_info, |
| ui::SelectFileDialog::Type picker_type, |
| - EntryType entry_type) { |
| + EntryType entry_type, |
| + bool multiple) { |
| // TODO(asargent/benwells) - As a short term remediation for crbug.com/179010 |
| // we're adding the ability for a whitelisted extension to use this API since |
| // chrome.fileBrowserHandler.selectFile is ChromeOS-only. Eventually we'd |
| @@ -614,7 +766,7 @@ void FileSystemChooseEntryFunction::ShowPicker( |
| // user has selected a file or cancelled the picker. At that point, the picker |
| // will delete itself, which will also free the function instance. |
| new FilePicker(this, web_contents, initial_path_, file_type_info, |
| - picker_type, entry_type); |
| + picker_type, entry_type, multiple); |
| } |
| // static |
| @@ -623,6 +775,14 @@ void FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathForTest( |
| g_skip_picker_for_test = true; |
| g_use_suggested_path_for_test = false; |
| g_path_to_be_picked_for_test = path; |
| + g_paths_to_be_picked_for_test = NULL; |
| +} |
| + |
| +void FileSystemChooseEntryFunction::SkipPickerAndAlwaysSelectPathsForTest( |
| + std::vector<base::FilePath>* paths) { |
| + g_skip_picker_for_test = true; |
| + g_use_suggested_path_for_test = false; |
| + g_paths_to_be_picked_for_test = paths; |
| } |
| // static |
| @@ -630,6 +790,7 @@ void FileSystemChooseEntryFunction::SkipPickerAndSelectSuggestedPathForTest() { |
| g_skip_picker_for_test = true; |
| g_use_suggested_path_for_test = true; |
| g_path_to_be_picked_for_test = NULL; |
| + g_paths_to_be_picked_for_test = NULL; |
| } |
| // static |
| @@ -637,6 +798,7 @@ void FileSystemChooseEntryFunction::SkipPickerAndAlwaysCancelForTest() { |
| g_skip_picker_for_test = true; |
| g_use_suggested_path_for_test = false; |
| g_path_to_be_picked_for_test = NULL; |
| + g_paths_to_be_picked_for_test = NULL; |
| } |
| // static |
| @@ -670,19 +832,19 @@ void FileSystemChooseEntryFunction::SetInitialPathOnFileThread( |
| } |
| } |
| -void FileSystemChooseEntryFunction::FileSelected(const base::FilePath& path, |
| - EntryType entry_type) { |
| +void FileSystemChooseEntryFunction::FilesSelected( |
| + const std::vector<base::FilePath>& paths, |
| + EntryType entry_type, |
| + bool multiple) { |
| file_system_api::SetLastChooseEntryDirectory( |
| - ExtensionPrefs::Get(profile()), |
| - GetExtension()->id(), |
| - path.DirName()); |
| + ExtensionPrefs::Get(profile()), GetExtension()->id(), paths[0].DirName()); |
| if (entry_type == WRITABLE) { |
| - CheckWritableFile(path); |
| + CheckWritableFiles(paths, multiple); |
| return; |
| } |
| // Don't need to check the file, it's for reading. |
| - RegisterFileSystemAndSendResponse(path, READ_ONLY); |
| + RegisterFileSystemsAndSendResponse(paths, READ_ONLY, multiple); |
| } |
| void FileSystemChooseEntryFunction::FileSelectionCanceled() { |
| @@ -761,10 +923,18 @@ bool FileSystemChooseEntryFunction::RunImpl() { |
| ui::SelectFileDialog::SELECT_OPEN_FILE; |
| file_system::ChooseEntryOptions* options = params->options.get(); |
| + bool multiple = false; |
| if (options) { |
| + multiple = options->accepts_multiple; |
| + if (multiple) |
| + picker_type = ui::SelectFileDialog::SELECT_OPEN_MULTI_FILE; |
| if (options->type == file_system::CHOOSE_ENTRY_TYPE_OPENWRITABLEFILE) { |
| entry_type = WRITABLE; |
| } else if (options->type == file_system::CHOOSE_ENTRY_TYPE_SAVEFILE) { |
| + if (multiple) { |
| + error_ = kMultipleUnsupportedError; |
| + return false; |
| + } |
| entry_type = WRITABLE; |
| picker_type = ui::SelectFileDialog::SELECT_SAVEAS_FILE; |
| } |
| @@ -798,7 +968,7 @@ bool FileSystemChooseEntryFunction::RunImpl() { |
| suggested_name, previous_path), |
| base::Bind( |
| &FileSystemChooseEntryFunction::ShowPicker, this, file_type_info, |
| - picker_type, entry_type)); |
| + picker_type, entry_type, multiple)); |
| return true; |
| } |