blob: e2b428fd60426db2e468e2dbe20a320a3fc39cf7 [file] [log] [blame]
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// The file contains the implementation of
6// fileBrowserHandlerInternal.selectFile extension function.
7// When invoked, the function does the following:
8// - Verifies that the extension function was invoked as a result of user
9// gesture.
10// - Display 'save as' dialog using FileSelectorImpl which waits for the user
11// feedback.
12// - Once the user selects the file path (or cancels the selection),
13// FileSelectorImpl notifies FileBrowserHandlerInternalSelectFileFunction of
14// the selection result by calling FileHandlerSelectFile::OnFilePathSelected.
15// - If the selection was canceled,
16// FileBrowserHandlerInternalSelectFileFunction returns reporting failure.
17// - If the file path was selected, the function opens external file system
18// needed to create FileEntry object for the selected path
19// (opening file system will create file system name and root url for the
20// caller's external file system).
21// - The function grants permissions needed to read/write/create file under the
22// selected path. To grant permissions to the caller, caller's extension ID
23// has to be allowed to access the files virtual path (e.g. /Downloads/foo)
Ben Murdoch7dbb3d52013-07-17 14:55:54 +010024// in ExternalFileSystemBackend. Additionally, the callers render
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010025// process ID has to be granted read, write and create permissions for the
26// selected file's full filesystem path (e.g.
27// /home/chronos/user/Downloads/foo) in ChildProcessSecurityPolicy.
28// - After the required file access permissions are granted, result object is
29// created and returned back.
30
31#include "chrome/browser/chromeos/extensions/file_manager/file_browser_handler_api.h"
32
33#include "base/bind.h"
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010034#include "base/files/file_path.h"
35#include "base/memory/scoped_ptr.h"
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +010036#include "base/message_loop/message_loop_proxy.h"
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010037#include "base/platform_file.h"
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +010038#include "chrome/browser/chromeos/extensions/file_manager/file_tasks.h"
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010039#include "chrome/browser/profiles/profile.h"
40#include "chrome/browser/ui/browser.h"
41#include "chrome/browser/ui/browser_window.h"
42#include "chrome/browser/ui/chrome_select_file_policy.h"
43#include "chrome/browser/ui/tabs/tab_strip_model.h"
44#include "chrome/common/extensions/api/file_browser_handler_internal.h"
45#include "content/public/browser/browser_thread.h"
46#include "content/public/browser/child_process_security_policy.h"
47#include "content/public/browser/render_process_host.h"
48#include "content/public/browser/render_view_host.h"
49#include "content/public/browser/storage_partition.h"
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010050#include "ui/shell_dialogs/select_file_dialog.h"
Ben Murdoch7dbb3d52013-07-17 14:55:54 +010051#include "webkit/browser/fileapi/file_system_backend.h"
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +010052#include "webkit/browser/fileapi/file_system_context.h"
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010053
54using content::BrowserContext;
55using content::BrowserThread;
56using extensions::api::file_browser_handler_internal::FileEntryInfo;
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +010057using file_manager::FileSelector;
58using file_manager::FileSelectorFactory;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010059
60namespace SelectFile =
61 extensions::api::file_browser_handler_internal::SelectFile;
62
63namespace {
64
65const char kNoUserGestureError[] =
66 "This method can only be called in response to user gesture, such as a "
67 "mouse click or key press.";
68
69// Converts file extensions to a ui::SelectFileDialog::FileTypeInfo.
70ui::SelectFileDialog::FileTypeInfo ConvertExtensionsToFileTypeInfo(
71 const std::vector<std::string>& extensions) {
72 ui::SelectFileDialog::FileTypeInfo file_type_info;
73
74 for (size_t i = 0; i < extensions.size(); ++i) {
75 base::FilePath::StringType allowed_extension =
76 base::FilePath::FromUTF8Unsafe(extensions[i]).value();
77
78 // FileTypeInfo takes a nested vector like [["htm", "html"], ["txt"]] to
79 // group equivalent extensions, but we don't use this feature here.
80 std::vector<base::FilePath::StringType> inner_vector;
81 inner_vector.push_back(allowed_extension);
82 file_type_info.extensions.push_back(inner_vector);
83 }
84
85 return file_type_info;
86}
87
88// File selector implementation.
89// When |SelectFile| is invoked, it will show save as dialog and listen for user
90// action. When user selects the file (or closes the dialog), the function's
91// |OnFilePathSelected| method will be called with the result.
92// SelectFile should be called only once, because the class instance takes
93// ownership of itself after the first call. It will delete itself after the
94// extension function is notified of file selection result.
95// Since the extension function object is ref counted, FileSelectorImpl holds
96// a reference to it to ensure that the extension function doesn't go away while
97// waiting for user action. The reference is released after the function is
98// notified of the selection result.
99class FileSelectorImpl : public FileSelector,
100 public ui::SelectFileDialog::Listener {
101 public:
102 explicit FileSelectorImpl();
103 virtual ~FileSelectorImpl() OVERRIDE;
104
105 protected:
Torne (Richard Coles)a36e5922013-08-05 13:57:33 +0100106 // file_manager::FileSelectr overrides.
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100107 // Shows save as dialog with suggested name in window bound to |browser|.
108 // |allowed_extensions| specifies the file extensions allowed to be shown,
109 // and selected. Extensions should not include '.'.
110 //
111 // After this method is called, the selector implementation should not be
112 // deleted by the caller. It will delete itself after it receives response
113 // from SelectFielDialog.
114 virtual void SelectFile(
115 const base::FilePath& suggested_name,
116 const std::vector<std::string>& allowed_extensions,
117 Browser* browser,
118 FileBrowserHandlerInternalSelectFileFunction* function) OVERRIDE;
119
120 // ui::SelectFileDialog::Listener overrides.
121 virtual void FileSelected(const base::FilePath& path,
122 int index,
123 void* params) OVERRIDE;
124 virtual void MultiFilesSelected(const std::vector<base::FilePath>& files,
125 void* params) OVERRIDE;
126 virtual void FileSelectionCanceled(void* params) OVERRIDE;
127
128 private:
129 // Initiates and shows 'save as' dialog which will be used to prompt user to
130 // select a file path. The initial selected file name in the dialog will be
131 // set to |suggested_name|. The dialog will be bound to the tab active in
132 // |browser|.
133 // |allowed_extensions| specifies the file extensions allowed to be shown,
134 // and selected. Extensions should not include '.'.
135 //
136 // Returns boolean indicating whether the dialog has been successfully shown
137 // to the user.
138 bool StartSelectFile(const base::FilePath& suggested_name,
139 const std::vector<std::string>& allowed_extensions,
140 Browser* browser);
141
142 // Reacts to the user action reported by the dialog and notifies |function_|
143 // about file selection result (by calling |OnFilePathSelected()|).
144 // The |this| object is self destruct after the function is notified.
Ben Murdochbb1529c2013-08-08 10:24:53 +0100145 // |success| indicates whether user has selected the file.
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100146 // |selected_path| is path that was selected. It is empty if the file wasn't
147 // selected.
148 void SendResponse(bool success, const base::FilePath& selected_path);
149
150 // Dialog that is shown by selector.
151 scoped_refptr<ui::SelectFileDialog> dialog_;
152
153 // Extension function that uses the selector.
154 scoped_refptr<FileBrowserHandlerInternalSelectFileFunction> function_;
155
156 DISALLOW_COPY_AND_ASSIGN(FileSelectorImpl);
157};
158
159FileSelectorImpl::FileSelectorImpl() {}
160
161FileSelectorImpl::~FileSelectorImpl() {
162 if (dialog_.get())
163 dialog_->ListenerDestroyed();
164 // Send response if needed.
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100165 if (function_.get())
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100166 SendResponse(false, base::FilePath());
167}
168
169void FileSelectorImpl::SelectFile(
170 const base::FilePath& suggested_name,
171 const std::vector<std::string>& allowed_extensions,
172 Browser* browser,
173 FileBrowserHandlerInternalSelectFileFunction* function) {
174 // We will hold reference to the function until it is notified of selection
175 // result.
176 function_ = function;
177
178 if (!StartSelectFile(suggested_name, allowed_extensions, browser)) {
179 // If the dialog wasn't launched, let's asynchronously report failure to the
180 // function.
181 base::MessageLoopProxy::current()->PostTask(FROM_HERE,
182 base::Bind(&FileSelectorImpl::FileSelectionCanceled,
Ben Murdocheb525c52013-07-10 11:40:50 +0100183 base::Unretained(this), static_cast<void*>(NULL)));
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100184 }
185}
186
187bool FileSelectorImpl::StartSelectFile(
188 const base::FilePath& suggested_name,
189 const std::vector<std::string>& allowed_extensions,
190 Browser* browser) {
191 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
192 DCHECK(!dialog_.get());
193 DCHECK(browser);
194
195 if (!browser->window())
196 return false;
197
198 content::WebContents* web_contents =
199 browser->tab_strip_model()->GetActiveWebContents();
200 if (!web_contents)
201 return false;
202
203 dialog_ = ui::SelectFileDialog::Create(
204 this, new ChromeSelectFilePolicy(web_contents));
205
206 // Convert |allowed_extensions| to ui::SelectFileDialog::FileTypeInfo.
207 ui::SelectFileDialog::FileTypeInfo allowed_file_info =
208 ConvertExtensionsToFileTypeInfo(allowed_extensions);
209 allowed_file_info.support_drive = true;
210
211 dialog_->SelectFile(ui::SelectFileDialog::SELECT_SAVEAS_FILE,
212 string16() /* dialog title*/,
213 suggested_name,
214 &allowed_file_info,
215 0 /* file type index */,
216 std::string() /* default file extension */,
217 browser->window()->GetNativeWindow(), NULL /* params */);
218
219 return dialog_->IsRunning(browser->window()->GetNativeWindow());
220}
221
222void FileSelectorImpl::FileSelected(
223 const base::FilePath& path, int index, void* params) {
224 SendResponse(true, path);
225 delete this;
226}
227
228void FileSelectorImpl::MultiFilesSelected(
229 const std::vector<base::FilePath>& files,
230 void* params) {
231 // Only single file should be selected in save-as dialog.
232 NOTREACHED();
233}
234
235void FileSelectorImpl::FileSelectionCanceled(
236 void* params) {
237 SendResponse(false, base::FilePath());
238 delete this;
239}
240
241void FileSelectorImpl::SendResponse(bool success,
242 const base::FilePath& selected_path) {
243 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
244
245 // We don't want to send multiple responses.
246 if (function_.get())
247 function_->OnFilePathSelected(success, selected_path);
248 function_ = NULL;
249}
250
251// FileSelectorFactory implementation.
252class FileSelectorFactoryImpl : public FileSelectorFactory {
253 public:
254 FileSelectorFactoryImpl() {}
255 virtual ~FileSelectorFactoryImpl() {}
256
257 // FileSelectorFactory implementation.
258 // Creates new FileSelectorImplementation for the function.
259 virtual FileSelector* CreateFileSelector() const OVERRIDE {
260 return new FileSelectorImpl();
261 }
262
263 private:
264 DISALLOW_COPY_AND_ASSIGN(FileSelectorFactoryImpl);
265};
266
267typedef base::Callback<void (bool success,
268 const std::string& file_system_name,
269 const GURL& file_system_root)>
270 FileSystemOpenCallback;
271
272// Relays callback from file system open operation by translating file error
273// returned by the operation to success boolean.
274void RunOpenFileSystemCallback(
275 const FileSystemOpenCallback& callback,
276 base::PlatformFileError error,
277 const std::string& file_system_name,
278 const GURL& file_system_root) {
279 bool success = (error == base::PLATFORM_FILE_OK);
280 callback.Run(success, file_system_name, file_system_root);
281}
282
283} // namespace
284
285FileBrowserHandlerInternalSelectFileFunction::
286 FileBrowserHandlerInternalSelectFileFunction()
287 : file_selector_factory_(new FileSelectorFactoryImpl()),
288 user_gesture_check_enabled_(true) {
289}
290
291FileBrowserHandlerInternalSelectFileFunction::
292 FileBrowserHandlerInternalSelectFileFunction(
293 FileSelectorFactory* file_selector_factory,
294 bool enable_user_gesture_check)
295 : file_selector_factory_(file_selector_factory),
296 user_gesture_check_enabled_(enable_user_gesture_check) {
297 DCHECK(file_selector_factory);
298}
299
300FileBrowserHandlerInternalSelectFileFunction::
301 ~FileBrowserHandlerInternalSelectFileFunction() {}
302
303bool FileBrowserHandlerInternalSelectFileFunction::RunImpl() {
304 scoped_ptr<SelectFile::Params> params(SelectFile::Params::Create(*args_));
305
306 base::FilePath suggested_name(params->selection_params.suggested_name);
307 std::vector<std::string> allowed_extensions;
308 if (params->selection_params.allowed_file_extensions.get())
309 allowed_extensions = *params->selection_params.allowed_file_extensions;
310
311 if (!user_gesture() && user_gesture_check_enabled_) {
312 error_ = kNoUserGestureError;
313 return false;
314 }
315
316 FileSelector* file_selector = file_selector_factory_->CreateFileSelector();
317 file_selector->SelectFile(suggested_name.BaseName(),
318 allowed_extensions,
319 GetCurrentBrowser(),
320 this);
321 return true;
322}
323
324void FileBrowserHandlerInternalSelectFileFunction::OnFilePathSelected(
325 bool success,
326 const base::FilePath& full_path) {
327 if (!success) {
328 Respond(false);
329 return;
330 }
331
332 full_path_ = full_path;
333
334 // We have to open file system in order to create a FileEntry object for the
335 // selected file path.
336 content::SiteInstance* site_instance = render_view_host()->GetSiteInstance();
337 BrowserContext::GetStoragePartition(profile_, site_instance)->
338 GetFileSystemContext()->OpenFileSystem(
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100339 source_url_.GetOrigin(), fileapi::kFileSystemTypeExternal,
340 fileapi::OPEN_FILE_SYSTEM_FAIL_IF_NONEXISTENT,
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100341 base::Bind(
342 &RunOpenFileSystemCallback,
343 base::Bind(&FileBrowserHandlerInternalSelectFileFunction::
344 OnFileSystemOpened,
345 this)));
346};
347
348void FileBrowserHandlerInternalSelectFileFunction::OnFileSystemOpened(
349 bool success,
350 const std::string& file_system_name,
351 const GURL& file_system_root) {
352 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
353
354 if (!success) {
355 Respond(false);
356 return;
357 }
358
359 // Remember opened file system's parameters.
360 file_system_name_ = file_system_name;
361 file_system_root_ = file_system_root;
362
363 GrantPermissions();
364
365 Respond(true);
366}
367
368void FileBrowserHandlerInternalSelectFileFunction::GrantPermissions() {
369 content::SiteInstance* site_instance = render_view_host()->GetSiteInstance();
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100370 fileapi::ExternalFileSystemBackend* external_backend =
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100371 BrowserContext::GetStoragePartition(profile_, site_instance)->
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100372 GetFileSystemContext()->external_backend();
373 DCHECK(external_backend);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100374
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100375 external_backend->GetVirtualPath(full_path_, &virtual_path_);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100376 DCHECK(!virtual_path_.empty());
377
378 // Grant access to this particular file to target extension. This will
379 // ensure that the target extension can access only this FS entry and
380 // prevent from traversing FS hierarchy upward.
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100381 external_backend->GrantFileAccessToExtension(extension_id(), virtual_path_);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100382
383 // Grant access to the selected file to target extensions render view process.
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100384 content::ChildProcessSecurityPolicy::GetInstance()->GrantCreateReadWriteFile(
385 render_view_host()->GetProcess()->GetID(), full_path_);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100386}
387
388void FileBrowserHandlerInternalSelectFileFunction::Respond(bool success) {
389 scoped_ptr<SelectFile::Results::Result> result(
390 new SelectFile::Results::Result());
391 result->success = success;
392
393 // If the file was selected, add 'entry' object which will be later used to
394 // create a FileEntry instance for the selected file.
395 if (success) {
396 result->entry.reset(new FileEntryInfo());
397 result->entry->file_system_name = file_system_name_;
398 result->entry->file_system_root = file_system_root_.spec();
Ben Murdocheb525c52013-07-10 11:40:50 +0100399 result->entry->file_full_path = "/" + virtual_path_.AsUTF8Unsafe();
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100400 result->entry->file_is_directory = false;
401 }
402
403 results_ = SelectFile::Results::Create(*result);
404 SendResponse(true);
405}