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 #include "chrome/browser/chromeos/file_manager/file_browser_handlers.h"
8 #include "base/file_util.h"
9 #include "base/i18n/case_conversion.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "chrome/browser/chromeos/drive/file_system_util.h"
12 #include "chrome/browser/chromeos/file_manager/app_id.h"
13 #include "chrome/browser/chromeos/file_manager/fileapi_util.h"
14 #include "chrome/browser/chromeos/file_manager/open_with_browser.h"
15 #include "chrome/browser/chromeos/fileapi/file_system_backend.h"
16 #include "chrome/browser/extensions/extension_service.h"
17 #include "chrome/browser/extensions/extension_util.h"
18 #include "chrome/browser/profiles/profile.h"
19 #include "chrome/browser/ui/browser_finder.h"
20 #include "chrome/common/extensions/api/file_browser_handlers/file_browser_handler.h"
21 #include "chrome/common/extensions/api/file_browser_private.h"
22 #include "content/public/browser/browser_thread.h"
23 #include "content/public/browser/child_process_security_policy.h"
24 #include "content/public/browser/render_process_host.h"
25 #include "content/public/browser/site_instance.h"
26 #include "content/public/browser/web_contents.h"
27 #include "extensions/browser/event_router.h"
28 #include "extensions/browser/extension_host.h"
29 #include "extensions/browser/extension_system.h"
30 #include "extensions/browser/lazy_background_task_queue.h"
31 #include "extensions/common/extension_set.h"
32 #include "extensions/common/manifest_handlers/background_info.h"
33 #include "net/base/escape.h"
34 #include "webkit/browser/fileapi/file_system_context.h"
35 #include "webkit/browser/fileapi/file_system_url.h"
36 #include "webkit/common/fileapi/file_system_info.h"
37 #include "webkit/common/fileapi/file_system_util.h"
39 using content::BrowserThread
;
40 using content::ChildProcessSecurityPolicy
;
41 using content::SiteInstance
;
42 using content::WebContents
;
43 using extensions::Extension
;
44 using fileapi::FileSystemURL
;
45 using file_manager::util::EntryDefinition
;
46 using file_manager::util::EntryDefinitionList
;
47 using file_manager::util::FileDefinition
;
48 using file_manager::util::FileDefinitionList
;
50 namespace file_manager
{
51 namespace file_browser_handlers
{
54 // Returns process id of the process the extension is running in.
55 int ExtractProcessFromExtensionId(Profile
* profile
,
56 const std::string
& extension_id
) {
58 Extension::GetBaseURLFromExtensionId(extension_id
);
59 extensions::ProcessManager
* manager
=
60 extensions::ExtensionSystem::Get(profile
)->process_manager();
62 SiteInstance
* site_instance
= manager
->GetSiteInstanceForURL(extension_url
);
63 if (!site_instance
|| !site_instance
->HasProcess())
65 content::RenderProcessHost
* process
= site_instance
->GetProcess();
67 return process
->GetID();
70 // Finds a file browser handler that matches |action_id|. Returns NULL if not
72 const FileBrowserHandler
* FindFileBrowserHandlerForActionId(
73 const Extension
* extension
,
74 const std::string
& action_id
) {
75 FileBrowserHandler::List
* handler_list
=
76 FileBrowserHandler::GetHandlers(extension
);
77 for (FileBrowserHandler::List::const_iterator handler_iter
=
78 handler_list
->begin();
79 handler_iter
!= handler_list
->end();
81 if (handler_iter
->get()->id() == action_id
)
82 return handler_iter
->get();
87 std::string
EscapedUtf8ToLower(const std::string
& str
) {
88 base::string16 utf16
= base::UTF8ToUTF16(
89 net::UnescapeURLComponent(str
, net::UnescapeRule::NORMAL
));
90 return net::EscapeUrlEncodedData(
91 base::UTF16ToUTF8(base::i18n::ToLower(utf16
)),
92 false /* do not replace space with plus */);
95 // Finds file browser handlers that can handle the |selected_file_url|.
96 FileBrowserHandlerList
FindFileBrowserHandlersForURL(
98 const GURL
& selected_file_url
) {
99 ExtensionService
* service
=
100 extensions::ExtensionSystem::Get(profile
)->extension_service();
101 // In unit-tests, we may not have an ExtensionService.
103 return FileBrowserHandlerList();
105 // We need case-insensitive matching, and pattern in the handler is already
107 const GURL
lowercase_url(EscapedUtf8ToLower(selected_file_url
.spec()));
109 FileBrowserHandlerList results
;
110 for (extensions::ExtensionSet::const_iterator iter
=
111 service
->extensions()->begin();
112 iter
!= service
->extensions()->end(); ++iter
) {
113 const Extension
* extension
= iter
->get();
114 if (profile
->IsOffTheRecord() &&
115 !extensions::util::IsIncognitoEnabled(extension
->id(), profile
))
118 FileBrowserHandler::List
* handler_list
=
119 FileBrowserHandler::GetHandlers(extension
);
122 for (FileBrowserHandler::List::const_iterator handler_iter
=
123 handler_list
->begin();
124 handler_iter
!= handler_list
->end();
126 const FileBrowserHandler
* handler
= handler_iter
->get();
127 if (!handler
->MatchesURL(lowercase_url
))
130 results
.push_back(handler_iter
->get());
136 // This class is used to execute a file browser handler task. Here's how this
139 // 1) Open the "external" file system
140 // 2) Set up permissions for the target files on the external file system.
141 // 3) Raise onExecute event with the action ID and entries of the target
142 // files. The event will launch the file browser handler if not active.
143 // 4) In the file browser handler, onExecute event is handled and executes the
144 // task in JavaScript.
146 // That said, the class itself does not execute a task. The task will be
147 // executed in JavaScript.
148 class FileBrowserHandlerExecutor
{
150 FileBrowserHandlerExecutor(Profile
* profile
,
151 const Extension
* extension
,
152 const std::string
& action_id
);
154 // Executes the task for each file. |done| will be run with the result.
155 void Execute(const std::vector
<FileSystemURL
>& file_urls
,
156 const file_tasks::FileTaskFinishedCallback
& done
);
159 // This object is responsible to delete itself.
160 virtual ~FileBrowserHandlerExecutor();
162 // Checks legitimacy of file url and grants file RO access permissions from
163 // handler (target) extension and its renderer process.
164 static scoped_ptr
<FileDefinitionList
> SetupFileAccessPermissions(
165 scoped_refptr
<fileapi::FileSystemContext
> file_system_context_handler
,
166 const scoped_refptr
<const Extension
>& handler_extension
,
167 const std::vector
<FileSystemURL
>& file_urls
);
169 void ExecuteDoneOnUIThread(bool success
);
170 void ExecuteAfterSetupFileAccess(scoped_ptr
<FileDefinitionList
> file_list
);
171 void ExecuteFileActionsOnUIThread(
172 scoped_ptr
<FileDefinitionList
> file_definition_list
,
173 scoped_ptr
<EntryDefinitionList
> entry_definition_list
);
174 void SetupPermissionsAndDispatchEvent(
175 scoped_ptr
<FileDefinitionList
> file_definition_list
,
176 scoped_ptr
<EntryDefinitionList
> entry_definition_list
,
178 extensions::ExtensionHost
* host
);
180 // Registers file permissions from |handler_host_permissions_| with
181 // ChildProcessSecurityPolicy for process with id |handler_pid|.
182 void SetupHandlerHostFileAccessPermissions(
183 FileDefinitionList
* file_definition_list
,
184 const Extension
* extension
,
188 scoped_refptr
<const Extension
> extension_
;
189 const std::string action_id_
;
190 file_tasks::FileTaskFinishedCallback done_
;
191 base::WeakPtrFactory
<FileBrowserHandlerExecutor
> weak_ptr_factory_
;
193 DISALLOW_COPY_AND_ASSIGN(FileBrowserHandlerExecutor
);
197 scoped_ptr
<FileDefinitionList
>
198 FileBrowserHandlerExecutor::SetupFileAccessPermissions(
199 scoped_refptr
<fileapi::FileSystemContext
> file_system_context_handler
,
200 const scoped_refptr
<const Extension
>& handler_extension
,
201 const std::vector
<FileSystemURL
>& file_urls
) {
202 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
203 DCHECK(handler_extension
.get());
205 fileapi::ExternalFileSystemBackend
* backend
=
206 file_system_context_handler
->external_backend();
208 scoped_ptr
<FileDefinitionList
> file_definition_list(new FileDefinitionList
);
209 for (size_t i
= 0; i
< file_urls
.size(); ++i
) {
210 const FileSystemURL
& url
= file_urls
[i
];
212 // Check if this file system entry exists first.
213 base::File::Info file_info
;
215 base::FilePath local_path
= url
.path();
216 base::FilePath virtual_path
= url
.virtual_path();
218 const bool is_drive_file
= url
.type() == fileapi::kFileSystemTypeDrive
;
219 DCHECK(!is_drive_file
|| drive::util::IsUnderDriveMountPoint(local_path
));
221 const bool is_native_file
=
222 url
.type() == fileapi::kFileSystemTypeNativeLocal
||
223 url
.type() == fileapi::kFileSystemTypeRestrictedNativeLocal
;
225 // If the file is from a physical volume, actual file must be found.
226 if (is_native_file
) {
227 if (!base::PathExists(local_path
) ||
228 base::IsLink(local_path
) ||
229 !base::GetFileInfo(local_path
, &file_info
)) {
234 // Grant access to this particular file to target extension. This will
235 // ensure that the target extension can access only this FS entry and
236 // prevent from traversing FS hierarchy upward.
237 backend
->GrantFileAccessToExtension(handler_extension
->id(), virtual_path
);
240 FileDefinition file_definition
;
241 file_definition
.virtual_path
= virtual_path
;
242 file_definition
.is_directory
= file_info
.is_directory
;
243 file_definition
.absolute_path
= local_path
;
244 file_definition_list
->push_back(file_definition
);
247 return file_definition_list
.Pass();
250 FileBrowserHandlerExecutor::FileBrowserHandlerExecutor(
252 const Extension
* extension
,
253 const std::string
& action_id
)
255 extension_(extension
),
256 action_id_(action_id
),
257 weak_ptr_factory_(this) {
260 FileBrowserHandlerExecutor::~FileBrowserHandlerExecutor() {}
262 void FileBrowserHandlerExecutor::Execute(
263 const std::vector
<FileSystemURL
>& file_urls
,
264 const file_tasks::FileTaskFinishedCallback
& done
) {
267 // Get file system context for the extension to which onExecute event will be
268 // sent. The file access permissions will be granted to the extension in the
269 // file system context for the files in |file_urls|.
270 scoped_refptr
<fileapi::FileSystemContext
> file_system_context(
271 util::GetFileSystemContextForExtensionId(
272 profile_
, extension_
->id()));
274 BrowserThread::PostTaskAndReplyWithResult(
277 base::Bind(&SetupFileAccessPermissions
,
281 base::Bind(&FileBrowserHandlerExecutor::ExecuteAfterSetupFileAccess
,
282 weak_ptr_factory_
.GetWeakPtr()));
285 void FileBrowserHandlerExecutor::ExecuteAfterSetupFileAccess(
286 scoped_ptr
<FileDefinitionList
> file_definition_list
) {
287 // Outlives the conversion process, since bound to the callback.
288 const FileDefinitionList
& file_definition_list_ref
=
289 *file_definition_list
.get();
290 file_manager::util::ConvertFileDefinitionListToEntryDefinitionList(
293 file_definition_list_ref
,
294 base::Bind(&FileBrowserHandlerExecutor::ExecuteFileActionsOnUIThread
,
295 weak_ptr_factory_
.GetWeakPtr(),
296 base::Passed(&file_definition_list
)));
299 void FileBrowserHandlerExecutor::ExecuteDoneOnUIThread(bool success
) {
300 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
301 if (!done_
.is_null())
304 ? extensions::api::file_browser_private::TASK_RESULT_MESSAGE_SENT
305 : extensions::api::file_browser_private::TASK_RESULT_FAILED
);
309 void FileBrowserHandlerExecutor::ExecuteFileActionsOnUIThread(
310 scoped_ptr
<FileDefinitionList
> file_definition_list
,
311 scoped_ptr
<EntryDefinitionList
> entry_definition_list
) {
312 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
314 if (file_definition_list
->empty() || entry_definition_list
->empty()) {
315 ExecuteDoneOnUIThread(false);
319 int handler_pid
= ExtractProcessFromExtensionId(profile_
, extension_
->id());
320 if (handler_pid
<= 0 &&
321 !extensions::BackgroundInfo::HasLazyBackgroundPage(extension_
.get())) {
322 ExecuteDoneOnUIThread(false);
326 if (handler_pid
> 0) {
327 SetupPermissionsAndDispatchEvent(file_definition_list
.Pass(),
328 entry_definition_list
.Pass(),
332 // We have to wake the handler background page before we proceed.
333 extensions::LazyBackgroundTaskQueue
* queue
=
334 extensions::ExtensionSystem::Get(profile_
)->
335 lazy_background_task_queue();
336 if (!queue
->ShouldEnqueueTask(profile_
, extension_
.get())) {
337 ExecuteDoneOnUIThread(false);
340 queue
->AddPendingTask(
344 &FileBrowserHandlerExecutor::SetupPermissionsAndDispatchEvent
,
345 weak_ptr_factory_
.GetWeakPtr(),
346 base::Passed(file_definition_list
.Pass()),
347 base::Passed(entry_definition_list
.Pass()),
352 void FileBrowserHandlerExecutor::SetupPermissionsAndDispatchEvent(
353 scoped_ptr
<FileDefinitionList
> file_definition_list
,
354 scoped_ptr
<EntryDefinitionList
> entry_definition_list
,
356 extensions::ExtensionHost
* host
) {
357 int handler_pid
= host
? host
->render_process_host()->GetID() :
360 if (handler_pid
<= 0) {
361 ExecuteDoneOnUIThread(false);
365 extensions::EventRouter
* router
= extensions::EventRouter::Get(profile_
);
367 ExecuteDoneOnUIThread(false);
371 SetupHandlerHostFileAccessPermissions(
372 file_definition_list
.get(), extension_
.get(), handler_pid
);
374 scoped_ptr
<base::ListValue
> event_args(new base::ListValue());
375 event_args
->Append(new base::StringValue(action_id_
));
376 base::DictionaryValue
* details
= new base::DictionaryValue();
377 event_args
->Append(details
);
378 // Get file definitions. These will be replaced with Entry instances by
379 // dispatchEvent() method from event_binding.js.
380 base::ListValue
* file_entries
= new base::ListValue();
381 details
->Set("entries", file_entries
);
383 for (EntryDefinitionList::const_iterator iter
=
384 entry_definition_list
->begin();
385 iter
!= entry_definition_list
->end();
387 base::DictionaryValue
* file_def
= new base::DictionaryValue();
388 file_entries
->Append(file_def
);
389 file_def
->SetString("fileSystemName", iter
->file_system_name
);
390 file_def
->SetString("fileSystemRoot", iter
->file_system_root_url
);
391 file_def
->SetString("fileFullPath",
392 "/" + iter
->full_path
.AsUTF8Unsafe());
393 file_def
->SetBoolean("fileIsDirectory", iter
->is_directory
);
396 scoped_ptr
<extensions::Event
> event(new extensions::Event(
397 "fileBrowserHandler.onExecute", event_args
.Pass()));
398 event
->restrict_to_browser_context
= profile_
;
399 router
->DispatchEventToExtension(extension_
->id(), event
.Pass());
401 ExecuteDoneOnUIThread(true);
404 void FileBrowserHandlerExecutor::SetupHandlerHostFileAccessPermissions(
405 FileDefinitionList
* file_definition_list
,
406 const Extension
* extension
,
408 const FileBrowserHandler
* action
= FindFileBrowserHandlerForActionId(
409 extension_
, action_id_
);
410 for (FileDefinitionList::const_iterator iter
= file_definition_list
->begin();
411 iter
!= file_definition_list
->end();
415 if (action
->CanRead()) {
416 content::ChildProcessSecurityPolicy::GetInstance()->GrantReadFile(
417 handler_pid
, iter
->absolute_path
);
419 if (action
->CanWrite()) {
420 content::ChildProcessSecurityPolicy::GetInstance()->
421 GrantCreateReadWriteFile(handler_pid
, iter
->absolute_path
);
426 // Returns true if |extension_id| and |action_id| indicate that the file
427 // currently being handled should be opened with the browser. This function
428 // is used to handle certain action IDs of the file manager.
429 bool ShouldBeOpenedWithBrowser(const std::string
& extension_id
,
430 const std::string
& action_id
) {
432 return (extension_id
== kFileManagerAppId
&&
433 (action_id
== "view-pdf" ||
434 action_id
== "view-swf" ||
435 action_id
== "view-in-browser" ||
436 action_id
== "open-hosted-generic" ||
437 action_id
== "open-hosted-gdoc" ||
438 action_id
== "open-hosted-gsheet" ||
439 action_id
== "open-hosted-gslides"));
442 // Opens the files specified by |file_urls| with the browser for |profile|.
443 // Returns true on success. It's a failure if no files are opened.
444 bool OpenFilesWithBrowser(Profile
* profile
,
445 const std::vector
<FileSystemURL
>& file_urls
) {
447 for (size_t i
= 0; i
< file_urls
.size(); ++i
) {
448 const FileSystemURL
& file_url
= file_urls
[i
];
449 if (chromeos::FileSystemBackend::CanHandleURL(file_url
)) {
450 const base::FilePath
& file_path
= file_url
.path();
451 num_opened
+= util::OpenFileWithBrowser(profile
, file_path
);
454 return num_opened
> 0;
459 bool ExecuteFileBrowserHandler(
461 const Extension
* extension
,
462 const std::string
& action_id
,
463 const std::vector
<FileSystemURL
>& file_urls
,
464 const file_tasks::FileTaskFinishedCallback
& done
) {
465 // Forbid calling undeclared handlers.
466 if (!FindFileBrowserHandlerForActionId(extension
, action_id
))
469 // Some action IDs of the file manager's file browser handlers require the
470 // files to be directly opened with the browser.
471 if (ShouldBeOpenedWithBrowser(extension
->id(), action_id
)) {
472 const bool result
= OpenFilesWithBrowser(profile
, file_urls
);
473 if (!done
.is_null()) {
475 ? extensions::api::file_browser_private::TASK_RESULT_OPENED
476 : extensions::api::file_browser_private::TASK_RESULT_FAILED
);
481 // The executor object will be self deleted on completion.
482 (new FileBrowserHandlerExecutor(
483 profile
, extension
, action_id
))->Execute(file_urls
, done
);
487 bool IsFallbackFileBrowserHandler(const file_tasks::TaskDescriptor
& task
) {
488 return ((task
.task_type
== file_tasks::TASK_TYPE_FILE_BROWSER_HANDLER
||
489 task
.task_type
== file_tasks::TASK_TYPE_FILE_HANDLER
) &&
490 (task
.app_id
== kFileManagerAppId
||
491 task
.app_id
== kVideoPlayerAppId
||
492 task
.app_id
== extension_misc::kQuickOfficeComponentExtensionId
||
493 task
.app_id
== extension_misc::kQuickOfficeInternalExtensionId
||
494 task
.app_id
== extension_misc::kQuickOfficeExtensionId
));
497 FileBrowserHandlerList
FindFileBrowserHandlers(
499 const std::vector
<GURL
>& file_list
) {
500 FileBrowserHandlerList common_handlers
;
501 for (std::vector
<GURL
>::const_iterator it
= file_list
.begin();
502 it
!= file_list
.end(); ++it
) {
503 FileBrowserHandlerList handlers
=
504 FindFileBrowserHandlersForURL(profile
, *it
);
505 // If there is nothing to do for one file, the intersection of handlers
506 // for all files will be empty at the end, so no need to check further.
507 if (handlers
.empty())
508 return FileBrowserHandlerList();
510 // For the very first file, just copy all the elements.
511 if (it
== file_list
.begin()) {
512 common_handlers
= handlers
;
514 // For all additional files, find intersection between the accumulated and
515 // file specific set.
516 FileBrowserHandlerList intersection
;
517 std::set
<const FileBrowserHandler
*> common_handler_set(
518 common_handlers
.begin(), common_handlers
.end());
520 for (FileBrowserHandlerList::const_iterator itr
= handlers
.begin();
521 itr
!= handlers
.end(); ++itr
) {
522 if (ContainsKey(common_handler_set
, *itr
))
523 intersection
.push_back(*itr
);
526 std::swap(common_handlers
, intersection
);
527 if (common_handlers
.empty())
528 return FileBrowserHandlerList();
532 return common_handlers
;
535 } // namespace file_browser_handlers
536 } // namespace file_manager