file_browser_handlers.cc revision f8ee788a64d60abd8f2d742a5fdedde054ecd910
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 "chrome/common/extensions/extension_constants.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/extension_host.h"
30#include "extensions/browser/extension_system.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
119    FileBrowserHandler::List* handler_list =
120        FileBrowserHandler::GetHandlers(extension);
121    if (!handler_list)
122      continue;
123    for (FileBrowserHandler::List::const_iterator handler_iter =
124             handler_list->begin();
125         handler_iter != handler_list->end();
126         ++handler_iter) {
127      const FileBrowserHandler* handler = handler_iter->get();
128      if (!handler->MatchesURL(lowercase_url))
129        continue;
130
131      results.push_back(handler_iter->get());
132    }
133  }
134  return results;
135}
136
137// This class is used to execute a file browser handler task. Here's how this
138// works:
139//
140// 1) Open the "external" file system
141// 2) Set up permissions for the target files on the external file system.
142// 3) Raise onExecute event with the action ID and entries of the target
143//    files. The event will launch the file browser handler if not active.
144// 4) In the file browser handler, onExecute event is handled and executes the
145//    task in JavaScript.
146//
147// That said, the class itself does not execute a task. The task will be
148// executed in JavaScript.
149class FileBrowserHandlerExecutor {
150 public:
151  FileBrowserHandlerExecutor(Profile* profile,
152                             const Extension* extension,
153                             const std::string& action_id);
154
155  // Executes the task for each file. |done| will be run with the result.
156  void Execute(const std::vector<FileSystemURL>& file_urls,
157               const file_tasks::FileTaskFinishedCallback& done);
158
159 private:
160  // This object is responsible to delete itself.
161  virtual ~FileBrowserHandlerExecutor();
162
163  // Checks legitimacy of file url and grants file RO access permissions from
164  // handler (target) extension and its renderer process.
165  static scoped_ptr<FileDefinitionList> SetupFileAccessPermissions(
166      scoped_refptr<fileapi::FileSystemContext> file_system_context_handler,
167      const scoped_refptr<const Extension>& handler_extension,
168      const std::vector<FileSystemURL>& file_urls);
169
170  void ExecuteDoneOnUIThread(bool success);
171  void ExecuteAfterSetupFileAccess(scoped_ptr<FileDefinitionList> file_list);
172  void ExecuteFileActionsOnUIThread(
173      scoped_ptr<FileDefinitionList> file_definition_list,
174      scoped_ptr<EntryDefinitionList> entry_definition_list);
175  void SetupPermissionsAndDispatchEvent(
176      scoped_ptr<FileDefinitionList> file_definition_list,
177      scoped_ptr<EntryDefinitionList> entry_definition_list,
178      int handler_pid_in,
179      extensions::ExtensionHost* host);
180
181  // Registers file permissions from |handler_host_permissions_| with
182  // ChildProcessSecurityPolicy for process with id |handler_pid|.
183  void SetupHandlerHostFileAccessPermissions(
184      FileDefinitionList* file_definition_list,
185      const Extension* extension,
186      int handler_pid);
187
188  Profile* profile_;
189  scoped_refptr<const Extension> extension_;
190  const std::string action_id_;
191  file_tasks::FileTaskFinishedCallback done_;
192  base::WeakPtrFactory<FileBrowserHandlerExecutor> weak_ptr_factory_;
193
194  DISALLOW_COPY_AND_ASSIGN(FileBrowserHandlerExecutor);
195};
196
197// static
198scoped_ptr<FileDefinitionList>
199FileBrowserHandlerExecutor::SetupFileAccessPermissions(
200    scoped_refptr<fileapi::FileSystemContext> file_system_context_handler,
201    const scoped_refptr<const Extension>& handler_extension,
202    const std::vector<FileSystemURL>& file_urls) {
203  DCHECK_CURRENTLY_ON(BrowserThread::FILE);
204  DCHECK(handler_extension.get());
205
206  fileapi::ExternalFileSystemBackend* backend =
207      file_system_context_handler->external_backend();
208
209  scoped_ptr<FileDefinitionList> file_definition_list(new FileDefinitionList);
210  for (size_t i = 0; i < file_urls.size(); ++i) {
211    const FileSystemURL& url = file_urls[i];
212
213    // Check if this file system entry exists first.
214    base::File::Info file_info;
215
216    base::FilePath local_path = url.path();
217    base::FilePath virtual_path = url.virtual_path();
218
219    const bool is_drive_file = url.type() == fileapi::kFileSystemTypeDrive;
220    DCHECK(!is_drive_file || drive::util::IsUnderDriveMountPoint(local_path));
221
222    const bool is_native_file =
223        url.type() == fileapi::kFileSystemTypeNativeLocal ||
224        url.type() == fileapi::kFileSystemTypeRestrictedNativeLocal;
225
226    // If the file is from a physical volume, actual file must be found.
227    if (is_native_file) {
228      if (!base::PathExists(local_path) ||
229          base::IsLink(local_path) ||
230          !base::GetFileInfo(local_path, &file_info)) {
231        continue;
232      }
233    }
234
235    // Grant access to this particular file to target extension. This will
236    // ensure that the target extension can access only this FS entry and
237    // prevent from traversing FS hierarchy upward.
238    backend->GrantFileAccessToExtension(handler_extension->id(), virtual_path);
239
240    // Output values.
241    FileDefinition file_definition;
242    file_definition.virtual_path = virtual_path;
243    file_definition.is_directory = file_info.is_directory;
244    file_definition.absolute_path = local_path;
245    file_definition_list->push_back(file_definition);
246  }
247
248  return file_definition_list.Pass();
249}
250
251FileBrowserHandlerExecutor::FileBrowserHandlerExecutor(
252    Profile* profile,
253    const Extension* extension,
254    const std::string& action_id)
255    : profile_(profile),
256      extension_(extension),
257      action_id_(action_id),
258      weak_ptr_factory_(this) {
259}
260
261FileBrowserHandlerExecutor::~FileBrowserHandlerExecutor() {}
262
263void FileBrowserHandlerExecutor::Execute(
264    const std::vector<FileSystemURL>& file_urls,
265    const file_tasks::FileTaskFinishedCallback& done) {
266  done_ = done;
267
268  // Get file system context for the extension to which onExecute event will be
269  // sent. The file access permissions will be granted to the extension in the
270  // file system context for the files in |file_urls|.
271  scoped_refptr<fileapi::FileSystemContext> file_system_context(
272      util::GetFileSystemContextForExtensionId(
273          profile_, extension_->id()));
274
275  BrowserThread::PostTaskAndReplyWithResult(
276      BrowserThread::FILE,
277      FROM_HERE,
278      base::Bind(&SetupFileAccessPermissions,
279                 file_system_context,
280                 extension_,
281                 file_urls),
282      base::Bind(&FileBrowserHandlerExecutor::ExecuteAfterSetupFileAccess,
283                 weak_ptr_factory_.GetWeakPtr()));
284}
285
286void FileBrowserHandlerExecutor::ExecuteAfterSetupFileAccess(
287    scoped_ptr<FileDefinitionList> file_definition_list) {
288  // Outlives the conversion process, since bound to the callback.
289  const FileDefinitionList& file_definition_list_ref =
290      *file_definition_list.get();
291  file_manager::util::ConvertFileDefinitionListToEntryDefinitionList(
292      profile_,
293      extension_->id(),
294      file_definition_list_ref,
295      base::Bind(&FileBrowserHandlerExecutor::ExecuteFileActionsOnUIThread,
296                 weak_ptr_factory_.GetWeakPtr(),
297                 base::Passed(&file_definition_list)));
298}
299
300void FileBrowserHandlerExecutor::ExecuteDoneOnUIThread(bool success) {
301  DCHECK_CURRENTLY_ON(BrowserThread::UI);
302  if (!done_.is_null())
303    done_.Run(
304        success
305            ? extensions::api::file_browser_private::TASK_RESULT_MESSAGE_SENT
306            : extensions::api::file_browser_private::TASK_RESULT_FAILED);
307  delete this;
308}
309
310void FileBrowserHandlerExecutor::ExecuteFileActionsOnUIThread(
311    scoped_ptr<FileDefinitionList> file_definition_list,
312    scoped_ptr<EntryDefinitionList> entry_definition_list) {
313  DCHECK_CURRENTLY_ON(BrowserThread::UI);
314
315  if (file_definition_list->empty() || entry_definition_list->empty()) {
316    ExecuteDoneOnUIThread(false);
317    return;
318  }
319
320  int handler_pid = ExtractProcessFromExtensionId(profile_, extension_->id());
321  if (handler_pid <= 0 &&
322      !extensions::BackgroundInfo::HasLazyBackgroundPage(extension_.get())) {
323    ExecuteDoneOnUIThread(false);
324    return;
325  }
326
327  if (handler_pid > 0) {
328    SetupPermissionsAndDispatchEvent(file_definition_list.Pass(),
329                                     entry_definition_list.Pass(),
330                                     handler_pid,
331                                     NULL);
332  } else {
333    // We have to wake the handler background page before we proceed.
334    extensions::LazyBackgroundTaskQueue* queue =
335        extensions::ExtensionSystem::Get(profile_)->
336        lazy_background_task_queue();
337    if (!queue->ShouldEnqueueTask(profile_, extension_.get())) {
338      ExecuteDoneOnUIThread(false);
339      return;
340    }
341    queue->AddPendingTask(
342        profile_,
343        extension_->id(),
344        base::Bind(
345            &FileBrowserHandlerExecutor::SetupPermissionsAndDispatchEvent,
346            weak_ptr_factory_.GetWeakPtr(),
347            base::Passed(file_definition_list.Pass()),
348            base::Passed(entry_definition_list.Pass()),
349            handler_pid));
350  }
351}
352
353void FileBrowserHandlerExecutor::SetupPermissionsAndDispatchEvent(
354    scoped_ptr<FileDefinitionList> file_definition_list,
355    scoped_ptr<EntryDefinitionList> entry_definition_list,
356    int handler_pid_in,
357    extensions::ExtensionHost* host) {
358  int handler_pid = host ? host->render_process_host()->GetID() :
359      handler_pid_in;
360
361  if (handler_pid <= 0) {
362    ExecuteDoneOnUIThread(false);
363    return;
364  }
365
366  extensions::EventRouter* router = extensions::EventRouter::Get(profile_);
367  if (!router) {
368    ExecuteDoneOnUIThread(false);
369    return;
370  }
371
372  SetupHandlerHostFileAccessPermissions(
373      file_definition_list.get(), extension_.get(), handler_pid);
374
375  scoped_ptr<base::ListValue> event_args(new base::ListValue());
376  event_args->Append(new base::StringValue(action_id_));
377  base::DictionaryValue* details = new base::DictionaryValue();
378  event_args->Append(details);
379  // Get file definitions. These will be replaced with Entry instances by
380  // dispatchEvent() method from event_binding.js.
381  base::ListValue* file_entries = new base::ListValue();
382  details->Set("entries", file_entries);
383
384  for (EntryDefinitionList::const_iterator iter =
385           entry_definition_list->begin();
386       iter != entry_definition_list->end();
387       ++iter) {
388    base::DictionaryValue* file_def = new base::DictionaryValue();
389    file_entries->Append(file_def);
390    file_def->SetString("fileSystemName", iter->file_system_name);
391    file_def->SetString("fileSystemRoot", iter->file_system_root_url);
392    file_def->SetString("fileFullPath",
393                        "/" + iter->full_path.AsUTF8Unsafe());
394    file_def->SetBoolean("fileIsDirectory", iter->is_directory);
395  }
396
397  scoped_ptr<extensions::Event> event(new extensions::Event(
398      "fileBrowserHandler.onExecute", event_args.Pass()));
399  event->restrict_to_browser_context = profile_;
400  router->DispatchEventToExtension(extension_->id(), event.Pass());
401
402  ExecuteDoneOnUIThread(true);
403}
404
405void FileBrowserHandlerExecutor::SetupHandlerHostFileAccessPermissions(
406    FileDefinitionList* file_definition_list,
407    const Extension* extension,
408    int handler_pid) {
409  const FileBrowserHandler* action = FindFileBrowserHandlerForActionId(
410      extension_, action_id_);
411  for (FileDefinitionList::const_iterator iter = file_definition_list->begin();
412       iter != file_definition_list->end();
413       ++iter) {
414    if (!action)
415      continue;
416    if (action->CanRead()) {
417      content::ChildProcessSecurityPolicy::GetInstance()->GrantReadFile(
418          handler_pid, iter->absolute_path);
419    }
420    if (action->CanWrite()) {
421      content::ChildProcessSecurityPolicy::GetInstance()->
422          GrantCreateReadWriteFile(handler_pid, iter->absolute_path);
423    }
424  }
425}
426
427// Returns true if |extension_id| and |action_id| indicate that the file
428// currently being handled should be opened with the browser. This function
429// is used to handle certain action IDs of the file manager.
430bool ShouldBeOpenedWithBrowser(const std::string& extension_id,
431                               const std::string& action_id) {
432
433  return (extension_id == kFileManagerAppId &&
434          (action_id == "view-pdf" ||
435           action_id == "view-swf" ||
436           action_id == "view-in-browser" ||
437           action_id == "open-hosted-generic" ||
438           action_id == "open-hosted-gdoc" ||
439           action_id == "open-hosted-gsheet" ||
440           action_id == "open-hosted-gslides"));
441}
442
443// Opens the files specified by |file_urls| with the browser for |profile|.
444// Returns true on success. It's a failure if no files are opened.
445bool OpenFilesWithBrowser(Profile* profile,
446                          const std::vector<FileSystemURL>& file_urls) {
447  int num_opened = 0;
448  for (size_t i = 0; i < file_urls.size(); ++i) {
449    const FileSystemURL& file_url = file_urls[i];
450    if (chromeos::FileSystemBackend::CanHandleURL(file_url)) {
451      const base::FilePath& file_path = file_url.path();
452      num_opened += util::OpenFileWithBrowser(profile, file_path);
453    }
454  }
455  return num_opened > 0;
456}
457
458}  // namespace
459
460bool ExecuteFileBrowserHandler(
461    Profile* profile,
462    const Extension* extension,
463    const std::string& action_id,
464    const std::vector<FileSystemURL>& file_urls,
465    const file_tasks::FileTaskFinishedCallback& done) {
466  // Forbid calling undeclared handlers.
467  if (!FindFileBrowserHandlerForActionId(extension, action_id))
468    return false;
469
470  // Some action IDs of the file manager's file browser handlers require the
471  // files to be directly opened with the browser.
472  if (ShouldBeOpenedWithBrowser(extension->id(), action_id)) {
473    const bool result = OpenFilesWithBrowser(profile, file_urls);
474    if (result && !done.is_null())
475      done.Run(extensions::api::file_browser_private::TASK_RESULT_OPENED);
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.task_type == file_tasks::TASK_TYPE_FILE_HANDLER) &&
488          (task.app_id == kFileManagerAppId ||
489           task.app_id == kVideoPlayerAppId ||
490           task.app_id == kGalleryAppId ||
491           task.app_id == extension_misc::kQuickOfficeComponentExtensionId ||
492           task.app_id == extension_misc::kQuickOfficeInternalExtensionId ||
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  return common_handlers;
532}
533
534}  // namespace file_browser_handlers
535}  // namespace file_manager
536