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 c02d5e011a633df8a2da4569854f4b0246ebfbcc..0b8bf72ceab657280ba710be6458373a80c30dd0 100644 |
--- a/chrome/browser/extensions/api/file_system/file_system_api.cc |
+++ b/chrome/browser/extensions/api/file_system/file_system_api.cc |
@@ -12,6 +12,7 @@ |
#include "base/logging.h" |
#include "base/path_service.h" |
#include "base/strings/string_util.h" |
+#include "base/strings/stringprintf.h" |
#include "base/strings/sys_string_conversions.h" |
#include "base/strings/utf_string_conversions.h" |
#include "base/value_conversions.h" |
@@ -60,10 +61,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 kWritableFileErrorFormat[] = "Error opening %s"; |
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 +160,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 +215,19 @@ 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 (base::PathExists(path) && file_util::IsLink(path)) |
+ if (base::PathExists(path) && file_util::IsLink(path)) { |
+ *error_message = base::StringPrintf(kWritableFileErrorFormat, |
+ path.BaseName().AsUTF8Unsafe().c_str()); |
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 +247,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 +263,125 @@ 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 = base::StringPrintf(kWritableFileErrorFormat, |
+ path.BaseName().AsUTF8Unsafe().c_str()); |
+ 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; |
} |
+// Checks whether a list of paths are all OK for writing and calls a provided |
+// on_success or on_failure callback when done. A file is OK for writing if it |
+// is not a symlink, is not in a blacklisted path and can be opened for writing; |
+// files are created if they do not exist. |
+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() {} |
+ |
+ // Called when a work item is completed. If all work items are done, this |
+ // calls the success or failure callback. |
+ void TaskDone() { |
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
+ if (--outstanding_tasks_ == 0) { |
+ if (error_.empty()) |
+ on_success_.Run(); |
+ else |
+ on_failure_.Run(error_); |
+ } |
+ } |
+ |
+ // Reports an error in completing a work item. This may be called more than |
+ // once, but only the last message will be retained. |
+ void Error(const std::string& message) { |
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
Matt Giuca
2013/08/01 00:02:24
Nit: Remove this DCHECK. There is nothing in this
Sam McNally
2013/08/01 00:08:39
Done.
|
+ 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, |
+ base::StringPrintf(kWritableFileErrorFormat, |
+ path.BaseName().AsUTF8Unsafe().c_str()))); |
+ } |
+ } |
#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 +504,67 @@ 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)); |
+ std::vector<std::pair<base::FilePath, std::string> > paths_and_overrides; |
+ for (std::vector<base::FilePath>::const_iterator it = paths.begin(); |
+ it != paths.end(); ++it) { |
+ paths_and_overrides.push_back(std::make_pair(*it, "")); |
} |
-#endif |
- content::BrowserThread::PostTask(content::BrowserThread::FILE, FROM_HERE, |
- base::Bind(&CheckLocalWritableFile, path, extension_->path(), on_success, |
- on_failure)); |
+ RegisterFileSystemsAndSendResponseWithIdOverrides( |
+ paths_and_overrides, entry_type, multiple); |
} |
-void FileSystemEntryFunction::RegisterFileSystemAndSendResponse( |
- const base::FilePath& path, EntryType entry_type) { |
- RegisterFileSystemAndSendResponseWithIdOverride(path, entry_type, ""); |
+void FileSystemEntryFunction::RegisterFileSystemsAndSendResponseWithIdOverrides( |
Matt Giuca
2013/08/01 00:02:24
Nit: Move this function below RegisterFileSystemAn
Sam McNally
2013/08/01 00:08:39
Done.
|
+ const std::vector<std::pair<base::FilePath, std::string> >& |
+ paths_and_overrides, |
+ 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<std::pair<base::FilePath, std::string> >::const_iterator it = |
+ paths_and_overrides.begin(); |
+ it != paths_and_overrides.end(); ++it) { |
+ list->Append(BuildEntryDict(it->first, entry_type, it->second)); |
+ } |
+ |
+ 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); |
+ std::vector<std::pair<base::FilePath, std::string> > paths_and_overrides; |
+ paths_and_overrides.push_back(std::make_pair(path, id)); |
+ RegisterFileSystemsAndSendResponseWithIdOverrides( |
+ paths_and_overrides, entry_type, false); |
+} |
+base::DictionaryValue* FileSystemEntryFunction::BuildEntryDict( |
+ 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 +572,19 @@ 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); |
- |
- SendResponse(true); |
+ return dict; |
} |
-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 +640,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 +659,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 +701,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 +717,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 +754,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 +781,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 +790,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 +805,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 +813,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 +847,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 +938,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 +983,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; |
} |