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