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/thread_task_runner_handle.h"
37 #include "chrome/browser/chromeos/file_manager/fileapi_util.h"
38 #include "chrome/browser/ui/browser.h"
39 #include "chrome/browser/ui/browser_window.h"
40 #include "chrome/browser/ui/chrome_select_file_policy.h"
41 #include "chrome/browser/ui/tabs/tab_strip_model.h"
42 #include "chrome/common/extensions/api/file_browser_handler_internal.h"
43 #include "content/public/browser/browser_thread.h"
44 #include "content/public/browser/child_process_security_policy.h"
45 #include "content/public/browser/render_frame_host.h"
46 #include "content/public/browser/render_process_host.h"
47 #include "storage/browser/fileapi/file_system_backend.h"
48 #include "storage/browser/fileapi/file_system_context.h"
49 #include "storage/common/fileapi/file_system_info.h"
50 #include "storage/common/fileapi/file_system_util.h"
51 #include "ui/shell_dialogs/select_file_dialog.h"
53 using content::BrowserThread
;
54 using extensions::api::file_browser_handler_internal::FileEntryInfo
;
55 using file_manager::FileSelector
;
56 using file_manager::FileSelectorFactory
;
57 using file_manager::util::EntryDefinition
;
58 using file_manager::util::FileDefinition
;
60 namespace SelectFile
=
61 extensions::api::file_browser_handler_internal::SelectFile
;
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.";
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
;
74 for (size_t i
= 0; i
< extensions
.size(); ++i
) {
75 base::FilePath::StringType allowed_extension
=
76 base::FilePath::FromUTF8Unsafe(extensions
[i
]).value();
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
);
85 return file_type_info
;
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
{
103 ~FileSelectorImpl() override
;
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 '.'.
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.
115 const base::FilePath
& suggested_name
,
116 const std::vector
<std::string
>& allowed_extensions
,
118 FileBrowserHandlerInternalSelectFileFunction
* function
) override
;
120 // ui::SelectFileDialog::Listener overrides.
121 void FileSelected(const base::FilePath
& path
,
123 void* params
) override
;
124 void MultiFilesSelected(const std::vector
<base::FilePath
>& files
,
125 void* params
) override
;
126 void FileSelectionCanceled(void* params
) override
;
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
133 // |allowed_extensions| specifies the file extensions allowed to be shown,
134 // and selected. Extensions should not include '.'.
136 // Returns boolean indicating whether the dialog has been successfully shown
138 bool StartSelectFile(const base::FilePath
& suggested_name
,
139 const std::vector
<std::string
>& allowed_extensions
,
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
148 void SendResponse(bool success
, const base::FilePath
& selected_path
);
150 // Dialog that is shown by selector.
151 scoped_refptr
<ui::SelectFileDialog
> dialog_
;
153 // Extension function that uses the selector.
154 scoped_refptr
<FileBrowserHandlerInternalSelectFileFunction
> function_
;
156 DISALLOW_COPY_AND_ASSIGN(FileSelectorImpl
);
159 FileSelectorImpl::FileSelectorImpl() {}
161 FileSelectorImpl::~FileSelectorImpl() {
163 dialog_
->ListenerDestroyed();
164 // Send response if needed.
166 SendResponse(false, base::FilePath());
169 void FileSelectorImpl::SelectFile(
170 const base::FilePath
& suggested_name
,
171 const std::vector
<std::string
>& allowed_extensions
,
173 FileBrowserHandlerInternalSelectFileFunction
* function
) {
174 // We will hold reference to the function until it is notified of selection
176 function_
= function
;
178 if (!StartSelectFile(suggested_name
, allowed_extensions
, browser
)) {
179 // If the dialog wasn't launched, let's asynchronously report failure to the
181 base::ThreadTaskRunnerHandle::Get()->PostTask(
183 base::Bind(&FileSelectorImpl::FileSelectionCanceled
,
184 base::Unretained(this), static_cast<void*>(NULL
)));
188 bool FileSelectorImpl::StartSelectFile(
189 const base::FilePath
& suggested_name
,
190 const std::vector
<std::string
>& allowed_extensions
,
192 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
193 DCHECK(!dialog_
.get());
196 if (!browser
->window())
199 content::WebContents
* web_contents
=
200 browser
->tab_strip_model()->GetActiveWebContents();
204 dialog_
= ui::SelectFileDialog::Create(
205 this, new ChromeSelectFilePolicy(web_contents
));
207 // Convert |allowed_extensions| to ui::SelectFileDialog::FileTypeInfo.
208 ui::SelectFileDialog::FileTypeInfo allowed_file_info
=
209 ConvertExtensionsToFileTypeInfo(allowed_extensions
);
210 allowed_file_info
.support_drive
= true;
212 dialog_
->SelectFile(ui::SelectFileDialog::SELECT_SAVEAS_FILE
,
213 base::string16() /* dialog title*/,
216 0 /* file type index */,
217 std::string() /* default file extension */,
218 browser
->window()->GetNativeWindow(), NULL
/* params */);
220 return dialog_
->IsRunning(browser
->window()->GetNativeWindow());
223 void FileSelectorImpl::FileSelected(
224 const base::FilePath
& path
, int index
, void* params
) {
225 SendResponse(true, path
);
229 void FileSelectorImpl::MultiFilesSelected(
230 const std::vector
<base::FilePath
>& files
,
232 // Only single file should be selected in save-as dialog.
236 void FileSelectorImpl::FileSelectionCanceled(
238 SendResponse(false, base::FilePath());
242 void FileSelectorImpl::SendResponse(bool success
,
243 const base::FilePath
& selected_path
) {
244 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
246 // We don't want to send multiple responses.
248 function_
->OnFilePathSelected(success
, selected_path
);
252 // FileSelectorFactory implementation.
253 class FileSelectorFactoryImpl
: public FileSelectorFactory
{
255 FileSelectorFactoryImpl() {}
256 ~FileSelectorFactoryImpl() override
{}
258 // FileSelectorFactory implementation.
259 // Creates new FileSelectorImplementation for the function.
260 FileSelector
* CreateFileSelector() const override
{
261 return new FileSelectorImpl();
265 DISALLOW_COPY_AND_ASSIGN(FileSelectorFactoryImpl
);
270 FileBrowserHandlerInternalSelectFileFunction::
271 FileBrowserHandlerInternalSelectFileFunction()
272 : file_selector_factory_(new FileSelectorFactoryImpl()),
273 user_gesture_check_enabled_(true) {
276 FileBrowserHandlerInternalSelectFileFunction::
277 FileBrowserHandlerInternalSelectFileFunction(
278 FileSelectorFactory
* file_selector_factory
,
279 bool enable_user_gesture_check
)
280 : file_selector_factory_(file_selector_factory
),
281 user_gesture_check_enabled_(enable_user_gesture_check
) {
282 DCHECK(file_selector_factory
);
285 FileBrowserHandlerInternalSelectFileFunction::
286 ~FileBrowserHandlerInternalSelectFileFunction() {}
288 bool FileBrowserHandlerInternalSelectFileFunction::RunAsync() {
289 scoped_ptr
<SelectFile::Params
> params(SelectFile::Params::Create(*args_
));
291 base::FilePath
suggested_name(params
->selection_params
.suggested_name
);
292 std::vector
<std::string
> allowed_extensions
;
293 if (params
->selection_params
.allowed_file_extensions
.get())
294 allowed_extensions
= *params
->selection_params
.allowed_file_extensions
;
296 if (!user_gesture() && user_gesture_check_enabled_
) {
297 SetError(kNoUserGestureError
);
301 FileSelector
* file_selector
= file_selector_factory_
->CreateFileSelector();
302 file_selector
->SelectFile(suggested_name
.BaseName(),
309 void FileBrowserHandlerInternalSelectFileFunction::OnFilePathSelected(
311 const base::FilePath
& full_path
) {
312 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
315 Respond(EntryDefinition(), false);
319 storage::ExternalFileSystemBackend
* external_backend
=
320 file_manager::util::GetFileSystemContextForRenderFrameHost(
321 GetProfile(), render_frame_host())->external_backend();
322 DCHECK(external_backend
);
324 FileDefinition file_definition
;
325 file_definition
.is_directory
= false;
327 external_backend
->GetVirtualPath(full_path
, &file_definition
.virtual_path
);
328 DCHECK(!file_definition
.virtual_path
.empty());
330 // Grant access to this particular file to target extension. This will
331 // ensure that the target extension can access only this FS entry and
332 // prevent from traversing FS hierarchy upward.
333 external_backend
->GrantFileAccessToExtension(extension_id(),
334 file_definition
.virtual_path
);
336 // Grant access to the selected file to target extensions render view process.
337 content::ChildProcessSecurityPolicy::GetInstance()->GrantCreateReadWriteFile(
338 render_frame_host()->GetProcess()->GetID(), full_path
);
340 file_manager::util::ConvertFileDefinitionToEntryDefinition(
345 &FileBrowserHandlerInternalSelectFileFunction::RespondEntryDefinition
,
349 void FileBrowserHandlerInternalSelectFileFunction::RespondEntryDefinition(
350 const EntryDefinition
& entry_definition
) {
351 Respond(entry_definition
, true);
354 void FileBrowserHandlerInternalSelectFileFunction::Respond(
355 const EntryDefinition
& entry_definition
,
357 scoped_ptr
<SelectFile::Results::Result
> result(
358 new SelectFile::Results::Result());
359 result
->success
= success
;
361 // If the file was selected, add 'entry' object which will be later used to
362 // create a FileEntry instance for the selected file.
363 if (success
&& entry_definition
.error
== base::File::FILE_OK
) {
364 result
->entry
.reset(new FileEntryInfo());
365 // TODO(mtomasz): Make the response fields consistent with other files.
366 result
->entry
->file_system_name
= entry_definition
.file_system_name
;
367 result
->entry
->file_system_root
= entry_definition
.file_system_root_url
;
368 result
->entry
->file_full_path
=
369 "/" + entry_definition
.full_path
.AsUTF8Unsafe();
370 result
->entry
->file_is_directory
= entry_definition
.is_directory
;
373 results_
= SelectFile::Results::Create(*result
);