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