1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374
|
// 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 FileBrowserHandlerInternalSelectFileFunction of
// the selection result by calling FileHandlerSelectFile::OnFilePathSelected.
// - If the selection was canceled,
// FileBrowserHandlerInternalSelectFileFunction 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 ExternalFileSystemBackend. 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.
// - After the required file access permissions are granted, result object is
// created and returned back.
#include "chrome/browser/chromeos/extensions/file_manager/file_browser_handler_api.h"
#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop/message_loop_proxy.h"
#include "chrome/browser/chromeos/file_manager/fileapi_util.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/chrome_select_file_policy.h"
#include "chrome/browser/ui/tabs/tab_strip_model.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 "storage/browser/fileapi/file_system_backend.h"
#include "storage/browser/fileapi/file_system_context.h"
#include "storage/common/fileapi/file_system_info.h"
#include "storage/common/fileapi/file_system_util.h"
#include "ui/shell_dialogs/select_file_dialog.h"
using content::BrowserThread;
using extensions::api::file_browser_handler_internal::FileEntryInfo;
using file_manager::FileSelector;
using file_manager::FileSelectorFactory;
using file_manager::util::EntryDefinition;
using file_manager::util::FileDefinition;
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) {
base::FilePath::StringType allowed_extension =
base::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<base::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:
FileSelectorImpl();
virtual ~FileSelectorImpl() override;
protected:
// file_manager::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 base::FilePath& suggested_name,
const std::vector<std::string>& allowed_extensions,
Browser* browser,
FileBrowserHandlerInternalSelectFileFunction* function) override;
// ui::SelectFileDialog::Listener overrides.
virtual void FileSelected(const base::FilePath& path,
int index,
void* params) override;
virtual void MultiFilesSelected(const std::vector<base::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 base::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 selected the file.
// |selected_path| is path that was selected. It is empty if the file wasn't
// selected.
void SendResponse(bool success, const base::FilePath& selected_path);
// Dialog that is shown by selector.
scoped_refptr<ui::SelectFileDialog> dialog_;
// Extension function that uses the selector.
scoped_refptr<FileBrowserHandlerInternalSelectFileFunction> function_;
DISALLOW_COPY_AND_ASSIGN(FileSelectorImpl);
};
FileSelectorImpl::FileSelectorImpl() {}
FileSelectorImpl::~FileSelectorImpl() {
if (dialog_.get())
dialog_->ListenerDestroyed();
// Send response if needed.
if (function_.get())
SendResponse(false, base::FilePath());
}
void FileSelectorImpl::SelectFile(
const base::FilePath& suggested_name,
const std::vector<std::string>& allowed_extensions,
Browser* browser,
FileBrowserHandlerInternalSelectFileFunction* 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), static_cast<void*>(NULL)));
}
}
bool FileSelectorImpl::StartSelectFile(
const base::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;
content::WebContents* web_contents =
browser->tab_strip_model()->GetActiveWebContents();
if (!web_contents)
return false;
dialog_ = ui::SelectFileDialog::Create(
this, new ChromeSelectFilePolicy(web_contents));
// Convert |allowed_extensions| to ui::SelectFileDialog::FileTypeInfo.
ui::SelectFileDialog::FileTypeInfo allowed_file_info =
ConvertExtensionsToFileTypeInfo(allowed_extensions);
allowed_file_info.support_drive = true;
dialog_->SelectFile(ui::SelectFileDialog::SELECT_SAVEAS_FILE,
base::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 base::FilePath& path, int index, void* params) {
SendResponse(true, path);
delete this;
}
void FileSelectorImpl::MultiFilesSelected(
const std::vector<base::FilePath>& files,
void* params) {
// Only single file should be selected in save-as dialog.
NOTREACHED();
}
void FileSelectorImpl::FileSelectionCanceled(
void* params) {
SendResponse(false, base::FilePath());
delete this;
}
void FileSelectorImpl::SendResponse(bool success,
const base::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);
};
} // namespace
FileBrowserHandlerInternalSelectFileFunction::
FileBrowserHandlerInternalSelectFileFunction()
: file_selector_factory_(new FileSelectorFactoryImpl()),
user_gesture_check_enabled_(true) {
}
FileBrowserHandlerInternalSelectFileFunction::
FileBrowserHandlerInternalSelectFileFunction(
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);
}
FileBrowserHandlerInternalSelectFileFunction::
~FileBrowserHandlerInternalSelectFileFunction() {}
bool FileBrowserHandlerInternalSelectFileFunction::RunAsync() {
scoped_ptr<SelectFile::Params> params(SelectFile::Params::Create(*args_));
base::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_) {
SetError(kNoUserGestureError);
return false;
}
FileSelector* file_selector = file_selector_factory_->CreateFileSelector();
file_selector->SelectFile(suggested_name.BaseName(),
allowed_extensions,
GetCurrentBrowser(),
this);
return true;
}
void FileBrowserHandlerInternalSelectFileFunction::OnFilePathSelected(
bool success,
const base::FilePath& full_path) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (!success) {
Respond(EntryDefinition(), false);
return;
}
storage::ExternalFileSystemBackend* external_backend =
file_manager::util::GetFileSystemContextForRenderViewHost(
GetProfile(), render_view_host())->external_backend();
DCHECK(external_backend);
FileDefinition file_definition;
file_definition.is_directory = false;
external_backend->GetVirtualPath(full_path, &file_definition.virtual_path);
DCHECK(!file_definition.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_backend->GrantFileAccessToExtension(extension_id(),
file_definition.virtual_path);
// Grant access to the selected file to target extensions render view process.
content::ChildProcessSecurityPolicy::GetInstance()->GrantCreateReadWriteFile(
render_view_host()->GetProcess()->GetID(), full_path);
file_manager::util::ConvertFileDefinitionToEntryDefinition(
GetProfile(),
extension_id(),
file_definition,
base::Bind(
&FileBrowserHandlerInternalSelectFileFunction::RespondEntryDefinition,
this));
}
void FileBrowserHandlerInternalSelectFileFunction::RespondEntryDefinition(
const EntryDefinition& entry_definition) {
Respond(entry_definition, true);
}
void FileBrowserHandlerInternalSelectFileFunction::Respond(
const EntryDefinition& entry_definition,
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 && entry_definition.error == base::File::FILE_OK) {
result->entry.reset(new FileEntryInfo());
// TODO(mtomasz): Make the response fields consistent with other files.
result->entry->file_system_name = entry_definition.file_system_name;
result->entry->file_system_root = entry_definition.file_system_root_url;
result->entry->file_full_path =
"/" + entry_definition.full_path.AsUTF8Unsafe();
result->entry->file_is_directory = entry_definition.is_directory;
}
results_ = SelectFile::Results::Create(*result);
SendResponse(true);
}
|