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_host.h"
17 #include "chrome/browser/extensions/extension_service.h"
18 #include "chrome/browser/extensions/extension_system.h"
19 #include "chrome/browser/extensions/extension_util.h"
20 #include "chrome/browser/profiles/profile.h"
21 #include "chrome/browser/ui/browser_finder.h"
22 #include "chrome/common/extensions/api/file_browser_handlers/file_browser_handler.h"
23 #include "content/public/browser/browser_thread.h"
24 #include "content/public/browser/child_process_security_policy.h"
25 #include "content/public/browser/render_process_host.h"
26 #include "content/public/browser/site_instance.h"
27 #include "content/public/browser/web_contents.h"
28 #include "extensions/browser/event_router.h"
29 #include "extensions/browser/lazy_background_task_queue.h"
30 #include "extensions/common/extension_set.h"
31 #include "extensions/common/manifest_handlers/background_info.h"
32 #include "net/base/escape.h"
33 #include "webkit/browser/fileapi/file_system_context.h"
34 #include "webkit/browser/fileapi/file_system_url.h"
35 #include "webkit/common/fileapi/file_system_info.h"
36 #include "webkit/common/fileapi/file_system_util.h"
38 using content::BrowserThread;
39 using content::ChildProcessSecurityPolicy;
40 using content::SiteInstance;
41 using content::WebContents;
42 using extensions::Extension;
43 using fileapi::FileSystemURL;
45 namespace file_manager {
46 namespace file_browser_handlers {
49 // Returns process id of the process the extension is running in.
50 int ExtractProcessFromExtensionId(Profile* profile,
51 const std::string& extension_id) {
53 Extension::GetBaseURLFromExtensionId(extension_id);
54 extensions::ProcessManager* manager =
55 extensions::ExtensionSystem::Get(profile)->process_manager();
57 SiteInstance* site_instance = manager->GetSiteInstanceForURL(extension_url);
58 if (!site_instance || !site_instance->HasProcess())
60 content::RenderProcessHost* process = site_instance->GetProcess();
62 return process->GetID();
65 // Finds a file browser handler that matches |action_id|. Returns NULL if not
67 const FileBrowserHandler* FindFileBrowserHandlerForActionId(
68 const Extension* extension,
69 const std::string& action_id) {
70 FileBrowserHandler::List* handler_list =
71 FileBrowserHandler::GetHandlers(extension);
72 for (FileBrowserHandler::List::const_iterator handler_iter =
73 handler_list->begin();
74 handler_iter != handler_list->end();
76 if (handler_iter->get()->id() == action_id)
77 return handler_iter->get();
82 std::string EscapedUtf8ToLower(const std::string& str) {
83 base::string16 utf16 = base::UTF8ToUTF16(
84 net::UnescapeURLComponent(str, net::UnescapeRule::NORMAL));
85 return net::EscapeUrlEncodedData(
86 base::UTF16ToUTF8(base::i18n::ToLower(utf16)),
87 false /* do not replace space with plus */);
90 // Finds file browser handlers that can handle the |selected_file_url|.
91 FileBrowserHandlerList FindFileBrowserHandlersForURL(
93 const GURL& selected_file_url) {
94 ExtensionService* service =
95 extensions::ExtensionSystem::Get(profile)->extension_service();
96 // In unit-tests, we may not have an ExtensionService.
98 return FileBrowserHandlerList();
100 // We need case-insensitive matching, and pattern in the handler is already
102 const GURL lowercase_url(EscapedUtf8ToLower(selected_file_url.spec()));
104 FileBrowserHandlerList results;
105 for (extensions::ExtensionSet::const_iterator iter =
106 service->extensions()->begin();
107 iter != service->extensions()->end(); ++iter) {
108 const Extension* extension = iter->get();
109 if (profile->IsOffTheRecord() &&
110 !extensions::util::IsIncognitoEnabled(extension->id(), profile))
113 FileBrowserHandler::List* handler_list =
114 FileBrowserHandler::GetHandlers(extension);
117 for (FileBrowserHandler::List::const_iterator handler_iter =
118 handler_list->begin();
119 handler_iter != handler_list->end();
121 const FileBrowserHandler* handler = handler_iter->get();
122 if (!handler->MatchesURL(lowercase_url))
125 results.push_back(handler_iter->get());
131 // Finds a file browser handler that matches |extension_id| and |action_id|
132 // from |handler_list|. Returns a mutable iterator to the handler if
133 // found. Returns handler_list->end() if not found.
134 FileBrowserHandlerList::iterator
135 FindFileBrowserHandlerForExtensionIdAndActionId(
136 FileBrowserHandlerList* handler_list,
137 const std::string& extension_id,
138 const std::string& action_id) {
139 DCHECK(handler_list);
141 FileBrowserHandlerList::iterator iter = handler_list->begin();
142 while (iter != handler_list->end() &&
143 !((*iter)->extension_id() == extension_id &&
144 (*iter)->id() == action_id)) {
150 // This class is used to execute a file browser handler task. Here's how this
153 // 1) Open the "external" file system
154 // 2) Set up permissions for the target files on the external file system.
155 // 3) Raise onExecute event with the action ID and entries of the target
156 // files. The event will launch the file browser handler if not active.
157 // 4) In the file browser handler, onExecute event is handled and executes the
158 // task in JavaScript.
160 // That said, the class itself does not execute a task. The task will be
161 // executed in JavaScript.
162 class FileBrowserHandlerExecutor {
164 FileBrowserHandlerExecutor(Profile* profile,
165 const Extension* extension,
166 const std::string& action_id);
168 // Executes the task for each file. |done| will be run with the result.
169 void Execute(const std::vector<FileSystemURL>& file_urls,
170 const file_tasks::FileTaskFinishedCallback& done);
173 // This object is responsible to delete itself.
174 virtual ~FileBrowserHandlerExecutor();
176 struct FileDefinition {
180 base::FilePath virtual_path;
181 base::FilePath absolute_path;
185 typedef std::vector<FileDefinition> FileDefinitionList;
187 // Checks legitimacy of file url and grants file RO access permissions from
188 // handler (target) extension and its renderer process.
189 static FileDefinitionList SetupFileAccessPermissions(
190 scoped_refptr<fileapi::FileSystemContext> file_system_context_handler,
191 const scoped_refptr<const Extension>& handler_extension,
192 const std::vector<FileSystemURL>& file_urls);
194 void ExecuteDoneOnUIThread(bool success);
195 void ExecuteFileActionsOnUIThread(const FileDefinitionList& file_list);
196 void SetupPermissionsAndDispatchEvent(const std::string& file_system_name,
197 const GURL& file_system_root,
198 const FileDefinitionList& file_list,
200 extensions::ExtensionHost* host);
202 // Registers file permissions from |handler_host_permissions_| with
203 // ChildProcessSecurityPolicy for process with id |handler_pid|.
204 void SetupHandlerHostFileAccessPermissions(
205 const FileDefinitionList& file_list,
206 const Extension* extension,
210 scoped_refptr<const Extension> extension_;
211 const std::string action_id_;
212 file_tasks::FileTaskFinishedCallback done_;
213 base::WeakPtrFactory<FileBrowserHandlerExecutor> weak_ptr_factory_;
215 DISALLOW_COPY_AND_ASSIGN(FileBrowserHandlerExecutor);
218 FileBrowserHandlerExecutor::FileDefinition::FileDefinition()
219 : is_directory(false) {
222 FileBrowserHandlerExecutor::FileDefinition::~FileDefinition() {
226 FileBrowserHandlerExecutor::FileDefinitionList
227 FileBrowserHandlerExecutor::SetupFileAccessPermissions(
228 scoped_refptr<fileapi::FileSystemContext> file_system_context_handler,
229 const scoped_refptr<const Extension>& handler_extension,
230 const std::vector<FileSystemURL>& file_urls) {
231 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
232 DCHECK(handler_extension.get());
234 fileapi::ExternalFileSystemBackend* backend =
235 file_system_context_handler->external_backend();
237 FileDefinitionList file_list;
238 for (size_t i = 0; i < file_urls.size(); ++i) {
239 const FileSystemURL& url = file_urls[i];
241 // Check if this file system entry exists first.
242 base::File::Info file_info;
244 base::FilePath local_path = url.path();
245 base::FilePath virtual_path = url.virtual_path();
247 bool is_drive_file = url.type() == fileapi::kFileSystemTypeDrive;
248 DCHECK(!is_drive_file || drive::util::IsUnderDriveMountPoint(local_path));
250 // If the file is under drive mount point, there is no actual file to be
251 // found on the url.path().
252 if (!is_drive_file) {
253 if (!base::PathExists(local_path) ||
254 base::IsLink(local_path) ||
255 !base::GetFileInfo(local_path, &file_info)) {
260 // Grant access to this particular file to target extension. This will
261 // ensure that the target extension can access only this FS entry and
262 // prevent from traversing FS hierarchy upward.
263 backend->GrantFileAccessToExtension(
264 handler_extension->id(), virtual_path);
268 file.virtual_path = virtual_path;
269 file.is_directory = file_info.is_directory;
270 file.absolute_path = local_path;
271 file_list.push_back(file);
276 FileBrowserHandlerExecutor::FileBrowserHandlerExecutor(
278 const Extension* extension,
279 const std::string& action_id)
281 extension_(extension),
282 action_id_(action_id),
283 weak_ptr_factory_(this) {
286 FileBrowserHandlerExecutor::~FileBrowserHandlerExecutor() {}
288 void FileBrowserHandlerExecutor::Execute(
289 const std::vector<FileSystemURL>& file_urls,
290 const file_tasks::FileTaskFinishedCallback& done) {
293 // Get file system context for the extension to which onExecute event will be
294 // sent. The file access permissions will be granted to the extension in the
295 // file system context for the files in |file_urls|.
296 scoped_refptr<fileapi::FileSystemContext> file_system_context(
297 util::GetFileSystemContextForExtensionId(
298 profile_, extension_->id()));
300 BrowserThread::PostTaskAndReplyWithResult(
303 base::Bind(&SetupFileAccessPermissions,
307 base::Bind(&FileBrowserHandlerExecutor::ExecuteFileActionsOnUIThread,
308 weak_ptr_factory_.GetWeakPtr()));
311 void FileBrowserHandlerExecutor::ExecuteDoneOnUIThread(bool success) {
312 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
313 if (!done_.is_null())
318 void FileBrowserHandlerExecutor::ExecuteFileActionsOnUIThread(
319 const FileDefinitionList& file_list) {
320 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
322 if (file_list.empty()) {
323 ExecuteDoneOnUIThread(false);
327 int handler_pid = ExtractProcessFromExtensionId(profile_, extension_->id());
328 if (handler_pid <= 0 &&
329 !extensions::BackgroundInfo::HasLazyBackgroundPage(extension_.get())) {
330 ExecuteDoneOnUIThread(false);
334 fileapi::FileSystemInfo info =
335 fileapi::GetFileSystemInfoForChromeOS(
336 Extension::GetBaseURLFromExtensionId(extension_->id()).GetOrigin());
338 if (handler_pid > 0) {
339 SetupPermissionsAndDispatchEvent(info.name, info.root_url,
340 file_list, handler_pid, NULL);
342 // We have to wake the handler background page before we proceed.
343 extensions::LazyBackgroundTaskQueue* queue =
344 extensions::ExtensionSystem::Get(profile_)->
345 lazy_background_task_queue();
346 if (!queue->ShouldEnqueueTask(profile_, extension_.get())) {
347 ExecuteDoneOnUIThread(false);
350 queue->AddPendingTask(
351 profile_, extension_->id(),
353 &FileBrowserHandlerExecutor::SetupPermissionsAndDispatchEvent,
354 weak_ptr_factory_.GetWeakPtr(),
355 info.name, info.root_url, file_list, handler_pid));
359 void FileBrowserHandlerExecutor::SetupPermissionsAndDispatchEvent(
360 const std::string& file_system_name,
361 const GURL& file_system_root,
362 const FileDefinitionList& file_list,
364 extensions::ExtensionHost* host) {
365 int handler_pid = host ? host->render_process_host()->GetID() :
368 if (handler_pid <= 0) {
369 ExecuteDoneOnUIThread(false);
373 extensions::EventRouter* event_router =
374 extensions::ExtensionSystem::Get(profile_)->event_router();
376 ExecuteDoneOnUIThread(false);
380 SetupHandlerHostFileAccessPermissions(
381 file_list, extension_.get(), handler_pid);
383 scoped_ptr<base::ListValue> event_args(new base::ListValue());
384 event_args->Append(new base::StringValue(action_id_));
385 base::DictionaryValue* details = new base::DictionaryValue();
386 event_args->Append(details);
387 // Get file definitions. These will be replaced with Entry instances by
388 // dispatchEvent() method from event_binding.js.
389 base::ListValue* file_entries = new base::ListValue();
390 details->Set("entries", file_entries);
391 for (FileDefinitionList::const_iterator iter = file_list.begin();
392 iter != file_list.end();
394 base::DictionaryValue* file_def = new base::DictionaryValue();
395 file_entries->Append(file_def);
396 file_def->SetString("fileSystemName", file_system_name);
397 file_def->SetString("fileSystemRoot", file_system_root.spec());
398 base::FilePath root(FILE_PATH_LITERAL("/"));
399 base::FilePath full_path = root.Append(iter->virtual_path);
400 file_def->SetString("fileFullPath", full_path.value());
401 file_def->SetBoolean("fileIsDirectory", iter->is_directory);
404 scoped_ptr<extensions::Event> event(new extensions::Event(
405 "fileBrowserHandler.onExecute", event_args.Pass()));
406 event->restrict_to_browser_context = profile_;
407 event_router->DispatchEventToExtension(extension_->id(), event.Pass());
409 ExecuteDoneOnUIThread(true);
412 void FileBrowserHandlerExecutor::SetupHandlerHostFileAccessPermissions(
413 const FileDefinitionList& file_list,
414 const Extension* extension,
416 const FileBrowserHandler* action = FindFileBrowserHandlerForActionId(
417 extension_, action_id_);
418 for (FileDefinitionList::const_iterator iter = file_list.begin();
419 iter != file_list.end();
423 if (action->CanRead()) {
424 content::ChildProcessSecurityPolicy::GetInstance()->GrantReadFile(
425 handler_pid, iter->absolute_path);
427 if (action->CanWrite()) {
428 content::ChildProcessSecurityPolicy::GetInstance()->
429 GrantCreateReadWriteFile(handler_pid, iter->absolute_path);
434 // Returns true if |extension_id| and |action_id| indicate that the file
435 // currently being handled should be opened with the browser. This function
436 // is used to handle certain action IDs of the file manager.
437 bool ShouldBeOpenedWithBrowser(const std::string& extension_id,
438 const std::string& action_id) {
440 return (extension_id == kFileManagerAppId &&
441 (action_id == "view-pdf" ||
442 action_id == "view-swf" ||
443 action_id == "view-in-browser" ||
444 action_id == "open-hosted-generic" ||
445 action_id == "open-hosted-gdoc" ||
446 action_id == "open-hosted-gsheet" ||
447 action_id == "open-hosted-gslides"));
450 // Opens the files specified by |file_urls| with the browser for |profile|.
451 // Returns true on success. It's a failure if no files are opened.
452 bool OpenFilesWithBrowser(Profile* profile,
453 const std::vector<FileSystemURL>& file_urls) {
455 for (size_t i = 0; i < file_urls.size(); ++i) {
456 const FileSystemURL& file_url = file_urls[i];
457 if (chromeos::FileSystemBackend::CanHandleURL(file_url)) {
458 const base::FilePath& file_path = file_url.path();
459 num_opened += util::OpenFileWithBrowser(profile, file_path);
462 return num_opened > 0;
467 bool ExecuteFileBrowserHandler(
469 const Extension* extension,
470 const std::string& action_id,
471 const std::vector<FileSystemURL>& file_urls,
472 const file_tasks::FileTaskFinishedCallback& done) {
473 // Forbid calling undeclared handlers.
474 if (!FindFileBrowserHandlerForActionId(extension, action_id))
477 // Some action IDs of the file manager's file browser handlers require the
478 // files to be directly opened with the browser.
479 if (ShouldBeOpenedWithBrowser(extension->id(), action_id)) {
480 return OpenFilesWithBrowser(profile, file_urls);
483 // The executor object will be self deleted on completion.
484 (new FileBrowserHandlerExecutor(
485 profile, extension, action_id))->Execute(file_urls, done);
489 bool IsFallbackFileBrowserHandler(const file_tasks::TaskDescriptor& task) {
490 return (task.task_type == file_tasks::TASK_TYPE_FILE_BROWSER_HANDLER &&
491 (task.app_id == kFileManagerAppId ||
492 task.app_id == extension_misc::kQuickOfficeComponentExtensionId ||
493 task.app_id == extension_misc::kQuickOfficeDevExtensionId ||
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 // "watch" and "gallery" are defined in the file manager's manifest.json.
533 FileBrowserHandlerList::iterator watch_iter =
534 FindFileBrowserHandlerForExtensionIdAndActionId(
535 &common_handlers, kFileManagerAppId, "watch");
536 FileBrowserHandlerList::iterator gallery_iter =
537 FindFileBrowserHandlerForExtensionIdAndActionId(
538 &common_handlers, kFileManagerAppId, "gallery");
539 if (watch_iter != common_handlers.end() &&
540 gallery_iter != common_handlers.end()) {
541 // Both "watch" and "gallery" actions are applicable which means that the
542 // selection is all videos. Showing them both is confusing, so we only keep
543 // the one that makes more sense ("watch" for single selection, "gallery"
544 // for multiple selection).
545 if (file_list.size() == 1)
546 common_handlers.erase(gallery_iter);
548 common_handlers.erase(watch_iter);
551 return common_handlers;
554 } // namespace file_browser_handlers
555 } // namespace file_manager