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/extensions/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/extensions/file_manager/app_id.h"
13 #include "chrome/browser/chromeos/extensions/file_manager/fileapi_util.h"
14 #include "chrome/browser/chromeos/extensions/file_manager/open_with_browser.h"
15 #include "chrome/browser/chromeos/fileapi/file_system_backend.h"
16 #include "chrome/browser/extensions/event_router.h"
17 #include "chrome/browser/extensions/extension_host.h"
18 #include "chrome/browser/extensions/extension_service.h"
19 #include "chrome/browser/extensions/extension_system.h"
20 #include "chrome/browser/extensions/lazy_background_task_queue.h"
21 #include "chrome/browser/profiles/profile.h"
22 #include "chrome/browser/ui/browser_finder.h"
23 #include "chrome/common/extensions/api/file_browser_handlers/file_browser_handler.h"
24 #include "chrome/common/extensions/background_info.h"
25 #include "content/public/browser/browser_thread.h"
26 #include "content/public/browser/child_process_security_policy.h"
27 #include "content/public/browser/render_process_host.h"
28 #include "content/public/browser/site_instance.h"
29 #include "content/public/browser/web_contents.h"
30 #include "net/base/escape.h"
31 #include "webkit/browser/fileapi/file_system_context.h"
32 #include "webkit/browser/fileapi/file_system_url.h"
34 using content::BrowserThread
;
35 using content::ChildProcessSecurityPolicy
;
36 using content::SiteInstance
;
37 using content::WebContents
;
38 using extensions::Extension
;
39 using fileapi::FileSystemURL
;
41 namespace file_manager
{
42 namespace file_browser_handlers
{
45 // Returns process id of the process the extension is running in.
46 int ExtractProcessFromExtensionId(Profile
* profile
,
47 const std::string
& extension_id
) {
49 Extension::GetBaseURLFromExtensionId(extension_id
);
50 ExtensionProcessManager
* manager
=
51 extensions::ExtensionSystem::Get(profile
)->process_manager();
53 SiteInstance
* site_instance
= manager
->GetSiteInstanceForURL(extension_url
);
54 if (!site_instance
|| !site_instance
->HasProcess())
56 content::RenderProcessHost
* process
= site_instance
->GetProcess();
58 return process
->GetID();
61 // Finds a file browser handler that matches |action_id|. Returns NULL if not
63 const FileBrowserHandler
* FindFileBrowserHandlerForActionId(
64 const Extension
* extension
,
65 const std::string
& action_id
) {
66 FileBrowserHandler::List
* handler_list
=
67 FileBrowserHandler::GetHandlers(extension
);
68 for (FileBrowserHandler::List::const_iterator handler_iter
=
69 handler_list
->begin();
70 handler_iter
!= handler_list
->end();
72 if (handler_iter
->get()->id() == action_id
)
73 return handler_iter
->get();
78 std::string
EscapedUtf8ToLower(const std::string
& str
) {
79 string16 utf16
= UTF8ToUTF16(
80 net::UnescapeURLComponent(str
, net::UnescapeRule::NORMAL
));
81 return net::EscapeUrlEncodedData(
82 UTF16ToUTF8(base::i18n::ToLower(utf16
)),
83 false /* do not replace space with plus */);
86 // Finds file browser handlers that can handle the |selected_file_url|.
87 FileBrowserHandlerList
FindFileBrowserHandlersForURL(
89 const GURL
& selected_file_url
) {
90 ExtensionService
* service
=
91 extensions::ExtensionSystem::Get(profile
)->extension_service();
92 // In unit-tests, we may not have an ExtensionService.
94 return FileBrowserHandlerList();
96 // We need case-insensitive matching, and pattern in the handler is already
98 const GURL
lowercase_url(EscapedUtf8ToLower(selected_file_url
.spec()));
100 FileBrowserHandlerList results
;
101 for (ExtensionSet::const_iterator iter
= service
->extensions()->begin();
102 iter
!= service
->extensions()->end();
104 const Extension
* extension
= iter
->get();
105 if (profile
->IsOffTheRecord() &&
106 !service
->IsIncognitoEnabled(extension
->id()))
109 FileBrowserHandler::List
* handler_list
=
110 FileBrowserHandler::GetHandlers(extension
);
113 for (FileBrowserHandler::List::const_iterator handler_iter
=
114 handler_list
->begin();
115 handler_iter
!= handler_list
->end();
117 const FileBrowserHandler
* handler
= handler_iter
->get();
118 if (!handler
->MatchesURL(lowercase_url
))
121 results
.push_back(handler_iter
->get());
127 // Finds a file browser handler that matches |extension_id| and |action_id|
128 // from |handler_list|. Returns a mutable iterator to the handler if
129 // found. Returns handler_list->end() if not found.
130 FileBrowserHandlerList::iterator
131 FindFileBrowserHandlerForExtensionIdAndActionId(
132 FileBrowserHandlerList
* handler_list
,
133 const std::string
& extension_id
,
134 const std::string
& action_id
) {
135 DCHECK(handler_list
);
137 FileBrowserHandlerList::iterator iter
= handler_list
->begin();
138 while (iter
!= handler_list
->end() &&
139 !((*iter
)->extension_id() == extension_id
&&
140 (*iter
)->id() == action_id
)) {
146 // This class is used to execute a file browser handler task. Here's how this
149 // 1) Open the "external" file system
150 // 2) Set up permissions for the target files on the external file system.
151 // 3) Raise onExecute event with the action ID and entries of the target
152 // files. The event will launch the file browser handler if not active.
153 // 4) In the file browser handler, onExecute event is handled and executes the
154 // task in JavaScript.
156 // That said, the class itself does not execute a task. The task will be
157 // executed in JavaScript.
158 class FileBrowserHandlerExecutor
{
160 FileBrowserHandlerExecutor(Profile
* profile
,
161 const Extension
* extension
,
163 const std::string
& action_id
);
165 // Executes the task for each file. |done| will be run with the result.
166 void Execute(const std::vector
<FileSystemURL
>& file_urls
,
167 const file_tasks::FileTaskFinishedCallback
& done
);
170 // This object is responsible to delete itself.
171 virtual ~FileBrowserHandlerExecutor();
173 struct FileDefinition
{
177 base::FilePath virtual_path
;
178 base::FilePath absolute_path
;
182 typedef std::vector
<FileDefinition
> FileDefinitionList
;
184 // Checks legitimacy of file url and grants file RO access permissions from
185 // handler (target) extension and its renderer process.
186 static FileDefinitionList
SetupFileAccessPermissions(
187 scoped_refptr
<fileapi::FileSystemContext
> file_system_context_handler
,
188 const scoped_refptr
<const Extension
>& handler_extension
,
189 const std::vector
<FileSystemURL
>& file_urls
);
191 void DidOpenFileSystem(const std::vector
<FileSystemURL
>& file_urls
,
192 base::PlatformFileError result
,
193 const std::string
& file_system_name
,
194 const GURL
& file_system_root
);
196 void ExecuteDoneOnUIThread(bool success
);
197 void ExecuteFileActionsOnUIThread(const std::string
& file_system_name
,
198 const GURL
& file_system_root
,
199 const FileDefinitionList
& file_list
);
200 void SetupPermissionsAndDispatchEvent(const std::string
& file_system_name
,
201 const GURL
& file_system_root
,
202 const FileDefinitionList
& file_list
,
204 extensions::ExtensionHost
* host
);
206 // Registers file permissions from |handler_host_permissions_| with
207 // ChildProcessSecurityPolicy for process with id |handler_pid|.
208 void SetupHandlerHostFileAccessPermissions(
209 const FileDefinitionList
& file_list
,
210 const Extension
* extension
,
214 scoped_refptr
<const Extension
> extension_
;
216 const std::string action_id_
;
217 file_tasks::FileTaskFinishedCallback done_
;
218 base::WeakPtrFactory
<FileBrowserHandlerExecutor
> weak_ptr_factory_
;
220 DISALLOW_COPY_AND_ASSIGN(FileBrowserHandlerExecutor
);
223 FileBrowserHandlerExecutor::FileDefinition::FileDefinition()
224 : is_directory(false) {
227 FileBrowserHandlerExecutor::FileDefinition::~FileDefinition() {
231 FileBrowserHandlerExecutor::FileDefinitionList
232 FileBrowserHandlerExecutor::SetupFileAccessPermissions(
233 scoped_refptr
<fileapi::FileSystemContext
> file_system_context_handler
,
234 const scoped_refptr
<const Extension
>& handler_extension
,
235 const std::vector
<FileSystemURL
>& file_urls
) {
236 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
237 DCHECK(handler_extension
.get());
239 fileapi::ExternalFileSystemBackend
* backend
=
240 file_system_context_handler
->external_backend();
242 FileDefinitionList file_list
;
243 for (size_t i
= 0; i
< file_urls
.size(); ++i
) {
244 const FileSystemURL
& url
= file_urls
[i
];
246 // Check if this file system entry exists first.
247 base::PlatformFileInfo file_info
;
249 base::FilePath local_path
= url
.path();
250 base::FilePath virtual_path
= url
.virtual_path();
252 bool is_drive_file
= url
.type() == fileapi::kFileSystemTypeDrive
;
253 DCHECK(!is_drive_file
|| drive::util::IsUnderDriveMountPoint(local_path
));
255 // If the file is under drive mount point, there is no actual file to be
256 // found on the url.path().
257 if (!is_drive_file
) {
258 if (!base::PathExists(local_path
) ||
259 file_util::IsLink(local_path
) ||
260 !file_util::GetFileInfo(local_path
, &file_info
)) {
265 // Grant access to this particular file to target extension. This will
266 // ensure that the target extension can access only this FS entry and
267 // prevent from traversing FS hierarchy upward.
268 backend
->GrantFileAccessToExtension(
269 handler_extension
->id(), virtual_path
);
273 file
.virtual_path
= virtual_path
;
274 file
.is_directory
= file_info
.is_directory
;
275 file
.absolute_path
= local_path
;
276 file_list
.push_back(file
);
281 FileBrowserHandlerExecutor::FileBrowserHandlerExecutor(
283 const Extension
* extension
,
285 const std::string
& action_id
)
287 extension_(extension
),
289 action_id_(action_id
),
290 weak_ptr_factory_(this) {
293 FileBrowserHandlerExecutor::~FileBrowserHandlerExecutor() {}
295 void FileBrowserHandlerExecutor::Execute(
296 const std::vector
<FileSystemURL
>& file_urls
,
297 const file_tasks::FileTaskFinishedCallback
& done
) {
300 // Get file system context for the extension to which onExecute event will be
301 // sent. The file access permissions will be granted to the extension in the
302 // file system context for the files in |file_urls|.
303 util::GetFileSystemContextForExtensionId(
304 profile_
, extension_
->id())->OpenFileSystem(
305 Extension::GetBaseURLFromExtensionId(extension_
->id()).GetOrigin(),
306 fileapi::kFileSystemTypeExternal
,
307 fileapi::OPEN_FILE_SYSTEM_FAIL_IF_NONEXISTENT
,
308 base::Bind(&FileBrowserHandlerExecutor::DidOpenFileSystem
,
309 weak_ptr_factory_
.GetWeakPtr(),
313 void FileBrowserHandlerExecutor::DidOpenFileSystem(
314 const std::vector
<FileSystemURL
>& file_urls
,
315 base::PlatformFileError result
,
316 const std::string
& file_system_name
,
317 const GURL
& file_system_root
) {
318 if (result
!= base::PLATFORM_FILE_OK
) {
319 ExecuteDoneOnUIThread(false);
323 scoped_refptr
<fileapi::FileSystemContext
> file_system_context(
324 util::GetFileSystemContextForExtensionId(
325 profile_
, extension_
->id()));
326 BrowserThread::PostTaskAndReplyWithResult(
329 base::Bind(&SetupFileAccessPermissions
,
333 base::Bind(&FileBrowserHandlerExecutor::ExecuteFileActionsOnUIThread
,
334 weak_ptr_factory_
.GetWeakPtr(),
339 void FileBrowserHandlerExecutor::ExecuteDoneOnUIThread(bool success
) {
340 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
341 if (!done_
.is_null())
346 void FileBrowserHandlerExecutor::ExecuteFileActionsOnUIThread(
347 const std::string
& file_system_name
,
348 const GURL
& file_system_root
,
349 const FileDefinitionList
& file_list
) {
350 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
352 if (file_list
.empty()) {
353 ExecuteDoneOnUIThread(false);
357 int handler_pid
= ExtractProcessFromExtensionId(profile_
, extension_
->id());
358 if (handler_pid
<= 0 &&
359 !extensions::BackgroundInfo::HasLazyBackgroundPage(extension_
.get())) {
360 ExecuteDoneOnUIThread(false);
364 if (handler_pid
> 0) {
365 SetupPermissionsAndDispatchEvent(file_system_name
, file_system_root
,
366 file_list
, handler_pid
, NULL
);
368 // We have to wake the handler background page before we proceed.
369 extensions::LazyBackgroundTaskQueue
* queue
=
370 extensions::ExtensionSystem::Get(profile_
)->
371 lazy_background_task_queue();
372 if (!queue
->ShouldEnqueueTask(profile_
, extension_
.get())) {
373 ExecuteDoneOnUIThread(false);
376 queue
->AddPendingTask(
377 profile_
, extension_
->id(),
379 &FileBrowserHandlerExecutor::SetupPermissionsAndDispatchEvent
,
380 weak_ptr_factory_
.GetWeakPtr(),
388 void FileBrowserHandlerExecutor::SetupPermissionsAndDispatchEvent(
389 const std::string
& file_system_name
,
390 const GURL
& file_system_root
,
391 const FileDefinitionList
& file_list
,
393 extensions::ExtensionHost
* host
) {
394 int handler_pid
= host
? host
->render_process_host()->GetID() :
397 if (handler_pid
<= 0) {
398 ExecuteDoneOnUIThread(false);
402 extensions::EventRouter
* event_router
=
403 extensions::ExtensionSystem::Get(profile_
)->event_router();
405 ExecuteDoneOnUIThread(false);
409 SetupHandlerHostFileAccessPermissions(
410 file_list
, extension_
.get(), handler_pid
);
412 scoped_ptr
<ListValue
> event_args(new ListValue());
413 event_args
->Append(new base::StringValue(action_id_
));
414 DictionaryValue
* details
= new DictionaryValue();
415 event_args
->Append(details
);
416 // Get file definitions. These will be replaced with Entry instances by
417 // dispatchEvent() method from event_binding.js.
418 ListValue
* file_entries
= new ListValue();
419 details
->Set("entries", file_entries
);
420 for (FileDefinitionList::const_iterator iter
= file_list
.begin();
421 iter
!= file_list
.end();
423 DictionaryValue
* file_def
= new DictionaryValue();
424 file_entries
->Append(file_def
);
425 file_def
->SetString("fileSystemName", file_system_name
);
426 file_def
->SetString("fileSystemRoot", file_system_root
.spec());
427 base::FilePath
root(FILE_PATH_LITERAL("/"));
428 base::FilePath full_path
= root
.Append(iter
->virtual_path
);
429 file_def
->SetString("fileFullPath", full_path
.value());
430 file_def
->SetBoolean("fileIsDirectory", iter
->is_directory
);
433 details
->SetInteger("tab_id", tab_id_
);
435 scoped_ptr
<extensions::Event
> event(new extensions::Event(
436 "fileBrowserHandler.onExecute", event_args
.Pass()));
437 event
->restrict_to_profile
= profile_
;
438 event_router
->DispatchEventToExtension(extension_
->id(), event
.Pass());
440 ExecuteDoneOnUIThread(true);
443 void FileBrowserHandlerExecutor::SetupHandlerHostFileAccessPermissions(
444 const FileDefinitionList
& file_list
,
445 const Extension
* extension
,
447 const FileBrowserHandler
* action
= FindFileBrowserHandlerForActionId(
448 extension_
, action_id_
);
449 for (FileDefinitionList::const_iterator iter
= file_list
.begin();
450 iter
!= file_list
.end();
454 if (action
->CanRead()) {
455 content::ChildProcessSecurityPolicy::GetInstance()->GrantReadFile(
456 handler_pid
, iter
->absolute_path
);
458 if (action
->CanWrite()) {
459 content::ChildProcessSecurityPolicy::GetInstance()->
460 GrantCreateReadWriteFile(handler_pid
, iter
->absolute_path
);
465 // Returns true if |extension_id| and |action_id| indicate that the file
466 // currently being handled should be opened with the browser. This function
467 // is used to handle certain action IDs of the file manager.
468 bool ShouldBeOpenedWithBrowser(const std::string
& extension_id
,
469 const std::string
& action_id
) {
471 return (extension_id
== kFileManagerAppId
&&
472 (action_id
== "view-pdf" ||
473 action_id
== "view-swf" ||
474 action_id
== "view-in-browser" ||
475 action_id
== "install-crx" ||
476 action_id
== "open-hosted-generic" ||
477 action_id
== "open-hosted-gdoc" ||
478 action_id
== "open-hosted-gsheet" ||
479 action_id
== "open-hosted-gslides"));
482 // Opens the files specified by |file_urls| with the browser. |profile| is
483 // used for finding an active browser. Returns true on success. It's a
484 // failure if no files are opened.
485 bool OpenFilesWithBrowser(Profile
* profile
,
486 const std::vector
<FileSystemURL
>& file_urls
) {
487 Browser
* browser
= chrome::FindLastActiveWithProfile(
489 chrome::HOST_DESKTOP_TYPE_ASH
);
494 for (size_t i
= 0; i
< file_urls
.size(); ++i
) {
495 const FileSystemURL
& file_url
= file_urls
[i
];
496 if (chromeos::FileSystemBackend::CanHandleURL(file_url
)) {
497 const base::FilePath
& file_path
= file_url
.path();
498 num_opened
+= util::OpenFileWithBrowser(browser
, file_path
);
501 return num_opened
> 0;
506 bool ExecuteFileBrowserHandler(
508 const Extension
* extension
,
510 const std::string
& action_id
,
511 const std::vector
<FileSystemURL
>& file_urls
,
512 const file_tasks::FileTaskFinishedCallback
& done
) {
513 // Forbid calling undeclared handlers.
514 if (!FindFileBrowserHandlerForActionId(extension
, action_id
))
517 // Some action IDs of the file manager's file browser handlers require the
518 // files to be directly opened with the browser.
519 if (ShouldBeOpenedWithBrowser(extension
->id(), action_id
)) {
520 return OpenFilesWithBrowser(profile
, file_urls
);
523 // The executor object will be self deleted on completion.
524 (new FileBrowserHandlerExecutor(
525 profile
, extension
, tab_id
, action_id
))->Execute(file_urls
, done
);
529 bool IsFallbackFileBrowserHandler(const FileBrowserHandler
* handler
) {
530 const std::string
& extension_id
= handler
->extension_id();
531 return (extension_id
== kFileManagerAppId
||
532 extension_id
== extension_misc::kQuickOfficeComponentExtensionId
||
533 extension_id
== extension_misc::kQuickOfficeDevExtensionId
||
534 extension_id
== extension_misc::kQuickOfficeExtensionId
);
537 FileBrowserHandlerList
FindDefaultFileBrowserHandlers(
539 const std::vector
<base::FilePath
>& file_list
,
540 const FileBrowserHandlerList
& common_handlers
) {
541 FileBrowserHandlerList default_handlers
;
543 std::set
<std::string
> default_ids
;
544 for (std::vector
<base::FilePath
>::const_iterator it
= file_list
.begin();
545 it
!= file_list
.end(); ++it
) {
546 std::string task_id
= file_tasks::GetDefaultTaskIdFromPrefs(
547 profile
, "", it
->Extension());
548 if (!task_id
.empty())
549 default_ids
.insert(task_id
);
552 const FileBrowserHandler
* fallback_handler
= NULL
;
553 // Convert the default task IDs collected above to one of the handler pointers
554 // from common_handlers.
555 for (size_t i
= 0; i
< common_handlers
.size(); ++i
) {
556 const FileBrowserHandler
* handler
= common_handlers
[i
];
557 std::string task_id
= file_tasks::MakeTaskID(
558 handler
->extension_id(),
559 file_tasks::TASK_TYPE_FILE_BROWSER_HANDLER
,
561 std::set
<std::string
>::iterator default_iter
= default_ids
.find(task_id
);
562 if (default_iter
!= default_ids
.end()) {
563 default_handlers
.push_back(handler
);
567 // Remember the first fallback handler.
568 if (!fallback_handler
&& IsFallbackFileBrowserHandler(handler
))
569 fallback_handler
= handler
;
572 // If there are no default handlers found, use fallback as default.
573 if (fallback_handler
&& default_handlers
.empty())
574 default_handlers
.push_back(fallback_handler
);
576 return default_handlers
;
579 FileBrowserHandlerList
FindCommonFileBrowserHandlers(
581 const std::vector
<GURL
>& file_list
) {
582 FileBrowserHandlerList common_handlers
;
583 for (std::vector
<GURL
>::const_iterator it
= file_list
.begin();
584 it
!= file_list
.end(); ++it
) {
585 FileBrowserHandlerList handlers
=
586 FindFileBrowserHandlersForURL(profile
, *it
);
587 // If there is nothing to do for one file, the intersection of handlers
588 // for all files will be empty at the end, so no need to check further.
589 if (handlers
.empty())
590 return FileBrowserHandlerList();
592 // For the very first file, just copy all the elements.
593 if (it
== file_list
.begin()) {
594 common_handlers
= handlers
;
596 // For all additional files, find intersection between the accumulated and
597 // file specific set.
598 FileBrowserHandlerList intersection
;
599 std::set_intersection(common_handlers
.begin(), common_handlers
.end(),
600 handlers
.begin(), handlers
.end(),
601 std::back_inserter(intersection
));
602 common_handlers
= intersection
;
603 if (common_handlers
.empty())
604 return FileBrowserHandlerList();
608 // "watch" and "gallery" are defined in the file manager's manifest.json.
609 FileBrowserHandlerList::iterator watch_iter
=
610 FindFileBrowserHandlerForExtensionIdAndActionId(
611 &common_handlers
, kFileManagerAppId
, "watch");
612 FileBrowserHandlerList::iterator gallery_iter
=
613 FindFileBrowserHandlerForExtensionIdAndActionId(
614 &common_handlers
, kFileManagerAppId
, "gallery");
615 if (watch_iter
!= common_handlers
.end() &&
616 gallery_iter
!= common_handlers
.end()) {
617 // Both "watch" and "gallery" actions are applicable which means that the
618 // selection is all videos. Showing them both is confusing, so we only keep
619 // the one that makes more sense ("watch" for single selection, "gallery"
620 // for multiple selection).
621 if (file_list
.size() == 1)
622 common_handlers
.erase(gallery_iter
);
624 common_handlers
.erase(watch_iter
);
627 return common_handlers
;
630 const FileBrowserHandler
* FindFileBrowserHandlerForURLAndPath(
633 const base::FilePath
& file_path
) {
634 std::vector
<GURL
> file_urls
;
635 file_urls
.push_back(url
);
637 FileBrowserHandlerList common_handlers
=
638 FindCommonFileBrowserHandlers(profile
, file_urls
);
639 if (common_handlers
.empty())
642 std::vector
<base::FilePath
> file_paths
;
643 file_paths
.push_back(file_path
);
645 FileBrowserHandlerList default_handlers
=
646 FindDefaultFileBrowserHandlers(profile
, file_paths
, common_handlers
);
648 // If there's none, or more than one, then we don't have a canonical default.
649 if (!default_handlers
.empty()) {
650 // There should not be multiple default handlers for a single URL.
651 DCHECK_EQ(1u, default_handlers
.size());
653 return *default_handlers
.begin();
656 // If there are no default handlers, use first handler in the list (file
657 // manager does the same in this situation). TODO(tbarzic): This is not so
658 // optimal behaviour.
659 return *common_handlers
.begin();
662 } // namespace file_browser_handlers
663 } // namespace file_manager