Revert of Add button to add new FSP services to Files app. (patchset #8 id:140001...
[chromium-blink-merge.git] / chrome / browser / chromeos / file_manager / file_browser_handlers.cc
blobda0aebd2503d6479f7bbb889f888a5ab39528b9c
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"
7 #include <algorithm>
8 #include <set>
10 #include "base/bind.h"
11 #include "base/command_line.h"
12 #include "base/files/file_util.h"
13 #include "base/i18n/case_conversion.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "chrome/browser/chromeos/drive/file_system_util.h"
16 #include "chrome/browser/chromeos/file_manager/app_id.h"
17 #include "chrome/browser/chromeos/file_manager/fileapi_util.h"
18 #include "chrome/browser/chromeos/file_manager/open_with_browser.h"
19 #include "chrome/browser/chromeos/fileapi/file_system_backend.h"
20 #include "chrome/browser/extensions/extension_service.h"
21 #include "chrome/browser/extensions/extension_util.h"
22 #include "chrome/browser/profiles/profile.h"
23 #include "chrome/browser/ui/browser_finder.h"
24 #include "chrome/common/extensions/api/file_browser_handlers/file_browser_handler.h"
25 #include "chrome/common/extensions/api/file_manager_private.h"
26 #include "chromeos/chromeos_switches.h"
27 #include "content/public/browser/browser_thread.h"
28 #include "content/public/browser/child_process_security_policy.h"
29 #include "content/public/browser/render_process_host.h"
30 #include "content/public/browser/site_instance.h"
31 #include "content/public/browser/web_contents.h"
32 #include "extensions/browser/event_router.h"
33 #include "extensions/browser/extension_host.h"
34 #include "extensions/browser/extension_registry.h"
35 #include "extensions/browser/extension_system.h"
36 #include "extensions/browser/extension_util.h"
37 #include "extensions/browser/lazy_background_task_queue.h"
38 #include "extensions/common/extension_set.h"
39 #include "extensions/common/manifest_handlers/background_info.h"
40 #include "extensions/common/url_pattern.h"
41 #include "net/base/escape.h"
42 #include "storage/browser/fileapi/file_system_context.h"
43 #include "storage/browser/fileapi/file_system_url.h"
44 #include "storage/common/fileapi/file_system_info.h"
45 #include "storage/common/fileapi/file_system_util.h"
47 using content::BrowserThread;
48 using content::ChildProcessSecurityPolicy;
49 using content::SiteInstance;
50 using content::WebContents;
51 using extensions::Extension;
52 using storage::FileSystemURL;
53 using file_manager::util::EntryDefinition;
54 using file_manager::util::EntryDefinitionList;
55 using file_manager::util::FileDefinition;
56 using file_manager::util::FileDefinitionList;
58 namespace file_manager {
59 namespace file_browser_handlers {
60 namespace {
62 // Returns process id of the process the extension is running in.
63 int ExtractProcessFromExtensionId(Profile* profile,
64 const std::string& extension_id) {
65 GURL extension_url =
66 Extension::GetBaseURLFromExtensionId(extension_id);
67 extensions::ProcessManager* manager =
68 extensions::ProcessManager::Get(profile);
70 scoped_refptr<SiteInstance> site_instance =
71 manager->GetSiteInstanceForURL(extension_url);
72 if (!site_instance || !site_instance->HasProcess())
73 return -1;
74 content::RenderProcessHost* process = site_instance->GetProcess();
76 return process->GetID();
79 // Finds a file browser handler that matches |action_id|. Returns NULL if not
80 // found.
81 const FileBrowserHandler* FindFileBrowserHandlerForActionId(
82 const Extension* extension,
83 const std::string& action_id) {
84 FileBrowserHandler::List* handler_list =
85 FileBrowserHandler::GetHandlers(extension);
86 for (FileBrowserHandler::List::const_iterator handler_iter =
87 handler_list->begin();
88 handler_iter != handler_list->end();
89 ++handler_iter) {
90 if (handler_iter->get()->id() == action_id)
91 return handler_iter->get();
93 return NULL;
96 std::string EscapedUtf8ToLower(const std::string& str) {
97 base::string16 utf16 = base::UTF8ToUTF16(
98 net::UnescapeURLComponent(str, net::UnescapeRule::NORMAL));
99 return net::EscapeUrlEncodedData(
100 base::UTF16ToUTF8(base::i18n::ToLower(utf16)),
101 false /* do not replace space with plus */);
104 // Finds file browser handlers that can handle the |selected_file_url|.
105 FileBrowserHandlerList FindFileBrowserHandlersForURL(
106 Profile* profile,
107 const GURL& selected_file_url) {
108 extensions::ExtensionRegistry* registry =
109 extensions::ExtensionRegistry::Get(profile);
110 // In unit-tests, we may not have an ExtensionRegistry.
111 if (!registry)
112 return FileBrowserHandlerList();
114 // We need case-insensitive matching, and pattern in the handler is already
115 // in lower case.
116 const GURL lowercase_url(EscapedUtf8ToLower(selected_file_url.spec()));
118 FileBrowserHandlerList results;
119 for (const scoped_refptr<const Extension>& extension :
120 registry->enabled_extensions()) {
121 if (profile->IsOffTheRecord() &&
122 !extensions::util::IsIncognitoEnabled(extension->id(), profile))
123 continue;
124 if (extensions::util::IsEphemeralApp(extension->id(), profile))
125 continue;
127 FileBrowserHandler::List* handler_list =
128 FileBrowserHandler::GetHandlers(extension.get());
129 if (!handler_list)
130 continue;
131 for (FileBrowserHandler::List::const_iterator handler_iter =
132 handler_list->begin();
133 handler_iter != handler_list->end();
134 ++handler_iter) {
135 const FileBrowserHandler* handler = handler_iter->get();
136 if (!handler->MatchesURL(lowercase_url))
137 continue;
138 // Filter out Files app from handling ZIP files via a handler, as it's
139 // now handled by new ZIP unpacker extension based on File System Provider
140 // API.
141 const URLPattern zip_pattern(URLPattern::SCHEME_EXTENSION,
142 "chrome-extension://*/*.zip");
143 if (handler->extension_id() == kFileManagerAppId &&
144 zip_pattern.MatchesURL(selected_file_url) &&
145 !base::CommandLine::ForCurrentProcess()->HasSwitch(
146 chromeos::switches::kDisableNewZIPUnpacker)) {
147 continue;
149 results.push_back(handler);
152 return results;
155 // This class is used to execute a file browser handler task. Here's how this
156 // works:
158 // 1) Open the "external" file system
159 // 2) Set up permissions for the target files on the external file system.
160 // 3) Raise onExecute event with the action ID and entries of the target
161 // files. The event will launch the file browser handler if not active.
162 // 4) In the file browser handler, onExecute event is handled and executes the
163 // task in JavaScript.
165 // That said, the class itself does not execute a task. The task will be
166 // executed in JavaScript.
167 class FileBrowserHandlerExecutor {
168 public:
169 FileBrowserHandlerExecutor(Profile* profile,
170 const Extension* extension,
171 const std::string& action_id);
173 // Executes the task for each file. |done| will be run with the result.
174 void Execute(const std::vector<FileSystemURL>& file_urls,
175 const file_tasks::FileTaskFinishedCallback& done);
177 private:
178 // This object is responsible to delete itself.
179 virtual ~FileBrowserHandlerExecutor();
181 // Checks legitimacy of file url and grants file RO access permissions from
182 // handler (target) extension and its renderer process.
183 static scoped_ptr<FileDefinitionList> SetupFileAccessPermissions(
184 scoped_refptr<storage::FileSystemContext> file_system_context_handler,
185 const scoped_refptr<const Extension>& handler_extension,
186 const std::vector<FileSystemURL>& file_urls);
188 void ExecuteDoneOnUIThread(bool success);
189 void ExecuteAfterSetupFileAccess(scoped_ptr<FileDefinitionList> file_list);
190 void ExecuteFileActionsOnUIThread(
191 scoped_ptr<FileDefinitionList> file_definition_list,
192 scoped_ptr<EntryDefinitionList> entry_definition_list);
193 void SetupPermissionsAndDispatchEvent(
194 scoped_ptr<FileDefinitionList> file_definition_list,
195 scoped_ptr<EntryDefinitionList> entry_definition_list,
196 int handler_pid_in,
197 extensions::ExtensionHost* host);
199 // Registers file permissions from |handler_host_permissions_| with
200 // ChildProcessSecurityPolicy for process with id |handler_pid|.
201 void SetupHandlerHostFileAccessPermissions(
202 FileDefinitionList* file_definition_list,
203 const Extension* extension,
204 int handler_pid);
206 Profile* profile_;
207 scoped_refptr<const Extension> extension_;
208 const std::string action_id_;
209 file_tasks::FileTaskFinishedCallback done_;
210 base::WeakPtrFactory<FileBrowserHandlerExecutor> weak_ptr_factory_;
212 DISALLOW_COPY_AND_ASSIGN(FileBrowserHandlerExecutor);
215 // static
216 scoped_ptr<FileDefinitionList>
217 FileBrowserHandlerExecutor::SetupFileAccessPermissions(
218 scoped_refptr<storage::FileSystemContext> file_system_context_handler,
219 const scoped_refptr<const Extension>& handler_extension,
220 const std::vector<FileSystemURL>& file_urls) {
221 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
222 DCHECK(handler_extension.get());
224 storage::ExternalFileSystemBackend* backend =
225 file_system_context_handler->external_backend();
227 scoped_ptr<FileDefinitionList> file_definition_list(new FileDefinitionList);
228 for (size_t i = 0; i < file_urls.size(); ++i) {
229 const FileSystemURL& url = file_urls[i];
231 // Check if this file system entry exists first.
232 base::File::Info file_info;
234 base::FilePath local_path = url.path();
235 base::FilePath virtual_path = url.virtual_path();
237 const bool is_drive_file = url.type() == storage::kFileSystemTypeDrive;
238 DCHECK(!is_drive_file || drive::util::IsUnderDriveMountPoint(local_path));
240 const bool is_native_file =
241 url.type() == storage::kFileSystemTypeNativeLocal ||
242 url.type() == storage::kFileSystemTypeRestrictedNativeLocal;
244 // If the file is from a physical volume, actual file must be found.
245 if (is_native_file) {
246 if (!base::PathExists(local_path) ||
247 base::IsLink(local_path) ||
248 !base::GetFileInfo(local_path, &file_info)) {
249 continue;
253 // Grant access to this particular file to target extension. This will
254 // ensure that the target extension can access only this FS entry and
255 // prevent from traversing FS hierarchy upward.
256 backend->GrantFileAccessToExtension(handler_extension->id(), virtual_path);
258 // Output values.
259 FileDefinition file_definition;
260 file_definition.virtual_path = virtual_path;
261 file_definition.is_directory = file_info.is_directory;
262 file_definition.absolute_path = local_path;
263 file_definition_list->push_back(file_definition);
266 return file_definition_list.Pass();
269 FileBrowserHandlerExecutor::FileBrowserHandlerExecutor(
270 Profile* profile,
271 const Extension* extension,
272 const std::string& action_id)
273 : profile_(profile),
274 extension_(extension),
275 action_id_(action_id),
276 weak_ptr_factory_(this) {
279 FileBrowserHandlerExecutor::~FileBrowserHandlerExecutor() {}
281 void FileBrowserHandlerExecutor::Execute(
282 const std::vector<FileSystemURL>& file_urls,
283 const file_tasks::FileTaskFinishedCallback& done) {
284 done_ = done;
286 // Get file system context for the extension to which onExecute event will be
287 // sent. The file access permissions will be granted to the extension in the
288 // file system context for the files in |file_urls|.
289 scoped_refptr<storage::FileSystemContext> file_system_context(
290 util::GetFileSystemContextForExtensionId(profile_, extension_->id()));
292 BrowserThread::PostTaskAndReplyWithResult(
293 BrowserThread::FILE,
294 FROM_HERE,
295 base::Bind(&SetupFileAccessPermissions,
296 file_system_context,
297 extension_,
298 file_urls),
299 base::Bind(&FileBrowserHandlerExecutor::ExecuteAfterSetupFileAccess,
300 weak_ptr_factory_.GetWeakPtr()));
303 void FileBrowserHandlerExecutor::ExecuteAfterSetupFileAccess(
304 scoped_ptr<FileDefinitionList> file_definition_list) {
305 // Outlives the conversion process, since bound to the callback.
306 const FileDefinitionList& file_definition_list_ref =
307 *file_definition_list.get();
308 file_manager::util::ConvertFileDefinitionListToEntryDefinitionList(
309 profile_,
310 extension_->id(),
311 file_definition_list_ref,
312 base::Bind(&FileBrowserHandlerExecutor::ExecuteFileActionsOnUIThread,
313 weak_ptr_factory_.GetWeakPtr(),
314 base::Passed(&file_definition_list)));
317 void FileBrowserHandlerExecutor::ExecuteDoneOnUIThread(bool success) {
318 DCHECK_CURRENTLY_ON(BrowserThread::UI);
319 if (!done_.is_null())
320 done_.Run(
321 success
322 ? extensions::api::file_manager_private::TASK_RESULT_MESSAGE_SENT
323 : extensions::api::file_manager_private::TASK_RESULT_FAILED);
324 delete this;
327 void FileBrowserHandlerExecutor::ExecuteFileActionsOnUIThread(
328 scoped_ptr<FileDefinitionList> file_definition_list,
329 scoped_ptr<EntryDefinitionList> entry_definition_list) {
330 DCHECK_CURRENTLY_ON(BrowserThread::UI);
332 if (file_definition_list->empty() || entry_definition_list->empty()) {
333 ExecuteDoneOnUIThread(false);
334 return;
337 int handler_pid = ExtractProcessFromExtensionId(profile_, extension_->id());
338 if (handler_pid <= 0 &&
339 !extensions::BackgroundInfo::HasLazyBackgroundPage(extension_.get())) {
340 ExecuteDoneOnUIThread(false);
341 return;
344 if (handler_pid > 0) {
345 SetupPermissionsAndDispatchEvent(file_definition_list.Pass(),
346 entry_definition_list.Pass(),
347 handler_pid,
348 NULL);
349 } else {
350 // We have to wake the handler background page before we proceed.
351 extensions::LazyBackgroundTaskQueue* queue =
352 extensions::ExtensionSystem::Get(profile_)->
353 lazy_background_task_queue();
354 if (!queue->ShouldEnqueueTask(profile_, extension_.get())) {
355 ExecuteDoneOnUIThread(false);
356 return;
358 queue->AddPendingTask(
359 profile_,
360 extension_->id(),
361 base::Bind(
362 &FileBrowserHandlerExecutor::SetupPermissionsAndDispatchEvent,
363 weak_ptr_factory_.GetWeakPtr(),
364 base::Passed(file_definition_list.Pass()),
365 base::Passed(entry_definition_list.Pass()),
366 handler_pid));
370 void FileBrowserHandlerExecutor::SetupPermissionsAndDispatchEvent(
371 scoped_ptr<FileDefinitionList> file_definition_list,
372 scoped_ptr<EntryDefinitionList> entry_definition_list,
373 int handler_pid_in,
374 extensions::ExtensionHost* host) {
375 int handler_pid = host ? host->render_process_host()->GetID() :
376 handler_pid_in;
378 if (handler_pid <= 0) {
379 ExecuteDoneOnUIThread(false);
380 return;
383 extensions::EventRouter* router = extensions::EventRouter::Get(profile_);
384 if (!router) {
385 ExecuteDoneOnUIThread(false);
386 return;
389 SetupHandlerHostFileAccessPermissions(
390 file_definition_list.get(), extension_.get(), handler_pid);
392 scoped_ptr<base::ListValue> event_args(new base::ListValue());
393 event_args->Append(new base::StringValue(action_id_));
394 base::DictionaryValue* details = new base::DictionaryValue();
395 event_args->Append(details);
396 // Get file definitions. These will be replaced with Entry instances by
397 // dispatchEvent() method from event_binding.js.
398 base::ListValue* file_entries = new base::ListValue();
399 details->Set("entries", file_entries);
401 for (EntryDefinitionList::const_iterator iter =
402 entry_definition_list->begin();
403 iter != entry_definition_list->end();
404 ++iter) {
405 base::DictionaryValue* file_def = new base::DictionaryValue();
406 file_entries->Append(file_def);
407 file_def->SetString("fileSystemName", iter->file_system_name);
408 file_def->SetString("fileSystemRoot", iter->file_system_root_url);
409 file_def->SetString("fileFullPath",
410 "/" + iter->full_path.AsUTF8Unsafe());
411 file_def->SetBoolean("fileIsDirectory", iter->is_directory);
414 scoped_ptr<extensions::Event> event(new extensions::Event(
415 "fileBrowserHandler.onExecute", event_args.Pass()));
416 event->restrict_to_browser_context = profile_;
417 router->DispatchEventToExtension(extension_->id(), event.Pass());
419 ExecuteDoneOnUIThread(true);
422 void FileBrowserHandlerExecutor::SetupHandlerHostFileAccessPermissions(
423 FileDefinitionList* file_definition_list,
424 const Extension* extension,
425 int handler_pid) {
426 const FileBrowserHandler* action =
427 FindFileBrowserHandlerForActionId(extension_.get(), action_id_);
428 for (FileDefinitionList::const_iterator iter = file_definition_list->begin();
429 iter != file_definition_list->end();
430 ++iter) {
431 if (!action)
432 continue;
433 if (action->CanRead()) {
434 content::ChildProcessSecurityPolicy::GetInstance()->GrantReadFile(
435 handler_pid, iter->absolute_path);
437 if (action->CanWrite()) {
438 content::ChildProcessSecurityPolicy::GetInstance()->
439 GrantCreateReadWriteFile(handler_pid, iter->absolute_path);
444 // Returns true if |extension_id| and |action_id| indicate that the file
445 // currently being handled should be opened with the browser. This function
446 // is used to handle certain action IDs of the file manager.
447 bool ShouldBeOpenedWithBrowser(const std::string& extension_id,
448 const std::string& action_id) {
449 return (extension_id == kFileManagerAppId &&
450 (action_id == "view-pdf" ||
451 action_id == "view-swf" ||
452 action_id == "view-in-browser" ||
453 action_id == "open-hosted-generic" ||
454 action_id == "open-hosted-gdoc" ||
455 action_id == "open-hosted-gsheet" ||
456 action_id == "open-hosted-gslides"));
459 // Opens the files specified by |file_urls| with the browser for |profile|.
460 // Returns true on success. It's a failure if no files are opened.
461 bool OpenFilesWithBrowser(Profile* profile,
462 const std::vector<FileSystemURL>& file_urls) {
463 int num_opened = 0;
464 for (size_t i = 0; i < file_urls.size(); ++i) {
465 const FileSystemURL& file_url = file_urls[i];
466 if (chromeos::FileSystemBackend::CanHandleURL(file_url)) {
467 num_opened += util::OpenFileWithBrowser(profile, file_url) ? 1 : 0;
470 return num_opened > 0;
473 } // namespace
475 bool ExecuteFileBrowserHandler(
476 Profile* profile,
477 const Extension* extension,
478 const std::string& action_id,
479 const std::vector<FileSystemURL>& file_urls,
480 const file_tasks::FileTaskFinishedCallback& done) {
481 // Forbid calling undeclared handlers.
482 if (!FindFileBrowserHandlerForActionId(extension, action_id))
483 return false;
485 // Some action IDs of the file manager's file browser handlers require the
486 // files to be directly opened with the browser.
487 if (ShouldBeOpenedWithBrowser(extension->id(), action_id)) {
488 const bool result = OpenFilesWithBrowser(profile, file_urls);
489 if (result && !done.is_null())
490 done.Run(extensions::api::file_manager_private::TASK_RESULT_OPENED);
491 return result;
494 // The executor object will be self deleted on completion.
495 (new FileBrowserHandlerExecutor(
496 profile, extension, action_id))->Execute(file_urls, done);
497 return true;
500 FileBrowserHandlerList FindFileBrowserHandlers(
501 Profile* profile,
502 const std::vector<GURL>& file_list) {
503 FileBrowserHandlerList common_handlers;
504 for (std::vector<GURL>::const_iterator it = file_list.begin();
505 it != file_list.end(); ++it) {
506 FileBrowserHandlerList handlers =
507 FindFileBrowserHandlersForURL(profile, *it);
508 // If there is nothing to do for one file, the intersection of handlers
509 // for all files will be empty at the end, so no need to check further.
510 if (handlers.empty())
511 return FileBrowserHandlerList();
513 // For the very first file, just copy all the elements.
514 if (it == file_list.begin()) {
515 common_handlers = handlers;
516 } else {
517 // For all additional files, find intersection between the accumulated and
518 // file specific set.
519 FileBrowserHandlerList intersection;
520 std::set<const FileBrowserHandler*> common_handler_set(
521 common_handlers.begin(), common_handlers.end());
523 for (FileBrowserHandlerList::const_iterator itr = handlers.begin();
524 itr != handlers.end(); ++itr) {
525 if (ContainsKey(common_handler_set, *itr))
526 intersection.push_back(*itr);
529 std::swap(common_handlers, intersection);
530 if (common_handlers.empty())
531 return FileBrowserHandlerList();
535 return common_handlers;
538 } // namespace file_browser_handlers
539 } // namespace file_manager