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