file_browser_handler_api.cc revision a36e5920737c6adbddd3e43b760e5de8431db6e0
1// 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)
24//    in ExternalFileSystemBackend. Additionally, the callers render
25//    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"
34#include "base/files/file_path.h"
35#include "base/memory/scoped_ptr.h"
36#include "base/message_loop/message_loop_proxy.h"
37#include "base/platform_file.h"
38#include "chrome/browser/chromeos/extensions/file_manager/file_tasks.h"
39#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"
50#include "ui/shell_dialogs/select_file_dialog.h"
51#include "webkit/browser/fileapi/file_system_backend.h"
52#include "webkit/browser/fileapi/file_system_context.h"
53
54using content::BrowserContext;
55using content::BrowserThread;
56using extensions::api::file_browser_handler_internal::FileEntryInfo;
57using file_manager::FileSelector;
58using file_manager::FileSelectorFactory;
59
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:
106  // file_manager::FileSelectr overrides.
107  // 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.
145  // |success| indicates whether user has selectd the file.
146  // |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.
165  if (function_.get())
166    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,
183                   base::Unretained(this), static_cast<void*>(NULL)));
184  }
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(
339          source_url_.GetOrigin(), fileapi::kFileSystemTypeExternal,
340          fileapi::OPEN_FILE_SYSTEM_FAIL_IF_NONEXISTENT,
341          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();
370  fileapi::ExternalFileSystemBackend* external_backend =
371      BrowserContext::GetStoragePartition(profile_, site_instance)->
372      GetFileSystemContext()->external_backend();
373  DCHECK(external_backend);
374
375  external_backend->GetVirtualPath(full_path_, &virtual_path_);
376  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.
381  external_backend->GrantFileAccessToExtension(extension_id(), virtual_path_);
382
383  // Grant access to the selected file to target extensions render view process.
384  content::ChildProcessSecurityPolicy::GetInstance()->GrantCreateReadWriteFile(
385      render_view_host()->GetProcess()->GetID(), full_path_);
386}
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();
399    result->entry->file_full_path = "/" + virtual_path_.AsUTF8Unsafe();
400    result->entry->file_is_directory = false;
401  }
402
403  results_ = SelectFile::Results::Create(*result);
404  SendResponse(true);
405}
406