blob: 9814bb61595d5c34b24831d27fc86d4e17a18c46 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// The file contains the implementation of
// fileBrowserHandlerInternal.selectFile extension function.
// When invoked, the function does the following:
// - Verifies that the extension function was invoked as a result of user
// gesture.
// - Display 'save as' dialog using FileSelectorImpl which waits for the user
// feedback.
// - Once the user selects the file path (or cancels the selection),
// FileSelectorImpl notifies FileHandlerSelectFileFunction of the selection
// result by calling FileHandlerSelectFile::OnFilePathSelected.
// - If the selection was canceled, FileHandlerSelectFileFunction returns
// reporting failure.
// - If the file path was selected, the function opens external file system
// needed to create FileEntry object for the selected path
// (opening file system will create file system name and root url for the
// caller's external file system).
// - The function grants permissions needed to read/write/create file under the
// selected path. To grant permissions to the caller, caller's extension ID
// has to be allowed to access the files virtual path (e.g. /Downloads/foo)
// in ExternalFileSystemMountPointProvider. Additionally, the callers render
// process ID has to be granted read, write and create permissions for the
// selected file's full filesystem path (e.g.
// /home/chronos/user/Downloads/foo) in ChildProcessSecurityPolicy.
// - If the selected file path is on drive mount point, read access permissions
// for file's possible local drive cache paths have to be granted to caller's
// render process ID in ChildProcessSecurityPolicy.
// - After the required file access permissions are granted, result object is
// created and returned back.
#include "chrome/browser/chromeos/extensions/file_browser_handler_api.h"
#include "base/bind.h"
#include "base/file_path.h"
#include "base/file_util.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop_proxy.h"
#include "base/platform_file.h"
#include "base/values.h"
#include "chrome/browser/chromeos/extensions/file_handler_util.h"
#include "chrome/browser/chromeos/extensions/file_manager_util.h"
#include "chrome/browser/chromeos/gdata/drive_file_system_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/chrome_select_file_policy.h"
#include "chrome/browser/ui/tab_contents/tab_contents.h"
#include "chrome/common/extensions/api/file_browser_handler_internal.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/child_process_security_policy.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/storage_partition.h"
#include "googleurl/src/gurl.h"
#include "webkit/fileapi/file_system_context.h"
#include "webkit/fileapi/file_system_mount_point_provider.h"
#include "ui/base/dialogs/select_file_dialog.h"
using content::BrowserContext;
using content::BrowserThread;
using extensions::api::file_browser_handler_internal::FileEntryInfo;
using file_handler::FileSelector;
using file_handler::FileSelectorFactory;
namespace SelectFile =
extensions::api::file_browser_handler_internal::SelectFile;
namespace {
const char kNoUserGestureError[] =
"This method can only be called in response to user gesture, such as a "
"mouse click or key press.";
// Converts file extensions to a ui::SelectFileDialog::FileTypeInfo.
ui::SelectFileDialog::FileTypeInfo ConvertExtensionsToFileTypeInfo(
const std::vector<std::string>& extensions) {
ui::SelectFileDialog::FileTypeInfo file_type_info;
for (size_t i = 0; i < extensions.size(); ++i) {
FilePath::StringType allowed_extension =
FilePath::FromUTF8Unsafe(extensions[i]).value();
// FileTypeInfo takes a nested vector like [["htm", "html"], ["txt"]] to
// group equivalent extensions, but we don't use this feature here.
std::vector<FilePath::StringType> inner_vector;
inner_vector.push_back(allowed_extension);
file_type_info.extensions.push_back(inner_vector);
}
return file_type_info;
}
// File selector implementation.
// When |SelectFile| is invoked, it will show save as dialog and listen for user
// action. When user selects the file (or closes the dialog), the function's
// |OnFilePathSelected| method will be called with the result.
// SelectFile should be called only once, because the class instance takes
// ownership of itself after the first call. It will delete itself after the
// extension function is notified of file selection result.
// Since the extension function object is ref counted, FileSelectorImpl holds
// a reference to it to ensure that the extension function doesn't go away while
// waiting for user action. The reference is released after the function is
// notified of the selection result.
class FileSelectorImpl : public FileSelector,
public ui::SelectFileDialog::Listener {
public:
explicit FileSelectorImpl();
virtual ~FileSelectorImpl() OVERRIDE;
protected:
// file_handler::FileSelectr overrides.
// Shows save as dialog with suggested name in window bound to |browser|.
// |allowed_extensions| specifies the file extensions allowed to be shown,
// and selected. Extensions should not include '.'.
//
// After this method is called, the selector implementation should not be
// deleted by the caller. It will delete itself after it receives response
// from SelectFielDialog.
virtual void SelectFile(const FilePath& suggested_name,
const std::vector<std::string>& allowed_extensions,
Browser* browser,
FileHandlerSelectFileFunction* function) OVERRIDE;
// ui::SelectFileDialog::Listener overrides.
virtual void FileSelected(const FilePath& path,
int index,
void* params) OVERRIDE;
virtual void MultiFilesSelected(const std::vector<FilePath>& files,
void* params) OVERRIDE;
virtual void FileSelectionCanceled(void* params) OVERRIDE;
private:
// Initiates and shows 'save as' dialog which will be used to prompt user to
// select a file path. The initial selected file name in the dialog will be
// set to |suggested_name|. The dialog will be bound to the tab active in
// |browser|.
// |allowed_extensions| specifies the file extensions allowed to be shown,
// and selected. Extensions should not include '.'.
//
// Returns boolean indicating whether the dialog has been successfully shown
// to the user.
bool StartSelectFile(const FilePath& suggested_name,
const std::vector<std::string>& allowed_extensions,
Browser* browser);
// Reacts to the user action reported by the dialog and notifies |function_|
// about file selection result (by calling |OnFilePathSelected()|).
// The |this| object is self destruct after the function is notified.
// |success| indicates whether user has selectd the file.
// |selected_path| is path that was selected. It is empty if the file wasn't
// selected.
void SendResponse(bool success, const FilePath& selected_path);
// Dialog that is shown by selector.
scoped_refptr<ui::SelectFileDialog> dialog_;
// Extension function that uses the selector.
scoped_refptr<FileHandlerSelectFileFunction> function_;
DISALLOW_COPY_AND_ASSIGN(FileSelectorImpl);
};
FileSelectorImpl::FileSelectorImpl() {}
FileSelectorImpl::~FileSelectorImpl() {
if (dialog_.get())
dialog_->ListenerDestroyed();
// Send response if needed.
if (function_)
SendResponse(false, FilePath());
}
void FileSelectorImpl::SelectFile(
const FilePath& suggested_name,
const std::vector<std::string>& allowed_extensions,
Browser* browser,
FileHandlerSelectFileFunction* function) {
// We will hold reference to the function until it is notified of selection
// result.
function_ = function;
if (!StartSelectFile(suggested_name, allowed_extensions, browser)) {
// If the dialog wasn't launched, let's asynchronously report failure to the
// function.
base::MessageLoopProxy::current()->PostTask(FROM_HERE,
base::Bind(&FileSelectorImpl::FileSelectionCanceled,
base::Unretained(this), reinterpret_cast<void*>(NULL)));
}
}
bool FileSelectorImpl::StartSelectFile(
const FilePath& suggested_name,
const std::vector<std::string>& allowed_extensions,
Browser* browser) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DCHECK(!dialog_.get());
DCHECK(browser);
if (!browser->window())
return false;
TabContents* tab_contents = chrome::GetActiveTabContents(browser);
if (!tab_contents)
return false;
dialog_ = ui::SelectFileDialog::Create(
this, new ChromeSelectFilePolicy(tab_contents->web_contents()));
// Convert |allowed_extensions| to ui::SelectFileDialog::FileTypeInfo.
ui::SelectFileDialog::FileTypeInfo allowed_file_info =
ConvertExtensionsToFileTypeInfo(allowed_extensions);
dialog_->SelectFile(ui::SelectFileDialog::SELECT_SAVEAS_FILE,
string16() /* dialog title*/,
suggested_name,
&allowed_file_info,
0 /* file type index */,
std::string() /* default file extension */,
browser->window()->GetNativeWindow(), NULL /* params */);
return dialog_->IsRunning(browser->window()->GetNativeWindow());
}
void FileSelectorImpl::FileSelected(
const FilePath& path, int index, void* params) {
SendResponse(true, path);
delete this;
}
void FileSelectorImpl::MultiFilesSelected(
const std::vector<FilePath>& files,
void* params) {
// Only single file should be selected in save-as dialog.
NOTREACHED();
}
void FileSelectorImpl::FileSelectionCanceled(
void* params) {
SendResponse(false, FilePath());
delete this;
}
void FileSelectorImpl::SendResponse(bool success,
const FilePath& selected_path) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
// We don't want to send multiple responses.
if (function_.get())
function_->OnFilePathSelected(success, selected_path);
function_ = NULL;
}
// FileSelectorFactory implementation.
class FileSelectorFactoryImpl : public FileSelectorFactory {
public:
FileSelectorFactoryImpl() {}
virtual ~FileSelectorFactoryImpl() {}
// FileSelectorFactory implementation.
// Creates new FileSelectorImplementation for the function.
virtual FileSelector* CreateFileSelector() const OVERRIDE {
return new FileSelectorImpl();
}
private:
DISALLOW_COPY_AND_ASSIGN(FileSelectorFactoryImpl);
};
typedef base::Callback<void (bool success,
const std::string& file_system_name,
const GURL& file_system_root)>
FileSystemOpenCallback;
// Relays callback from file system open operation by translating file error
// returned by the operation to success boolean.
void RunOpenFileSystemCallback(
const FileSystemOpenCallback& callback,
base::PlatformFileError error,
const std::string& file_system_name,
const GURL& file_system_root) {
bool success = (error == base::PLATFORM_FILE_OK);
callback.Run(success, file_system_name, file_system_root);
}
} // namespace
FileHandlerSelectFileFunction::FileHandlerSelectFileFunction()
: file_selector_factory_(new FileSelectorFactoryImpl()),
user_gesture_check_enabled_(true) {
}
FileHandlerSelectFileFunction::FileHandlerSelectFileFunction(
FileSelectorFactory* file_selector_factory,
bool enable_user_gesture_check)
: file_selector_factory_(file_selector_factory),
user_gesture_check_enabled_(enable_user_gesture_check) {
DCHECK(file_selector_factory);
}
FileHandlerSelectFileFunction::~FileHandlerSelectFileFunction() {}
bool FileHandlerSelectFileFunction::RunImpl() {
scoped_ptr<SelectFile::Params> params(SelectFile::Params::Create(*args_));
FilePath suggested_name(params->selection_params.suggested_name);
std::vector<std::string> allowed_extensions;
if (params->selection_params.allowed_file_extensions.get())
allowed_extensions = *params->selection_params.allowed_file_extensions;
if (!user_gesture() && user_gesture_check_enabled_) {
error_ = kNoUserGestureError;
return false;
}
FileSelector* file_selector = file_selector_factory_->CreateFileSelector();
file_selector->SelectFile(suggested_name.BaseName(),
allowed_extensions,
GetCurrentBrowser(),
this);
return true;
}
void FileHandlerSelectFileFunction::OnFilePathSelected(
bool success,
const FilePath& full_path) {
if (!success) {
Respond(false);
return;
}
full_path_ = full_path;
// We have to open file system in order to create a FileEntry object for the
// selected file path.
BrowserContext::GetDefaultStoragePartition(profile_)->
GetFileSystemContext()->OpenFileSystem(
source_url_.GetOrigin(), fileapi::kFileSystemTypeExternal, false,
base::Bind(
&RunOpenFileSystemCallback,
base::Bind(&FileHandlerSelectFileFunction::OnFileSystemOpened,
this)));
};
void FileHandlerSelectFileFunction::OnFileSystemOpened(
bool success,
const std::string& file_system_name,
const GURL& file_system_root) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (!success) {
Respond(false);
return;
}
// Remember opened file system's parameters.
file_system_name_ = file_system_name;
file_system_root_ = file_system_root;
GrantPermissions();
}
void FileHandlerSelectFileFunction::GrantPermissions() {
fileapi::ExternalFileSystemMountPointProvider* external_provider =
BrowserContext::GetDefaultStoragePartition(profile_)->
GetFileSystemContext()->external_provider();
DCHECK(external_provider);
external_provider->GetVirtualPath(full_path_, &virtual_path_);
DCHECK(!virtual_path_.empty());
// Grant access to this particular file to target extension. This will
// ensure that the target extension can access only this FS entry and
// prevent from traversing FS hierarchy upward.
external_provider->GrantFileAccessToExtension(extension_id(), virtual_path_);
// Add read write permissions for the selected file's virtual path to the list
// of permissions that have to be granted.
permissions_to_grant_.push_back(std::make_pair(
full_path_,
file_handler_util::GetReadWritePermissions()));
if (!gdata::util::IsUnderDriveMountPoint(full_path_)) {
// If the file is not on drive, we have to only grant permission for the
// file's virtual path.
OnGotPermissionsToGrant();
return;
}
// For drive files, we also have to grant permissions for drive cache paths
// under which the selected path could be kept.
scoped_ptr<std::vector<FilePath> > gdata_paths(new std::vector<FilePath>());
gdata_paths->push_back(virtual_path_);
gdata::util::InsertDriveCachePathsPermissions(
profile(),
gdata_paths.Pass(),
&permissions_to_grant_,
base::Bind(&FileHandlerSelectFileFunction::OnGotPermissionsToGrant,
this));
}
void FileHandlerSelectFileFunction::OnGotPermissionsToGrant() {
// At this point all needed permissions should be collected, so let's grant
// them.
for (size_t i = 0; i < permissions_to_grant_.size(); i++) {
content::ChildProcessSecurityPolicy::GetInstance()->GrantPermissionsForFile(
render_view_host()->GetProcess()->GetID(),
permissions_to_grant_[i].first,
permissions_to_grant_[i].second);
}
Respond(true);
}
void FileHandlerSelectFileFunction::Respond(bool success) {
scoped_ptr<SelectFile::Results::Result> result(
new SelectFile::Results::Result());
result->success = success;
// If the file was selected, add 'entry' object which will be later used to
// create a FileEntry instance for the selected file.
if (success) {
result->entry.reset(new FileEntryInfo());
result->entry->file_system_name = file_system_name_;
result->entry->file_system_root = file_system_root_.spec();
result->entry->file_full_path = "/" + virtual_path_.value();
result->entry->file_is_directory = false;
}
results_ = SelectFile::Results::Create(*result);
SendResponse(true);
}