Home | History | Annotate | Download | only in file_manager
      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 
     54 using content::BrowserContext;
     55 using content::BrowserThread;
     56 using extensions::api::file_browser_handler_internal::FileEntryInfo;
     57 using file_manager::FileSelector;
     58 using file_manager::FileSelectorFactory;
     59 
     60 namespace SelectFile =
     61     extensions::api::file_browser_handler_internal::SelectFile;
     62 
     63 namespace {
     64 
     65 const 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.
     70 ui::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.
     99 class 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 selected 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 
    159 FileSelectorImpl::FileSelectorImpl() {}
    160 
    161 FileSelectorImpl::~FileSelectorImpl() {
    162   if (dialog_.get())
    163     dialog_->ListenerDestroyed();
    164   // Send response if needed.
    165   if (function_.get())
    166     SendResponse(false, base::FilePath());
    167 }
    168 
    169 void 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 
    187 bool 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 
    222 void FileSelectorImpl::FileSelected(
    223     const base::FilePath& path, int index, void* params) {
    224   SendResponse(true, path);
    225   delete this;
    226 }
    227 
    228 void 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 
    235 void FileSelectorImpl::FileSelectionCanceled(
    236     void* params) {
    237   SendResponse(false, base::FilePath());
    238   delete this;
    239 }
    240 
    241 void 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.
    252 class 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 
    267 typedef 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.
    274 void 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 
    285 FileBrowserHandlerInternalSelectFileFunction::
    286     FileBrowserHandlerInternalSelectFileFunction()
    287         : file_selector_factory_(new FileSelectorFactoryImpl()),
    288           user_gesture_check_enabled_(true) {
    289 }
    290 
    291 FileBrowserHandlerInternalSelectFileFunction::
    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 
    300 FileBrowserHandlerInternalSelectFileFunction::
    301     ~FileBrowserHandlerInternalSelectFileFunction() {}
    302 
    303 bool 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 
    324 void 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 
    348 void 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 
    368 void 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 
    388 void 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