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