file_browser_handlers.cc revision f2477e01787aa58f445919b809d89e252beef54f
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.
4
5#include "chrome/browser/chromeos/file_manager/file_browser_handlers.h"
6
7#include "base/bind.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/manifest_handlers/background_info.h"
31#include "net/base/escape.h"
32#include "webkit/browser/fileapi/file_system_context.h"
33#include "webkit/browser/fileapi/file_system_url.h"
34#include "webkit/common/fileapi/file_system_info.h"
35#include "webkit/common/fileapi/file_system_util.h"
36
37using content::BrowserThread;
38using content::ChildProcessSecurityPolicy;
39using content::SiteInstance;
40using content::WebContents;
41using extensions::Extension;
42using fileapi::FileSystemURL;
43
44namespace file_manager {
45namespace file_browser_handlers {
46namespace {
47
48// Returns process id of the process the extension is running in.
49int ExtractProcessFromExtensionId(Profile* profile,
50                                  const std::string& extension_id) {
51  GURL extension_url =
52      Extension::GetBaseURLFromExtensionId(extension_id);
53  extensions::ProcessManager* manager =
54    extensions::ExtensionSystem::Get(profile)->process_manager();
55
56  SiteInstance* site_instance = manager->GetSiteInstanceForURL(extension_url);
57  if (!site_instance || !site_instance->HasProcess())
58    return -1;
59  content::RenderProcessHost* process = site_instance->GetProcess();
60
61  return process->GetID();
62}
63
64// Finds a file browser handler that matches |action_id|. Returns NULL if not
65// found.
66const FileBrowserHandler* FindFileBrowserHandlerForActionId(
67    const Extension* extension,
68    const std::string& action_id) {
69  FileBrowserHandler::List* handler_list =
70      FileBrowserHandler::GetHandlers(extension);
71  for (FileBrowserHandler::List::const_iterator handler_iter =
72           handler_list->begin();
73       handler_iter != handler_list->end();
74       ++handler_iter) {
75    if (handler_iter->get()->id() == action_id)
76      return handler_iter->get();
77  }
78  return NULL;
79}
80
81std::string EscapedUtf8ToLower(const std::string& str) {
82  string16 utf16 = UTF8ToUTF16(
83      net::UnescapeURLComponent(str, net::UnescapeRule::NORMAL));
84  return net::EscapeUrlEncodedData(
85      UTF16ToUTF8(base::i18n::ToLower(utf16)),
86      false /* do not replace space with plus */);
87}
88
89// Finds file browser handlers that can handle the |selected_file_url|.
90FileBrowserHandlerList FindFileBrowserHandlersForURL(
91    Profile* profile,
92    const GURL& selected_file_url) {
93  ExtensionService* service =
94      extensions::ExtensionSystem::Get(profile)->extension_service();
95  // In unit-tests, we may not have an ExtensionService.
96  if (!service)
97    return FileBrowserHandlerList();
98
99  // We need case-insensitive matching, and pattern in the handler is already
100  // in lower case.
101  const GURL lowercase_url(EscapedUtf8ToLower(selected_file_url.spec()));
102
103  FileBrowserHandlerList results;
104  for (ExtensionSet::const_iterator iter = service->extensions()->begin();
105       iter != service->extensions()->end();
106       ++iter) {
107    const Extension* extension = iter->get();
108    if (profile->IsOffTheRecord() &&
109        !extension_util::IsIncognitoEnabled(extension->id(), service))
110      continue;
111
112    FileBrowserHandler::List* handler_list =
113        FileBrowserHandler::GetHandlers(extension);
114    if (!handler_list)
115      continue;
116    for (FileBrowserHandler::List::const_iterator handler_iter =
117             handler_list->begin();
118         handler_iter != handler_list->end();
119         ++handler_iter) {
120      const FileBrowserHandler* handler = handler_iter->get();
121      if (!handler->MatchesURL(lowercase_url))
122        continue;
123
124      results.push_back(handler_iter->get());
125    }
126  }
127  return results;
128}
129
130// Finds a file browser handler that matches |extension_id| and |action_id|
131// from |handler_list|.  Returns a mutable iterator to the handler if
132// found. Returns handler_list->end() if not found.
133FileBrowserHandlerList::iterator
134FindFileBrowserHandlerForExtensionIdAndActionId(
135    FileBrowserHandlerList* handler_list,
136    const std::string& extension_id,
137    const std::string& action_id) {
138  DCHECK(handler_list);
139
140  FileBrowserHandlerList::iterator iter = handler_list->begin();
141  while (iter != handler_list->end() &&
142         !((*iter)->extension_id() == extension_id &&
143           (*iter)->id() == action_id)) {
144    ++iter;
145  }
146  return iter;
147}
148
149// This class is used to execute a file browser handler task. Here's how this
150// works:
151//
152// 1) Open the "external" file system
153// 2) Set up permissions for the target files on the external file system.
154// 3) Raise onExecute event with the action ID and entries of the target
155//    files. The event will launch the file browser handler if not active.
156// 4) In the file browser handler, onExecute event is handled and executes the
157//    task in JavaScript.
158//
159// That said, the class itself does not execute a task. The task will be
160// executed in JavaScript.
161class FileBrowserHandlerExecutor {
162 public:
163  FileBrowserHandlerExecutor(Profile* profile,
164                             const Extension* extension,
165                             int32 tab_id,
166                             const std::string& action_id);
167
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);
171
172 private:
173  // This object is responsible to delete itself.
174  virtual ~FileBrowserHandlerExecutor();
175
176  struct FileDefinition {
177    FileDefinition();
178    ~FileDefinition();
179
180    base::FilePath virtual_path;
181    base::FilePath absolute_path;
182    bool is_directory;
183  };
184
185  typedef std::vector<FileDefinition> FileDefinitionList;
186
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);
193
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,
199                                        int handler_pid_in,
200                                        extensions::ExtensionHost* host);
201
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,
207      int handler_pid);
208
209  Profile* profile_;
210  scoped_refptr<const Extension> extension_;
211  int32 tab_id_;
212  const std::string action_id_;
213  file_tasks::FileTaskFinishedCallback done_;
214  base::WeakPtrFactory<FileBrowserHandlerExecutor> weak_ptr_factory_;
215
216  DISALLOW_COPY_AND_ASSIGN(FileBrowserHandlerExecutor);
217};
218
219FileBrowserHandlerExecutor::FileDefinition::FileDefinition()
220    : is_directory(false) {
221}
222
223FileBrowserHandlerExecutor::FileDefinition::~FileDefinition() {
224}
225
226// static
227FileBrowserHandlerExecutor::FileDefinitionList
228FileBrowserHandlerExecutor::SetupFileAccessPermissions(
229    scoped_refptr<fileapi::FileSystemContext> file_system_context_handler,
230    const scoped_refptr<const Extension>& handler_extension,
231    const std::vector<FileSystemURL>& file_urls) {
232  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
233  DCHECK(handler_extension.get());
234
235  fileapi::ExternalFileSystemBackend* backend =
236      file_system_context_handler->external_backend();
237
238  FileDefinitionList file_list;
239  for (size_t i = 0; i < file_urls.size(); ++i) {
240    const FileSystemURL& url = file_urls[i];
241
242    // Check if this file system entry exists first.
243    base::PlatformFileInfo file_info;
244
245    base::FilePath local_path = url.path();
246    base::FilePath virtual_path = url.virtual_path();
247
248    bool is_drive_file = url.type() == fileapi::kFileSystemTypeDrive;
249    DCHECK(!is_drive_file || drive::util::IsUnderDriveMountPoint(local_path));
250
251    // If the file is under drive mount point, there is no actual file to be
252    // found on the url.path().
253    if (!is_drive_file) {
254      if (!base::PathExists(local_path) ||
255          file_util::IsLink(local_path) ||
256          !file_util::GetFileInfo(local_path, &file_info)) {
257        continue;
258      }
259    }
260
261    // Grant access to this particular file to target extension. This will
262    // ensure that the target extension can access only this FS entry and
263    // prevent from traversing FS hierarchy upward.
264    backend->GrantFileAccessToExtension(
265        handler_extension->id(), virtual_path);
266
267    // Output values.
268    FileDefinition file;
269    file.virtual_path = virtual_path;
270    file.is_directory = file_info.is_directory;
271    file.absolute_path = local_path;
272    file_list.push_back(file);
273  }
274  return file_list;
275}
276
277FileBrowserHandlerExecutor::FileBrowserHandlerExecutor(
278    Profile* profile,
279    const Extension* extension,
280    int32 tab_id,
281    const std::string& action_id)
282    : profile_(profile),
283      extension_(extension),
284      tab_id_(tab_id),
285      action_id_(action_id),
286      weak_ptr_factory_(this) {
287}
288
289FileBrowserHandlerExecutor::~FileBrowserHandlerExecutor() {}
290
291void FileBrowserHandlerExecutor::Execute(
292    const std::vector<FileSystemURL>& file_urls,
293    const file_tasks::FileTaskFinishedCallback& done) {
294  done_ = done;
295
296  // Get file system context for the extension to which onExecute event will be
297  // sent. The file access permissions will be granted to the extension in the
298  // file system context for the files in |file_urls|.
299  scoped_refptr<fileapi::FileSystemContext> file_system_context(
300      util::GetFileSystemContextForExtensionId(
301          profile_, extension_->id()));
302
303  BrowserThread::PostTaskAndReplyWithResult(
304      BrowserThread::FILE,
305      FROM_HERE,
306      base::Bind(&SetupFileAccessPermissions,
307                 file_system_context,
308                 extension_,
309                 file_urls),
310      base::Bind(&FileBrowserHandlerExecutor::ExecuteFileActionsOnUIThread,
311                 weak_ptr_factory_.GetWeakPtr()));
312}
313
314void FileBrowserHandlerExecutor::ExecuteDoneOnUIThread(bool success) {
315  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
316  if (!done_.is_null())
317    done_.Run(success);
318  delete this;
319}
320
321void FileBrowserHandlerExecutor::ExecuteFileActionsOnUIThread(
322    const FileDefinitionList& file_list) {
323  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
324
325  if (file_list.empty()) {
326    ExecuteDoneOnUIThread(false);
327    return;
328  }
329
330  int handler_pid = ExtractProcessFromExtensionId(profile_, extension_->id());
331  if (handler_pid <= 0 &&
332      !extensions::BackgroundInfo::HasLazyBackgroundPage(extension_.get())) {
333    ExecuteDoneOnUIThread(false);
334    return;
335  }
336
337  fileapi::FileSystemInfo info =
338      fileapi::GetFileSystemInfoForChromeOS(
339          Extension::GetBaseURLFromExtensionId(extension_->id()).GetOrigin());
340
341  if (handler_pid > 0) {
342    SetupPermissionsAndDispatchEvent(info.name, info.root_url,
343                                     file_list, handler_pid, NULL);
344  } else {
345    // We have to wake the handler background page before we proceed.
346    extensions::LazyBackgroundTaskQueue* queue =
347        extensions::ExtensionSystem::Get(profile_)->
348        lazy_background_task_queue();
349    if (!queue->ShouldEnqueueTask(profile_, extension_.get())) {
350      ExecuteDoneOnUIThread(false);
351      return;
352    }
353    queue->AddPendingTask(
354        profile_, extension_->id(),
355        base::Bind(
356            &FileBrowserHandlerExecutor::SetupPermissionsAndDispatchEvent,
357            weak_ptr_factory_.GetWeakPtr(),
358            info.name, info.root_url, file_list, handler_pid));
359  }
360}
361
362void FileBrowserHandlerExecutor::SetupPermissionsAndDispatchEvent(
363    const std::string& file_system_name,
364    const GURL& file_system_root,
365    const FileDefinitionList& file_list,
366    int handler_pid_in,
367    extensions::ExtensionHost* host) {
368  int handler_pid = host ? host->render_process_host()->GetID() :
369      handler_pid_in;
370
371  if (handler_pid <= 0) {
372    ExecuteDoneOnUIThread(false);
373    return;
374  }
375
376  extensions::EventRouter* event_router =
377      extensions::ExtensionSystem::Get(profile_)->event_router();
378  if (!event_router) {
379    ExecuteDoneOnUIThread(false);
380    return;
381  }
382
383  SetupHandlerHostFileAccessPermissions(
384      file_list, extension_.get(), handler_pid);
385
386  scoped_ptr<ListValue> event_args(new ListValue());
387  event_args->Append(new base::StringValue(action_id_));
388  DictionaryValue* details = new DictionaryValue();
389  event_args->Append(details);
390  // Get file definitions. These will be replaced with Entry instances by
391  // dispatchEvent() method from event_binding.js.
392  ListValue* file_entries = new ListValue();
393  details->Set("entries", file_entries);
394  for (FileDefinitionList::const_iterator iter = file_list.begin();
395       iter != file_list.end();
396       ++iter) {
397    DictionaryValue* file_def = new DictionaryValue();
398    file_entries->Append(file_def);
399    file_def->SetString("fileSystemName", file_system_name);
400    file_def->SetString("fileSystemRoot", file_system_root.spec());
401    base::FilePath root(FILE_PATH_LITERAL("/"));
402    base::FilePath full_path = root.Append(iter->virtual_path);
403    file_def->SetString("fileFullPath", full_path.value());
404    file_def->SetBoolean("fileIsDirectory", iter->is_directory);
405  }
406
407  details->SetInteger("tab_id", tab_id_);
408
409  scoped_ptr<extensions::Event> event(new extensions::Event(
410      "fileBrowserHandler.onExecute", event_args.Pass()));
411  event->restrict_to_browser_context = profile_;
412  event_router->DispatchEventToExtension(extension_->id(), event.Pass());
413
414  ExecuteDoneOnUIThread(true);
415}
416
417void FileBrowserHandlerExecutor::SetupHandlerHostFileAccessPermissions(
418    const FileDefinitionList& file_list,
419    const Extension* extension,
420    int handler_pid) {
421  const FileBrowserHandler* action = FindFileBrowserHandlerForActionId(
422      extension_, action_id_);
423  for (FileDefinitionList::const_iterator iter = file_list.begin();
424       iter != file_list.end();
425       ++iter) {
426    if (!action)
427      continue;
428    if (action->CanRead()) {
429      content::ChildProcessSecurityPolicy::GetInstance()->GrantReadFile(
430          handler_pid, iter->absolute_path);
431    }
432    if (action->CanWrite()) {
433      content::ChildProcessSecurityPolicy::GetInstance()->
434          GrantCreateReadWriteFile(handler_pid, iter->absolute_path);
435    }
436  }
437}
438
439// Returns true if |extension_id| and |action_id| indicate that the file
440// currently being handled should be opened with the browser. This function
441// is used to handle certain action IDs of the file manager.
442bool ShouldBeOpenedWithBrowser(const std::string& extension_id,
443                               const std::string& action_id) {
444
445  return (extension_id == kFileManagerAppId &&
446          (action_id == "view-pdf" ||
447           action_id == "view-swf" ||
448           action_id == "view-in-browser" ||
449           action_id == "open-hosted-generic" ||
450           action_id == "open-hosted-gdoc" ||
451           action_id == "open-hosted-gsheet" ||
452           action_id == "open-hosted-gslides"));
453}
454
455// Opens the files specified by |file_urls| with the browser for |profile|.
456// Returns true on success. It's a failure if no files are opened.
457bool OpenFilesWithBrowser(Profile* profile,
458                          const std::vector<FileSystemURL>& file_urls) {
459  int num_opened = 0;
460  for (size_t i = 0; i < file_urls.size(); ++i) {
461    const FileSystemURL& file_url = file_urls[i];
462    if (chromeos::FileSystemBackend::CanHandleURL(file_url)) {
463      const base::FilePath& file_path = file_url.path();
464      num_opened += util::OpenFileWithBrowser(profile, file_path);
465    }
466  }
467  return num_opened > 0;
468}
469
470}  // namespace
471
472bool ExecuteFileBrowserHandler(
473    Profile* profile,
474    const Extension* extension,
475    int32 tab_id,
476    const std::string& action_id,
477    const std::vector<FileSystemURL>& file_urls,
478    const file_tasks::FileTaskFinishedCallback& done) {
479  // Forbid calling undeclared handlers.
480  if (!FindFileBrowserHandlerForActionId(extension, action_id))
481    return false;
482
483  // Some action IDs of the file manager's file browser handlers require the
484  // files to be directly opened with the browser.
485  if (ShouldBeOpenedWithBrowser(extension->id(), action_id)) {
486    return OpenFilesWithBrowser(profile, file_urls);
487  }
488
489  // The executor object will be self deleted on completion.
490  (new FileBrowserHandlerExecutor(
491      profile, extension, tab_id, action_id))->Execute(file_urls, done);
492  return true;
493}
494
495bool IsFallbackFileBrowserHandler(const file_tasks::TaskDescriptor& task) {
496  return (task.task_type == file_tasks::TASK_TYPE_FILE_BROWSER_HANDLER &&
497          (task.app_id == kFileManagerAppId ||
498           task.app_id == extension_misc::kQuickOfficeComponentExtensionId ||
499           task.app_id == extension_misc::kQuickOfficeDevExtensionId ||
500           task.app_id == extension_misc::kQuickOfficeExtensionId));
501}
502
503FileBrowserHandlerList FindFileBrowserHandlers(
504    Profile* profile,
505    const std::vector<GURL>& file_list) {
506  FileBrowserHandlerList common_handlers;
507  for (std::vector<GURL>::const_iterator it = file_list.begin();
508       it != file_list.end(); ++it) {
509    FileBrowserHandlerList handlers =
510        FindFileBrowserHandlersForURL(profile, *it);
511    // If there is nothing to do for one file, the intersection of handlers
512    // for all files will be empty at the end, so no need to check further.
513    if (handlers.empty())
514      return FileBrowserHandlerList();
515
516    // For the very first file, just copy all the elements.
517    if (it == file_list.begin()) {
518      common_handlers = handlers;
519    } else {
520      // For all additional files, find intersection between the accumulated and
521      // file specific set.
522      FileBrowserHandlerList intersection;
523      std::set_intersection(common_handlers.begin(), common_handlers.end(),
524                            handlers.begin(), handlers.end(),
525                            std::back_inserter(intersection));
526      common_handlers = intersection;
527      if (common_handlers.empty())
528        return FileBrowserHandlerList();
529    }
530  }
531
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);
547    else
548      common_handlers.erase(watch_iter);
549  }
550
551  return common_handlers;
552}
553
554}  // namespace file_browser_handlers
555}  // namespace file_manager
556