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.
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
10 // - Display 'save as' dialog using FileSelectorImpl which waits for the user
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.
31 #include "chrome/browser/chromeos/extensions/file_manager/file_browser_handler_api.h"
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/file_manager/fileapi_util.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 #include "webkit/common/fileapi/file_system_info.h"
54 #include "webkit/common/fileapi/file_system_util.h"
56 using content::BrowserContext;
57 using content::BrowserThread;
58 using extensions::api::file_browser_handler_internal::FileEntryInfo;
59 using file_manager::FileSelector;
60 using file_manager::FileSelectorFactory;
62 namespace SelectFile =
63 extensions::api::file_browser_handler_internal::SelectFile;
67 const char kNoUserGestureError[] =
68 "This method can only be called in response to user gesture, such as a "
69 "mouse click or key press.";
71 // Converts file extensions to a ui::SelectFileDialog::FileTypeInfo.
72 ui::SelectFileDialog::FileTypeInfo ConvertExtensionsToFileTypeInfo(
73 const std::vector<std::string>& extensions) {
74 ui::SelectFileDialog::FileTypeInfo file_type_info;
76 for (size_t i = 0; i < extensions.size(); ++i) {
77 base::FilePath::StringType allowed_extension =
78 base::FilePath::FromUTF8Unsafe(extensions[i]).value();
80 // FileTypeInfo takes a nested vector like [["htm", "html"], ["txt"]] to
81 // group equivalent extensions, but we don't use this feature here.
82 std::vector<base::FilePath::StringType> inner_vector;
83 inner_vector.push_back(allowed_extension);
84 file_type_info.extensions.push_back(inner_vector);
87 return file_type_info;
90 // File selector implementation.
91 // When |SelectFile| is invoked, it will show save as dialog and listen for user
92 // action. When user selects the file (or closes the dialog), the function's
93 // |OnFilePathSelected| method will be called with the result.
94 // SelectFile should be called only once, because the class instance takes
95 // ownership of itself after the first call. It will delete itself after the
96 // extension function is notified of file selection result.
97 // Since the extension function object is ref counted, FileSelectorImpl holds
98 // a reference to it to ensure that the extension function doesn't go away while
99 // waiting for user action. The reference is released after the function is
100 // notified of the selection result.
101 class FileSelectorImpl : public FileSelector,
102 public ui::SelectFileDialog::Listener {
104 explicit FileSelectorImpl();
105 virtual ~FileSelectorImpl() OVERRIDE;
108 // file_manager::FileSelectr overrides.
109 // Shows save as dialog with suggested name in window bound to |browser|.
110 // |allowed_extensions| specifies the file extensions allowed to be shown,
111 // and selected. Extensions should not include '.'.
113 // After this method is called, the selector implementation should not be
114 // deleted by the caller. It will delete itself after it receives response
115 // from SelectFielDialog.
116 virtual void SelectFile(
117 const base::FilePath& suggested_name,
118 const std::vector<std::string>& allowed_extensions,
120 FileBrowserHandlerInternalSelectFileFunction* function) OVERRIDE;
122 // ui::SelectFileDialog::Listener overrides.
123 virtual void FileSelected(const base::FilePath& path,
125 void* params) OVERRIDE;
126 virtual void MultiFilesSelected(const std::vector<base::FilePath>& files,
127 void* params) OVERRIDE;
128 virtual void FileSelectionCanceled(void* params) OVERRIDE;
131 // Initiates and shows 'save as' dialog which will be used to prompt user to
132 // select a file path. The initial selected file name in the dialog will be
133 // set to |suggested_name|. The dialog will be bound to the tab active in
135 // |allowed_extensions| specifies the file extensions allowed to be shown,
136 // and selected. Extensions should not include '.'.
138 // Returns boolean indicating whether the dialog has been successfully shown
140 bool StartSelectFile(const base::FilePath& suggested_name,
141 const std::vector<std::string>& allowed_extensions,
144 // Reacts to the user action reported by the dialog and notifies |function_|
145 // about file selection result (by calling |OnFilePathSelected()|).
146 // The |this| object is self destruct after the function is notified.
147 // |success| indicates whether user has selected the file.
148 // |selected_path| is path that was selected. It is empty if the file wasn't
150 void SendResponse(bool success, const base::FilePath& selected_path);
152 // Dialog that is shown by selector.
153 scoped_refptr<ui::SelectFileDialog> dialog_;
155 // Extension function that uses the selector.
156 scoped_refptr<FileBrowserHandlerInternalSelectFileFunction> function_;
158 DISALLOW_COPY_AND_ASSIGN(FileSelectorImpl);
161 FileSelectorImpl::FileSelectorImpl() {}
163 FileSelectorImpl::~FileSelectorImpl() {
165 dialog_->ListenerDestroyed();
166 // Send response if needed.
168 SendResponse(false, base::FilePath());
171 void FileSelectorImpl::SelectFile(
172 const base::FilePath& suggested_name,
173 const std::vector<std::string>& allowed_extensions,
175 FileBrowserHandlerInternalSelectFileFunction* function) {
176 // We will hold reference to the function until it is notified of selection
178 function_ = function;
180 if (!StartSelectFile(suggested_name, allowed_extensions, browser)) {
181 // If the dialog wasn't launched, let's asynchronously report failure to the
183 base::MessageLoopProxy::current()->PostTask(FROM_HERE,
184 base::Bind(&FileSelectorImpl::FileSelectionCanceled,
185 base::Unretained(this), static_cast<void*>(NULL)));
189 bool FileSelectorImpl::StartSelectFile(
190 const base::FilePath& suggested_name,
191 const std::vector<std::string>& allowed_extensions,
193 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
194 DCHECK(!dialog_.get());
197 if (!browser->window())
200 content::WebContents* web_contents =
201 browser->tab_strip_model()->GetActiveWebContents();
205 dialog_ = ui::SelectFileDialog::Create(
206 this, new ChromeSelectFilePolicy(web_contents));
208 // Convert |allowed_extensions| to ui::SelectFileDialog::FileTypeInfo.
209 ui::SelectFileDialog::FileTypeInfo allowed_file_info =
210 ConvertExtensionsToFileTypeInfo(allowed_extensions);
211 allowed_file_info.support_drive = true;
213 dialog_->SelectFile(ui::SelectFileDialog::SELECT_SAVEAS_FILE,
214 string16() /* dialog title*/,
217 0 /* file type index */,
218 std::string() /* default file extension */,
219 browser->window()->GetNativeWindow(), NULL /* params */);
221 return dialog_->IsRunning(browser->window()->GetNativeWindow());
224 void FileSelectorImpl::FileSelected(
225 const base::FilePath& path, int index, void* params) {
226 SendResponse(true, path);
230 void FileSelectorImpl::MultiFilesSelected(
231 const std::vector<base::FilePath>& files,
233 // Only single file should be selected in save-as dialog.
237 void FileSelectorImpl::FileSelectionCanceled(
239 SendResponse(false, base::FilePath());
243 void FileSelectorImpl::SendResponse(bool success,
244 const base::FilePath& selected_path) {
245 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
247 // We don't want to send multiple responses.
249 function_->OnFilePathSelected(success, selected_path);
253 // FileSelectorFactory implementation.
254 class FileSelectorFactoryImpl : public FileSelectorFactory {
256 FileSelectorFactoryImpl() {}
257 virtual ~FileSelectorFactoryImpl() {}
259 // FileSelectorFactory implementation.
260 // Creates new FileSelectorImplementation for the function.
261 virtual FileSelector* CreateFileSelector() const OVERRIDE {
262 return new FileSelectorImpl();
266 DISALLOW_COPY_AND_ASSIGN(FileSelectorFactoryImpl);
271 FileBrowserHandlerInternalSelectFileFunction::
272 FileBrowserHandlerInternalSelectFileFunction()
273 : file_selector_factory_(new FileSelectorFactoryImpl()),
274 user_gesture_check_enabled_(true) {
277 FileBrowserHandlerInternalSelectFileFunction::
278 FileBrowserHandlerInternalSelectFileFunction(
279 FileSelectorFactory* file_selector_factory,
280 bool enable_user_gesture_check)
281 : file_selector_factory_(file_selector_factory),
282 user_gesture_check_enabled_(enable_user_gesture_check) {
283 DCHECK(file_selector_factory);
286 FileBrowserHandlerInternalSelectFileFunction::
287 ~FileBrowserHandlerInternalSelectFileFunction() {}
289 bool FileBrowserHandlerInternalSelectFileFunction::RunImpl() {
290 scoped_ptr<SelectFile::Params> params(SelectFile::Params::Create(*args_));
292 base::FilePath suggested_name(params->selection_params.suggested_name);
293 std::vector<std::string> allowed_extensions;
294 if (params->selection_params.allowed_file_extensions.get())
295 allowed_extensions = *params->selection_params.allowed_file_extensions;
297 if (!user_gesture() && user_gesture_check_enabled_) {
298 error_ = kNoUserGestureError;
302 FileSelector* file_selector = file_selector_factory_->CreateFileSelector();
303 file_selector->SelectFile(suggested_name.BaseName(),
310 void FileBrowserHandlerInternalSelectFileFunction::OnFilePathSelected(
312 const base::FilePath& full_path) {
313 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
320 full_path_ = full_path;
322 fileapi::FileSystemInfo info =
323 fileapi::GetFileSystemInfoForChromeOS(source_url_.GetOrigin());
324 file_system_name_ = info.name;
325 file_system_root_ = info.root_url;
332 void FileBrowserHandlerInternalSelectFileFunction::GrantPermissions() {
333 fileapi::ExternalFileSystemBackend* external_backend =
334 file_manager::util::GetFileSystemContextForRenderViewHost(
335 GetProfile(), render_view_host())->external_backend();
336 DCHECK(external_backend);
338 external_backend->GetVirtualPath(full_path_, &virtual_path_);
339 DCHECK(!virtual_path_.empty());
341 // Grant access to this particular file to target extension. This will
342 // ensure that the target extension can access only this FS entry and
343 // prevent from traversing FS hierarchy upward.
344 external_backend->GrantFileAccessToExtension(extension_id(), virtual_path_);
346 // Grant access to the selected file to target extensions render view process.
347 content::ChildProcessSecurityPolicy::GetInstance()->GrantCreateReadWriteFile(
348 render_view_host()->GetProcess()->GetID(), full_path_);
351 void FileBrowserHandlerInternalSelectFileFunction::Respond(bool success) {
352 scoped_ptr<SelectFile::Results::Result> result(
353 new SelectFile::Results::Result());
354 result->success = success;
356 // If the file was selected, add 'entry' object which will be later used to
357 // create a FileEntry instance for the selected file.
359 result->entry.reset(new FileEntryInfo());
360 result->entry->file_system_name = file_system_name_;
361 result->entry->file_system_root = file_system_root_.spec();
362 result->entry->file_full_path = "/" + virtual_path_.AsUTF8Unsafe();
363 result->entry->file_is_directory = false;
366 results_ = SelectFile::Results::Create(*result);