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  base::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                             const std::string& action_id);
166
167  // Executes the task for each file. |done| will be run with the result.
168  void Execute(const std::vector<FileSystemURL>& file_urls,
169               const file_tasks::FileTaskFinishedCallback& done);
170
171 private:
172  // This object is responsible to delete itself.
173  virtual ~FileBrowserHandlerExecutor();
174
175  struct FileDefinition {
176    FileDefinition();
177    ~FileDefinition();
178
179    base::FilePath virtual_path;
180    base::FilePath absolute_path;
181    bool is_directory;
182  };
183
184  typedef std::vector<FileDefinition> FileDefinitionList;
185
186  // Checks legitimacy of file url and grants file RO access permissions from
187  // handler (target) extension and its renderer process.
188  static FileDefinitionList SetupFileAccessPermissions(
189      scoped_refptr<fileapi::FileSystemContext> file_system_context_handler,
190      const scoped_refptr<const Extension>& handler_extension,
191      const std::vector<FileSystemURL>& file_urls);
192
193  void ExecuteDoneOnUIThread(bool success);
194  void ExecuteFileActionsOnUIThread(const FileDefinitionList& file_list);
195  void SetupPermissionsAndDispatchEvent(const std::string& file_system_name,
196                                        const GURL& file_system_root,
197                                        const FileDefinitionList& file_list,
198                                        int handler_pid_in,
199                                        extensions::ExtensionHost* host);
200
201  // Registers file permissions from |handler_host_permissions_| with
202  // ChildProcessSecurityPolicy for process with id |handler_pid|.
203  void SetupHandlerHostFileAccessPermissions(
204      const FileDefinitionList& file_list,
205      const Extension* extension,
206      int handler_pid);
207
208  Profile* profile_;
209  scoped_refptr<const Extension> extension_;
210  const std::string action_id_;
211  file_tasks::FileTaskFinishedCallback done_;
212  base::WeakPtrFactory<FileBrowserHandlerExecutor> weak_ptr_factory_;
213
214  DISALLOW_COPY_AND_ASSIGN(FileBrowserHandlerExecutor);
215};
216
217FileBrowserHandlerExecutor::FileDefinition::FileDefinition()
218    : is_directory(false) {
219}
220
221FileBrowserHandlerExecutor::FileDefinition::~FileDefinition() {
222}
223
224// static
225FileBrowserHandlerExecutor::FileDefinitionList
226FileBrowserHandlerExecutor::SetupFileAccessPermissions(
227    scoped_refptr<fileapi::FileSystemContext> file_system_context_handler,
228    const scoped_refptr<const Extension>& handler_extension,
229    const std::vector<FileSystemURL>& file_urls) {
230  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
231  DCHECK(handler_extension.get());
232
233  fileapi::ExternalFileSystemBackend* backend =
234      file_system_context_handler->external_backend();
235
236  FileDefinitionList file_list;
237  for (size_t i = 0; i < file_urls.size(); ++i) {
238    const FileSystemURL& url = file_urls[i];
239
240    // Check if this file system entry exists first.
241    base::PlatformFileInfo file_info;
242
243    base::FilePath local_path = url.path();
244    base::FilePath virtual_path = url.virtual_path();
245
246    bool is_drive_file = url.type() == fileapi::kFileSystemTypeDrive;
247    DCHECK(!is_drive_file || drive::util::IsUnderDriveMountPoint(local_path));
248
249    // If the file is under drive mount point, there is no actual file to be
250    // found on the url.path().
251    if (!is_drive_file) {
252      if (!base::PathExists(local_path) ||
253          base::IsLink(local_path) ||
254          !base::GetFileInfo(local_path, &file_info)) {
255        continue;
256      }
257    }
258
259    // Grant access to this particular file to target extension. This will
260    // ensure that the target extension can access only this FS entry and
261    // prevent from traversing FS hierarchy upward.
262    backend->GrantFileAccessToExtension(
263        handler_extension->id(), virtual_path);
264
265    // Output values.
266    FileDefinition file;
267    file.virtual_path = virtual_path;
268    file.is_directory = file_info.is_directory;
269    file.absolute_path = local_path;
270    file_list.push_back(file);
271  }
272  return file_list;
273}
274
275FileBrowserHandlerExecutor::FileBrowserHandlerExecutor(
276    Profile* profile,
277    const Extension* extension,
278    const std::string& action_id)
279    : profile_(profile),
280      extension_(extension),
281      action_id_(action_id),
282      weak_ptr_factory_(this) {
283}
284
285FileBrowserHandlerExecutor::~FileBrowserHandlerExecutor() {}
286
287void FileBrowserHandlerExecutor::Execute(
288    const std::vector<FileSystemURL>& file_urls,
289    const file_tasks::FileTaskFinishedCallback& done) {
290  done_ = done;
291
292  // Get file system context for the extension to which onExecute event will be
293  // sent. The file access permissions will be granted to the extension in the
294  // file system context for the files in |file_urls|.
295  scoped_refptr<fileapi::FileSystemContext> file_system_context(
296      util::GetFileSystemContextForExtensionId(
297          profile_, extension_->id()));
298
299  BrowserThread::PostTaskAndReplyWithResult(
300      BrowserThread::FILE,
301      FROM_HERE,
302      base::Bind(&SetupFileAccessPermissions,
303                 file_system_context,
304                 extension_,
305                 file_urls),
306      base::Bind(&FileBrowserHandlerExecutor::ExecuteFileActionsOnUIThread,
307                 weak_ptr_factory_.GetWeakPtr()));
308}
309
310void FileBrowserHandlerExecutor::ExecuteDoneOnUIThread(bool success) {
311  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
312  if (!done_.is_null())
313    done_.Run(success);
314  delete this;
315}
316
317void FileBrowserHandlerExecutor::ExecuteFileActionsOnUIThread(
318    const FileDefinitionList& file_list) {
319  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
320
321  if (file_list.empty()) {
322    ExecuteDoneOnUIThread(false);
323    return;
324  }
325
326  int handler_pid = ExtractProcessFromExtensionId(profile_, extension_->id());
327  if (handler_pid <= 0 &&
328      !extensions::BackgroundInfo::HasLazyBackgroundPage(extension_.get())) {
329    ExecuteDoneOnUIThread(false);
330    return;
331  }
332
333  fileapi::FileSystemInfo info =
334      fileapi::GetFileSystemInfoForChromeOS(
335          Extension::GetBaseURLFromExtensionId(extension_->id()).GetOrigin());
336
337  if (handler_pid > 0) {
338    SetupPermissionsAndDispatchEvent(info.name, info.root_url,
339                                     file_list, handler_pid, NULL);
340  } else {
341    // We have to wake the handler background page before we proceed.
342    extensions::LazyBackgroundTaskQueue* queue =
343        extensions::ExtensionSystem::Get(profile_)->
344        lazy_background_task_queue();
345    if (!queue->ShouldEnqueueTask(profile_, extension_.get())) {
346      ExecuteDoneOnUIThread(false);
347      return;
348    }
349    queue->AddPendingTask(
350        profile_, extension_->id(),
351        base::Bind(
352            &FileBrowserHandlerExecutor::SetupPermissionsAndDispatchEvent,
353            weak_ptr_factory_.GetWeakPtr(),
354            info.name, info.root_url, file_list, handler_pid));
355  }
356}
357
358void FileBrowserHandlerExecutor::SetupPermissionsAndDispatchEvent(
359    const std::string& file_system_name,
360    const GURL& file_system_root,
361    const FileDefinitionList& file_list,
362    int handler_pid_in,
363    extensions::ExtensionHost* host) {
364  int handler_pid = host ? host->render_process_host()->GetID() :
365      handler_pid_in;
366
367  if (handler_pid <= 0) {
368    ExecuteDoneOnUIThread(false);
369    return;
370  }
371
372  extensions::EventRouter* event_router =
373      extensions::ExtensionSystem::Get(profile_)->event_router();
374  if (!event_router) {
375    ExecuteDoneOnUIThread(false);
376    return;
377  }
378
379  SetupHandlerHostFileAccessPermissions(
380      file_list, extension_.get(), handler_pid);
381
382  scoped_ptr<ListValue> event_args(new ListValue());
383  event_args->Append(new base::StringValue(action_id_));
384  DictionaryValue* details = new DictionaryValue();
385  event_args->Append(details);
386  // Get file definitions. These will be replaced with Entry instances by
387  // dispatchEvent() method from event_binding.js.
388  ListValue* file_entries = new ListValue();
389  details->Set("entries", file_entries);
390  for (FileDefinitionList::const_iterator iter = file_list.begin();
391       iter != file_list.end();
392       ++iter) {
393    DictionaryValue* file_def = new DictionaryValue();
394    file_entries->Append(file_def);
395    file_def->SetString("fileSystemName", file_system_name);
396    file_def->SetString("fileSystemRoot", file_system_root.spec());
397    base::FilePath root(FILE_PATH_LITERAL("/"));
398    base::FilePath full_path = root.Append(iter->virtual_path);
399    file_def->SetString("fileFullPath", full_path.value());
400    file_def->SetBoolean("fileIsDirectory", iter->is_directory);
401  }
402
403  scoped_ptr<extensions::Event> event(new extensions::Event(
404      "fileBrowserHandler.onExecute", event_args.Pass()));
405  event->restrict_to_browser_context = profile_;
406  event_router->DispatchEventToExtension(extension_->id(), event.Pass());
407
408  ExecuteDoneOnUIThread(true);
409}
410
411void FileBrowserHandlerExecutor::SetupHandlerHostFileAccessPermissions(
412    const FileDefinitionList& file_list,
413    const Extension* extension,
414    int handler_pid) {
415  const FileBrowserHandler* action = FindFileBrowserHandlerForActionId(
416      extension_, action_id_);
417  for (FileDefinitionList::const_iterator iter = file_list.begin();
418       iter != file_list.end();
419       ++iter) {
420    if (!action)
421      continue;
422    if (action->CanRead()) {
423      content::ChildProcessSecurityPolicy::GetInstance()->GrantReadFile(
424          handler_pid, iter->absolute_path);
425    }
426    if (action->CanWrite()) {
427      content::ChildProcessSecurityPolicy::GetInstance()->
428          GrantCreateReadWriteFile(handler_pid, iter->absolute_path);
429    }
430  }
431}
432
433// Returns true if |extension_id| and |action_id| indicate that the file
434// currently being handled should be opened with the browser. This function
435// is used to handle certain action IDs of the file manager.
436bool ShouldBeOpenedWithBrowser(const std::string& extension_id,
437                               const std::string& action_id) {
438
439  return (extension_id == kFileManagerAppId &&
440          (action_id == "view-pdf" ||
441           action_id == "view-swf" ||
442           action_id == "view-in-browser" ||
443           action_id == "open-hosted-generic" ||
444           action_id == "open-hosted-gdoc" ||
445           action_id == "open-hosted-gsheet" ||
446           action_id == "open-hosted-gslides"));
447}
448
449// Opens the files specified by |file_urls| with the browser for |profile|.
450// Returns true on success. It's a failure if no files are opened.
451bool OpenFilesWithBrowser(Profile* profile,
452                          const std::vector<FileSystemURL>& file_urls) {
453  int num_opened = 0;
454  for (size_t i = 0; i < file_urls.size(); ++i) {
455    const FileSystemURL& file_url = file_urls[i];
456    if (chromeos::FileSystemBackend::CanHandleURL(file_url)) {
457      const base::FilePath& file_path = file_url.path();
458      num_opened += util::OpenFileWithBrowser(profile, file_path);
459    }
460  }
461  return num_opened > 0;
462}
463
464}  // namespace
465
466bool ExecuteFileBrowserHandler(
467    Profile* profile,
468    const Extension* extension,
469    const std::string& action_id,
470    const std::vector<FileSystemURL>& file_urls,
471    const file_tasks::FileTaskFinishedCallback& done) {
472  // Forbid calling undeclared handlers.
473  if (!FindFileBrowserHandlerForActionId(extension, action_id))
474    return false;
475
476  // Some action IDs of the file manager's file browser handlers require the
477  // files to be directly opened with the browser.
478  if (ShouldBeOpenedWithBrowser(extension->id(), action_id)) {
479    return OpenFilesWithBrowser(profile, file_urls);
480  }
481
482  // The executor object will be self deleted on completion.
483  (new FileBrowserHandlerExecutor(
484      profile, extension, action_id))->Execute(file_urls, done);
485  return true;
486}
487
488bool IsFallbackFileBrowserHandler(const file_tasks::TaskDescriptor& task) {
489  return (task.task_type == file_tasks::TASK_TYPE_FILE_BROWSER_HANDLER &&
490          (task.app_id == kFileManagerAppId ||
491           task.app_id == extension_misc::kQuickOfficeComponentExtensionId ||
492           task.app_id == extension_misc::kQuickOfficeDevExtensionId ||
493           task.app_id == extension_misc::kQuickOfficeExtensionId));
494}
495
496FileBrowserHandlerList FindFileBrowserHandlers(
497    Profile* profile,
498    const std::vector<GURL>& file_list) {
499  FileBrowserHandlerList common_handlers;
500  for (std::vector<GURL>::const_iterator it = file_list.begin();
501       it != file_list.end(); ++it) {
502    FileBrowserHandlerList handlers =
503        FindFileBrowserHandlersForURL(profile, *it);
504    // If there is nothing to do for one file, the intersection of handlers
505    // for all files will be empty at the end, so no need to check further.
506    if (handlers.empty())
507      return FileBrowserHandlerList();
508
509    // For the very first file, just copy all the elements.
510    if (it == file_list.begin()) {
511      common_handlers = handlers;
512    } else {
513      // For all additional files, find intersection between the accumulated and
514      // file specific set.
515      FileBrowserHandlerList intersection;
516      std::set<const FileBrowserHandler*> common_handler_set(
517          common_handlers.begin(), common_handlers.end());
518
519      for (FileBrowserHandlerList::const_iterator itr = handlers.begin();
520           itr != handlers.end(); ++itr) {
521        if (ContainsKey(common_handler_set, *itr))
522          intersection.push_back(*itr);
523      }
524
525      std::swap(common_handlers, intersection);
526      if (common_handlers.empty())
527        return FileBrowserHandlerList();
528    }
529  }
530
531  // "watch" and "gallery" are defined in the file manager's manifest.json.
532  FileBrowserHandlerList::iterator watch_iter =
533      FindFileBrowserHandlerForExtensionIdAndActionId(
534          &common_handlers, kFileManagerAppId, "watch");
535  FileBrowserHandlerList::iterator gallery_iter =
536      FindFileBrowserHandlerForExtensionIdAndActionId(
537          &common_handlers, kFileManagerAppId, "gallery");
538  if (watch_iter != common_handlers.end() &&
539      gallery_iter != common_handlers.end()) {
540    // Both "watch" and "gallery" actions are applicable which means that the
541    // selection is all videos. Showing them both is confusing, so we only keep
542    // the one that makes more sense ("watch" for single selection, "gallery"
543    // for multiple selection).
544    if (file_list.size() == 1)
545      common_handlers.erase(gallery_iter);
546    else
547      common_handlers.erase(watch_iter);
548  }
549
550  return common_handlers;
551}
552
553}  // namespace file_browser_handlers
554}  // namespace file_manager
555