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
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
|
// 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);
}
|