Home | History | Annotate | Download | only in file_manager
      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 
     37 using content::BrowserThread;
     38 using content::ChildProcessSecurityPolicy;
     39 using content::SiteInstance;
     40 using content::WebContents;
     41 using extensions::Extension;
     42 using fileapi::FileSystemURL;
     43 
     44 namespace file_manager {
     45 namespace file_browser_handlers {
     46 namespace {
     47 
     48 // Returns process id of the process the extension is running in.
     49 int 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.
     66 const 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 
     81 std::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|.
     90 FileBrowserHandlerList 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.
    133 FileBrowserHandlerList::iterator
    134 FindFileBrowserHandlerForExtensionIdAndActionId(
    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.
    161 class 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 
    217 FileBrowserHandlerExecutor::FileDefinition::FileDefinition()
    218     : is_directory(false) {
    219 }
    220 
    221 FileBrowserHandlerExecutor::FileDefinition::~FileDefinition() {
    222 }
    223 
    224 // static
    225 FileBrowserHandlerExecutor::FileDefinitionList
    226 FileBrowserHandlerExecutor::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 
    275 FileBrowserHandlerExecutor::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 
    285 FileBrowserHandlerExecutor::~FileBrowserHandlerExecutor() {}
    286 
    287 void 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 
    310 void FileBrowserHandlerExecutor::ExecuteDoneOnUIThread(bool success) {
    311   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    312   if (!done_.is_null())
    313     done_.Run(success);
    314   delete this;
    315 }
    316 
    317 void 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 
    358 void 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 
    411 void 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.
    436 bool 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.
    451 bool 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 
    466 bool 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 
    488 bool 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 
    496 FileBrowserHandlerList 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