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/extensions/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/extensions/file_manager/file_manager_util.h"
13#include "chrome/browser/chromeos/extensions/file_manager/fileapi_util.h"
14#include "chrome/browser/chromeos/fileapi/file_system_backend.h"
15#include "chrome/browser/extensions/event_router.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/lazy_background_task_queue.h"
20#include "chrome/browser/profiles/profile.h"
21#include "chrome/common/extensions/api/file_browser_handlers/file_browser_handler.h"
22#include "chrome/common/extensions/background_info.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 "net/base/escape.h"
29#include "webkit/browser/fileapi/file_system_context.h"
30#include "webkit/browser/fileapi/file_system_url.h"
31
32using content::BrowserThread;
33using content::ChildProcessSecurityPolicy;
34using content::SiteInstance;
35using content::WebContents;
36using extensions::Extension;
37using fileapi::FileSystemURL;
38
39namespace file_manager {
40namespace file_browser_handlers {
41namespace {
42
43// Returns process id of the process the extension is running in.
44int ExtractProcessFromExtensionId(Profile* profile,
45                                  const std::string& extension_id) {
46  GURL extension_url =
47      Extension::GetBaseURLFromExtensionId(extension_id);
48  ExtensionProcessManager* manager =
49    extensions::ExtensionSystem::Get(profile)->process_manager();
50
51  SiteInstance* site_instance = manager->GetSiteInstanceForURL(extension_url);
52  if (!site_instance || !site_instance->HasProcess())
53    return -1;
54  content::RenderProcessHost* process = site_instance->GetProcess();
55
56  return process->GetID();
57}
58
59// Finds a file browser handler that matches |action_id|. Returns NULL if not
60// found.
61const FileBrowserHandler* FindFileBrowserHandlerForActionId(
62    const Extension* extension,
63    const std::string& action_id) {
64  FileBrowserHandler::List* handler_list =
65      FileBrowserHandler::GetHandlers(extension);
66  for (FileBrowserHandler::List::const_iterator handler_iter =
67           handler_list->begin();
68       handler_iter != handler_list->end();
69       ++handler_iter) {
70    if (handler_iter->get()->id() == action_id)
71      return handler_iter->get();
72  }
73  return NULL;
74}
75
76std::string EscapedUtf8ToLower(const std::string& str) {
77  string16 utf16 = UTF8ToUTF16(
78      net::UnescapeURLComponent(str, net::UnescapeRule::NORMAL));
79  return net::EscapeUrlEncodedData(
80      UTF16ToUTF8(base::i18n::ToLower(utf16)),
81      false /* do not replace space with plus */);
82}
83
84// Finds file browser handlers that can handle the |selected_file_url|.
85FileBrowserHandlerList FindFileBrowserHandlersForURL(
86    Profile* profile,
87    const GURL& selected_file_url) {
88  ExtensionService* service =
89      extensions::ExtensionSystem::Get(profile)->extension_service();
90  // In unit-tests, we may not have an ExtensionService.
91  if (!service)
92    return FileBrowserHandlerList();
93
94  // We need case-insensitive matching, and pattern in the handler is already
95  // in lower case.
96  const GURL lowercase_url(EscapedUtf8ToLower(selected_file_url.spec()));
97
98  FileBrowserHandlerList results;
99  for (ExtensionSet::const_iterator iter = service->extensions()->begin();
100       iter != service->extensions()->end();
101       ++iter) {
102    const Extension* extension = iter->get();
103    if (profile->IsOffTheRecord() &&
104        !service->IsIncognitoEnabled(extension->id()))
105      continue;
106
107    FileBrowserHandler::List* handler_list =
108        FileBrowserHandler::GetHandlers(extension);
109    if (!handler_list)
110      continue;
111    for (FileBrowserHandler::List::const_iterator handler_iter =
112             handler_list->begin();
113         handler_iter != handler_list->end();
114         ++handler_iter) {
115      const FileBrowserHandler* handler = handler_iter->get();
116      if (!handler->MatchesURL(lowercase_url))
117        continue;
118
119      results.push_back(handler_iter->get());
120    }
121  }
122  return results;
123}
124
125// Finds a file browser handler that matches |extension_id| and |action_id|
126// from |handler_list|.  Returns a mutable iterator to the handler if
127// found. Returns handler_list->end() if not found.
128FileBrowserHandlerList::iterator
129FindFileBrowserHandlerForExtensionIdAndActionId(
130    FileBrowserHandlerList* handler_list,
131    const std::string& extension_id,
132    const std::string& action_id) {
133  DCHECK(handler_list);
134
135  FileBrowserHandlerList::iterator iter = handler_list->begin();
136  while (iter != handler_list->end() &&
137         !((*iter)->extension_id() == extension_id &&
138           (*iter)->id() == action_id)) {
139    ++iter;
140  }
141  return iter;
142}
143
144// This class is used to execute a file browser handler task. Here's how this
145// works:
146//
147// 1) Open the "external" file system
148// 2) Set up permissions for the target files on the external file system.
149// 3) Raise onExecute event with the action ID and entries of the target
150//    files. The event will launch the file browser handler if not active.
151// 4) In the file browser handler, onExecute event is handled and executes the
152//    task in JavaScript.
153//
154// That said, the class itself does not execute a task. The task will be
155// executed in JavaScript.
156class FileBrowserHandlerExecutor {
157 public:
158  FileBrowserHandlerExecutor(Profile* profile,
159                             const Extension* extension,
160                             int32 tab_id,
161                             const std::string& action_id);
162
163  // Executes the task for each file. |done| will be run with the result.
164  void Execute(const std::vector<FileSystemURL>& file_urls,
165               const file_tasks::FileTaskFinishedCallback& done);
166
167 private:
168  // This object is responsible to delete itself.
169  virtual ~FileBrowserHandlerExecutor();
170
171  struct FileDefinition {
172    FileDefinition();
173    ~FileDefinition();
174
175    base::FilePath virtual_path;
176    base::FilePath absolute_path;
177    bool is_directory;
178  };
179
180  typedef std::vector<FileDefinition> FileDefinitionList;
181
182  // Checks legitimacy of file url and grants file RO access permissions from
183  // handler (target) extension and its renderer process.
184  static FileDefinitionList SetupFileAccessPermissions(
185      scoped_refptr<fileapi::FileSystemContext> file_system_context_handler,
186      const scoped_refptr<const Extension>& handler_extension,
187      const std::vector<FileSystemURL>& file_urls);
188
189  void DidOpenFileSystem(const std::vector<FileSystemURL>& file_urls,
190                         base::PlatformFileError result,
191                         const std::string& file_system_name,
192                         const GURL& file_system_root);
193
194  void ExecuteDoneOnUIThread(bool success);
195  void ExecuteFileActionsOnUIThread(const std::string& file_system_name,
196                                    const GURL& file_system_root,
197                                    const FileDefinitionList& file_list);
198  void SetupPermissionsAndDispatchEvent(const std::string& file_system_name,
199                                        const GURL& file_system_root,
200                                        const FileDefinitionList& file_list,
201                                        int handler_pid_in,
202                                        extensions::ExtensionHost* host);
203
204  // Registers file permissions from |handler_host_permissions_| with
205  // ChildProcessSecurityPolicy for process with id |handler_pid|.
206  void SetupHandlerHostFileAccessPermissions(
207      const FileDefinitionList& file_list,
208      const Extension* extension,
209      int handler_pid);
210
211  Profile* profile_;
212  scoped_refptr<const Extension> extension_;
213  int32 tab_id_;
214  const std::string action_id_;
215  file_tasks::FileTaskFinishedCallback done_;
216  base::WeakPtrFactory<FileBrowserHandlerExecutor> weak_ptr_factory_;
217
218  DISALLOW_COPY_AND_ASSIGN(FileBrowserHandlerExecutor);
219};
220
221FileBrowserHandlerExecutor::FileDefinition::FileDefinition()
222    : is_directory(false) {
223}
224
225FileBrowserHandlerExecutor::FileDefinition::~FileDefinition() {
226}
227
228// static
229FileBrowserHandlerExecutor::FileDefinitionList
230FileBrowserHandlerExecutor::SetupFileAccessPermissions(
231    scoped_refptr<fileapi::FileSystemContext> file_system_context_handler,
232    const scoped_refptr<const Extension>& handler_extension,
233    const std::vector<FileSystemURL>& file_urls) {
234  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
235  DCHECK(handler_extension.get());
236
237  fileapi::ExternalFileSystemBackend* backend =
238      file_system_context_handler->external_backend();
239
240  FileDefinitionList file_list;
241  for (size_t i = 0; i < file_urls.size(); ++i) {
242    const FileSystemURL& url = file_urls[i];
243
244    // Check if this file system entry exists first.
245    base::PlatformFileInfo file_info;
246
247    base::FilePath local_path = url.path();
248    base::FilePath virtual_path = url.virtual_path();
249
250    bool is_drive_file = url.type() == fileapi::kFileSystemTypeDrive;
251    DCHECK(!is_drive_file || drive::util::IsUnderDriveMountPoint(local_path));
252
253    // If the file is under drive mount point, there is no actual file to be
254    // found on the url.path().
255    if (!is_drive_file) {
256      if (!base::PathExists(local_path) ||
257          file_util::IsLink(local_path) ||
258          !file_util::GetFileInfo(local_path, &file_info)) {
259        continue;
260      }
261    }
262
263    // Grant access to this particular file to target extension. This will
264    // ensure that the target extension can access only this FS entry and
265    // prevent from traversing FS hierarchy upward.
266    backend->GrantFileAccessToExtension(
267        handler_extension->id(), virtual_path);
268
269    // Output values.
270    FileDefinition file;
271    file.virtual_path = virtual_path;
272    file.is_directory = file_info.is_directory;
273    file.absolute_path = local_path;
274    file_list.push_back(file);
275  }
276  return file_list;
277}
278
279FileBrowserHandlerExecutor::FileBrowserHandlerExecutor(
280    Profile* profile,
281    const Extension* extension,
282    int32 tab_id,
283    const std::string& action_id)
284    : profile_(profile),
285      extension_(extension),
286      tab_id_(tab_id),
287      action_id_(action_id),
288      weak_ptr_factory_(this) {
289}
290
291FileBrowserHandlerExecutor::~FileBrowserHandlerExecutor() {}
292
293void FileBrowserHandlerExecutor::Execute(
294    const std::vector<FileSystemURL>& file_urls,
295    const file_tasks::FileTaskFinishedCallback& done) {
296  done_ = done;
297
298  // Get file system context for the extension to which onExecute event will be
299  // sent. The file access permissions will be granted to the extension in the
300  // file system context for the files in |file_urls|.
301  fileapi_util::GetFileSystemContextForExtensionId(
302      profile_, extension_->id())->OpenFileSystem(
303          Extension::GetBaseURLFromExtensionId(extension_->id()).GetOrigin(),
304          fileapi::kFileSystemTypeExternal,
305          fileapi::OPEN_FILE_SYSTEM_FAIL_IF_NONEXISTENT,
306          base::Bind(&FileBrowserHandlerExecutor::DidOpenFileSystem,
307                     weak_ptr_factory_.GetWeakPtr(),
308                     file_urls));
309}
310
311void FileBrowserHandlerExecutor::DidOpenFileSystem(
312    const std::vector<FileSystemURL>& file_urls,
313    base::PlatformFileError result,
314    const std::string& file_system_name,
315    const GURL& file_system_root) {
316  if (result != base::PLATFORM_FILE_OK) {
317    ExecuteDoneOnUIThread(false);
318    return;
319  }
320
321  scoped_refptr<fileapi::FileSystemContext> file_system_context(
322      fileapi_util::GetFileSystemContextForExtensionId(
323          profile_, extension_->id()));
324  BrowserThread::PostTaskAndReplyWithResult(
325      BrowserThread::FILE,
326      FROM_HERE,
327      base::Bind(&SetupFileAccessPermissions,
328                 file_system_context,
329                 extension_,
330                 file_urls),
331      base::Bind(&FileBrowserHandlerExecutor::ExecuteFileActionsOnUIThread,
332                 weak_ptr_factory_.GetWeakPtr(),
333                 file_system_name,
334                 file_system_root));
335}
336
337void FileBrowserHandlerExecutor::ExecuteDoneOnUIThread(bool success) {
338  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
339  if (!done_.is_null())
340    done_.Run(success);
341  delete this;
342}
343
344void FileBrowserHandlerExecutor::ExecuteFileActionsOnUIThread(
345    const std::string& file_system_name,
346    const GURL& file_system_root,
347    const FileDefinitionList& file_list) {
348  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
349
350  if (file_list.empty()) {
351    ExecuteDoneOnUIThread(false);
352    return;
353  }
354
355  int handler_pid = ExtractProcessFromExtensionId(profile_, extension_->id());
356  if (handler_pid <= 0 &&
357      !extensions::BackgroundInfo::HasLazyBackgroundPage(extension_.get())) {
358    ExecuteDoneOnUIThread(false);
359    return;
360  }
361
362  if (handler_pid > 0) {
363    SetupPermissionsAndDispatchEvent(file_system_name, file_system_root,
364                                     file_list, handler_pid, NULL);
365  } else {
366    // We have to wake the handler background page before we proceed.
367    extensions::LazyBackgroundTaskQueue* queue =
368        extensions::ExtensionSystem::Get(profile_)->
369        lazy_background_task_queue();
370    if (!queue->ShouldEnqueueTask(profile_, extension_.get())) {
371      ExecuteDoneOnUIThread(false);
372      return;
373    }
374    queue->AddPendingTask(
375        profile_, extension_->id(),
376        base::Bind(
377            &FileBrowserHandlerExecutor::SetupPermissionsAndDispatchEvent,
378            weak_ptr_factory_.GetWeakPtr(),
379            file_system_name,
380            file_system_root,
381            file_list,
382            handler_pid));
383  }
384}
385
386void FileBrowserHandlerExecutor::SetupPermissionsAndDispatchEvent(
387    const std::string& file_system_name,
388    const GURL& file_system_root,
389    const FileDefinitionList& file_list,
390    int handler_pid_in,
391    extensions::ExtensionHost* host) {
392  int handler_pid = host ? host->render_process_host()->GetID() :
393      handler_pid_in;
394
395  if (handler_pid <= 0) {
396    ExecuteDoneOnUIThread(false);
397    return;
398  }
399
400  extensions::EventRouter* event_router =
401      extensions::ExtensionSystem::Get(profile_)->event_router();
402  if (!event_router) {
403    ExecuteDoneOnUIThread(false);
404    return;
405  }
406
407  SetupHandlerHostFileAccessPermissions(
408      file_list, extension_.get(), handler_pid);
409
410  scoped_ptr<ListValue> event_args(new ListValue());
411  event_args->Append(new base::StringValue(action_id_));
412  DictionaryValue* details = new DictionaryValue();
413  event_args->Append(details);
414  // Get file definitions. These will be replaced with Entry instances by
415  // dispatchEvent() method from event_binding.js.
416  ListValue* file_entries = new ListValue();
417  details->Set("entries", file_entries);
418  for (FileDefinitionList::const_iterator iter = file_list.begin();
419       iter != file_list.end();
420       ++iter) {
421    DictionaryValue* file_def = new DictionaryValue();
422    file_entries->Append(file_def);
423    file_def->SetString("fileSystemName", file_system_name);
424    file_def->SetString("fileSystemRoot", file_system_root.spec());
425    base::FilePath root(FILE_PATH_LITERAL("/"));
426    base::FilePath full_path = root.Append(iter->virtual_path);
427    file_def->SetString("fileFullPath", full_path.value());
428    file_def->SetBoolean("fileIsDirectory", iter->is_directory);
429  }
430
431  details->SetInteger("tab_id", tab_id_);
432
433  scoped_ptr<extensions::Event> event(new extensions::Event(
434      "fileBrowserHandler.onExecute", event_args.Pass()));
435  event->restrict_to_profile = profile_;
436  event_router->DispatchEventToExtension(extension_->id(), event.Pass());
437
438  ExecuteDoneOnUIThread(true);
439}
440
441void FileBrowserHandlerExecutor::SetupHandlerHostFileAccessPermissions(
442    const FileDefinitionList& file_list,
443    const Extension* extension,
444    int handler_pid) {
445  const FileBrowserHandler* action = FindFileBrowserHandlerForActionId(
446      extension_, action_id_);
447  for (FileDefinitionList::const_iterator iter = file_list.begin();
448       iter != file_list.end();
449       ++iter) {
450    if (!action)
451      continue;
452    if (action->CanRead()) {
453      content::ChildProcessSecurityPolicy::GetInstance()->GrantReadFile(
454          handler_pid, iter->absolute_path);
455    }
456    if (action->CanWrite()) {
457      content::ChildProcessSecurityPolicy::GetInstance()->
458          GrantCreateReadWriteFile(handler_pid, iter->absolute_path);
459    }
460  }
461}
462
463}  // namespace
464
465bool ExecuteFileBrowserHandler(
466    Profile* profile,
467    const Extension* extension,
468    int32 tab_id,
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  // The executor object will be self deleted on completion.
477  (new FileBrowserHandlerExecutor(
478      profile, extension, tab_id, action_id))->Execute(file_urls, done);
479  return true;
480}
481
482bool IsFallbackFileBrowserHandler(const FileBrowserHandler* handler) {
483  const std::string& extension_id = handler->extension_id();
484  return (extension_id == kFileBrowserDomain ||
485          extension_id == extension_misc::kQuickOfficeComponentExtensionId ||
486          extension_id == extension_misc::kQuickOfficeDevExtensionId ||
487          extension_id == extension_misc::kQuickOfficeExtensionId);
488}
489
490FileBrowserHandlerList FindDefaultFileBrowserHandlers(
491    Profile* profile,
492    const std::vector<base::FilePath>& file_list,
493    const FileBrowserHandlerList& common_handlers) {
494  FileBrowserHandlerList default_handlers;
495
496  std::set<std::string> default_ids;
497  for (std::vector<base::FilePath>::const_iterator it = file_list.begin();
498       it != file_list.end(); ++it) {
499    std::string task_id = file_tasks::GetDefaultTaskIdFromPrefs(
500        profile, "", it->Extension());
501    if (!task_id.empty())
502      default_ids.insert(task_id);
503  }
504
505  const FileBrowserHandler* fallback_handler = NULL;
506  // Convert the default task IDs collected above to one of the handler pointers
507  // from common_handlers.
508  for (size_t i = 0; i < common_handlers.size(); ++i) {
509    const FileBrowserHandler* handler = common_handlers[i];
510    std::string task_id = file_tasks::MakeTaskID(
511        handler->extension_id(),
512        file_tasks::kFileBrowserHandlerTaskType,
513        handler->id());
514    std::set<std::string>::iterator default_iter = default_ids.find(task_id);
515    if (default_iter != default_ids.end()) {
516      default_handlers.push_back(handler);
517      continue;
518    }
519
520    // Remember the first fallback handler.
521    if (!fallback_handler && IsFallbackFileBrowserHandler(handler))
522      fallback_handler = handler;
523  }
524
525  // If there are no default handlers found, use fallback as default.
526  if (fallback_handler && default_handlers.empty())
527    default_handlers.push_back(fallback_handler);
528
529  return default_handlers;
530}
531
532FileBrowserHandlerList FindCommonFileBrowserHandlers(
533    Profile* profile,
534    const std::vector<GURL>& file_list) {
535  FileBrowserHandlerList common_handlers;
536  for (std::vector<GURL>::const_iterator it = file_list.begin();
537       it != file_list.end(); ++it) {
538    FileBrowserHandlerList handlers =
539        FindFileBrowserHandlersForURL(profile, *it);
540    // If there is nothing to do for one file, the intersection of handlers
541    // for all files will be empty at the end, so no need to check further.
542    if (handlers.empty())
543      return FileBrowserHandlerList();
544
545    // For the very first file, just copy all the elements.
546    if (it == file_list.begin()) {
547      common_handlers = handlers;
548    } else {
549      // For all additional files, find intersection between the accumulated and
550      // file specific set.
551      FileBrowserHandlerList intersection;
552      std::set_intersection(common_handlers.begin(), common_handlers.end(),
553                            handlers.begin(), handlers.end(),
554                            std::back_inserter(intersection));
555      common_handlers = intersection;
556      if (common_handlers.empty())
557        return FileBrowserHandlerList();
558    }
559  }
560
561  FileBrowserHandlerList::iterator watch_iter =
562      FindFileBrowserHandlerForExtensionIdAndActionId(
563          &common_handlers, kFileBrowserDomain, kFileBrowserWatchTaskId);
564  FileBrowserHandlerList::iterator gallery_iter =
565      FindFileBrowserHandlerForExtensionIdAndActionId(
566          &common_handlers, kFileBrowserDomain, kFileBrowserGalleryTaskId);
567  if (watch_iter != common_handlers.end() &&
568      gallery_iter != common_handlers.end()) {
569    // Both "watch" and "gallery" actions are applicable which means that the
570    // selection is all videos. Showing them both is confusing, so we only keep
571    // the one that makes more sense ("watch" for single selection, "gallery"
572    // for multiple selection).
573    if (file_list.size() == 1)
574      common_handlers.erase(gallery_iter);
575    else
576      common_handlers.erase(watch_iter);
577  }
578
579  return common_handlers;
580}
581
582const FileBrowserHandler* FindFileBrowserHandlerForURLAndPath(
583    Profile* profile,
584    const GURL& url,
585    const base::FilePath& file_path) {
586  std::vector<GURL> file_urls;
587  file_urls.push_back(url);
588
589  FileBrowserHandlerList common_handlers =
590      FindCommonFileBrowserHandlers(profile, file_urls);
591  if (common_handlers.empty())
592    return NULL;
593
594  std::vector<base::FilePath> file_paths;
595  file_paths.push_back(file_path);
596
597  FileBrowserHandlerList default_handlers =
598      FindDefaultFileBrowserHandlers(profile, file_paths, common_handlers);
599
600  // If there's none, or more than one, then we don't have a canonical default.
601  if (!default_handlers.empty()) {
602    // There should not be multiple default handlers for a single URL.
603    DCHECK_EQ(1u, default_handlers.size());
604
605    return *default_handlers.begin();
606  }
607
608  // If there are no default handlers, use first handler in the list (file
609  // manager does the same in this situation).  TODO(tbarzic): This is not so
610  // optimal behaviour.
611  return *common_handlers.begin();
612}
613
614}  // namespace file_browser_handlers
615}  // namespace file_manager
616