Home | History | Annotate | Download | only in apps
      1 // Copyright 2013 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 "apps/launcher.h"
      6 
      7 #include "apps/apps_client.h"
      8 #include "base/command_line.h"
      9 #include "base/file_util.h"
     10 #include "base/files/file_path.h"
     11 #include "base/logging.h"
     12 #include "base/memory/ref_counted.h"
     13 #include "base/strings/string_util.h"
     14 #include "base/strings/utf_string_conversions.h"
     15 #include "chrome/browser/extensions/api/app_runtime/app_runtime_api.h"
     16 #include "chrome/browser/extensions/api/file_handlers/app_file_handler_util.h"
     17 #include "chrome/browser/extensions/api/file_system/file_system_api.h"
     18 #include "chrome/browser/extensions/extension_host.h"
     19 #include "chrome/browser/extensions/extension_prefs.h"
     20 #include "chrome/browser/extensions/extension_service.h"
     21 #include "chrome/browser/extensions/extension_system.h"
     22 #include "chrome/browser/profiles/profile.h"
     23 #include "chrome/common/extensions/api/app_runtime.h"
     24 #include "chrome/common/extensions/extension_messages.h"
     25 #include "content/public/browser/browser_thread.h"
     26 #include "content/public/browser/render_process_host.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/browser/process_manager.h"
     31 #include "extensions/common/extension.h"
     32 #include "extensions/common/manifest_handlers/kiosk_mode_info.h"
     33 #include "net/base/mime_util.h"
     34 #include "net/base/net_util.h"
     35 #include "url/gurl.h"
     36 
     37 #if defined(OS_CHROMEOS)
     38 #include "chrome/browser/chromeos/drive/file_errors.h"
     39 #include "chrome/browser/chromeos/drive/file_system_interface.h"
     40 #include "chrome/browser/chromeos/drive/file_system_util.h"
     41 #include "chrome/browser/chromeos/login/user_manager.h"
     42 #endif
     43 
     44 #if defined(OS_WIN)
     45 #include "win8/util/win8_util.h"
     46 #endif
     47 
     48 namespace app_runtime = extensions::api::app_runtime;
     49 
     50 using content::BrowserThread;
     51 using extensions::app_file_handler_util::CheckWritableFiles;
     52 using extensions::app_file_handler_util::FileHandlerForId;
     53 using extensions::app_file_handler_util::FileHandlerCanHandleFile;
     54 using extensions::app_file_handler_util::FirstFileHandlerForFile;
     55 using extensions::app_file_handler_util::CreateFileEntry;
     56 using extensions::app_file_handler_util::GrantedFileEntry;
     57 using extensions::app_file_handler_util::HasFileSystemWritePermission;
     58 using extensions::Extension;
     59 using extensions::ExtensionHost;
     60 using extensions::ExtensionSystem;
     61 
     62 namespace apps {
     63 
     64 namespace {
     65 
     66 const char kFallbackMimeType[] = "application/octet-stream";
     67 
     68 bool MakePathAbsolute(const base::FilePath& current_directory,
     69                       base::FilePath* file_path) {
     70   DCHECK(file_path);
     71   if (file_path->IsAbsolute())
     72     return true;
     73 
     74   if (current_directory.empty()) {
     75     *file_path = base::MakeAbsoluteFilePath(*file_path);
     76     return !file_path->empty();
     77   }
     78 
     79   if (!current_directory.IsAbsolute())
     80     return false;
     81 
     82   *file_path = current_directory.Append(*file_path);
     83   return true;
     84 }
     85 
     86 bool GetAbsolutePathFromCommandLine(const CommandLine* command_line,
     87                                     const base::FilePath& current_directory,
     88                                     base::FilePath* path) {
     89   if (!command_line || !command_line->GetArgs().size())
     90     return false;
     91 
     92   base::FilePath relative_path(command_line->GetArgs()[0]);
     93   base::FilePath absolute_path(relative_path);
     94   if (!MakePathAbsolute(current_directory, &absolute_path)) {
     95     LOG(WARNING) << "Cannot make absolute path from " << relative_path.value();
     96     return false;
     97   }
     98   *path = absolute_path;
     99   return true;
    100 }
    101 
    102 // Helper method to launch the platform app |extension| with no data. This
    103 // should be called in the fallback case, where it has been impossible to
    104 // load or obtain file launch data.
    105 void LaunchPlatformAppWithNoData(Profile* profile, const Extension* extension) {
    106   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    107   extensions::AppEventRouter::DispatchOnLaunchedEvent(profile, extension);
    108 }
    109 
    110 // Class to handle launching of platform apps to open a specific path.
    111 // An instance of this class is created for each launch. The lifetime of these
    112 // instances is managed by reference counted pointers. As long as an instance
    113 // has outstanding tasks on a message queue it will be retained; once all
    114 // outstanding tasks are completed it will be deleted.
    115 class PlatformAppPathLauncher
    116     : public base::RefCountedThreadSafe<PlatformAppPathLauncher> {
    117  public:
    118   PlatformAppPathLauncher(Profile* profile,
    119                           const Extension* extension,
    120                           const base::FilePath& file_path)
    121       : profile_(profile), extension_(extension), file_path_(file_path) {}
    122 
    123   void Launch() {
    124     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    125     if (file_path_.empty()) {
    126       LaunchPlatformAppWithNoData(profile_, extension_);
    127       return;
    128     }
    129 
    130     DCHECK(file_path_.IsAbsolute());
    131 
    132     if (HasFileSystemWritePermission(extension_)) {
    133       std::vector<base::FilePath> paths;
    134       paths.push_back(file_path_);
    135       CheckWritableFiles(
    136           paths,
    137           profile_,
    138           false,
    139           base::Bind(&PlatformAppPathLauncher::OnFileValid, this),
    140           base::Bind(&PlatformAppPathLauncher::OnFileInvalid, this));
    141       return;
    142     }
    143 
    144     OnFileValid();
    145   }
    146 
    147   void LaunchWithHandler(const std::string& handler_id) {
    148     handler_id_ = handler_id;
    149     Launch();
    150   }
    151 
    152  private:
    153   friend class base::RefCountedThreadSafe<PlatformAppPathLauncher>;
    154 
    155   virtual ~PlatformAppPathLauncher() {}
    156 
    157   void OnFileValid() {
    158 #if defined(OS_CHROMEOS)
    159     if (drive::util::IsUnderDriveMountPoint(file_path_)) {
    160       PlatformAppPathLauncher::GetMimeTypeAndLaunchForDriveFile();
    161       return;
    162     }
    163 #endif
    164 
    165     BrowserThread::PostTask(
    166         BrowserThread::FILE,
    167         FROM_HERE,
    168         base::Bind(&PlatformAppPathLauncher::GetMimeTypeAndLaunch, this));
    169   }
    170 
    171   void OnFileInvalid(const base::FilePath& /* error_path */) {
    172     LaunchWithNoLaunchData();
    173   }
    174 
    175   void GetMimeTypeAndLaunch() {
    176     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    177 
    178     // If the file doesn't exist, or is a directory, launch with no launch data.
    179     if (!base::PathExists(file_path_) ||
    180         base::DirectoryExists(file_path_)) {
    181       LOG(WARNING) << "No file exists with path " << file_path_.value();
    182       BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(
    183               &PlatformAppPathLauncher::LaunchWithNoLaunchData, this));
    184       return;
    185     }
    186 
    187     std::string mime_type;
    188     if (!net::GetMimeTypeFromFile(file_path_, &mime_type))
    189       mime_type = kFallbackMimeType;
    190 
    191     BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::Bind(
    192             &PlatformAppPathLauncher::LaunchWithMimeType, this, mime_type));
    193   }
    194 
    195 #if defined(OS_CHROMEOS)
    196   void GetMimeTypeAndLaunchForDriveFile() {
    197     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    198 
    199     drive::FileSystemInterface* file_system =
    200         drive::util::GetFileSystemByProfile(profile_);
    201     if (!file_system) {
    202       LaunchWithNoLaunchData();
    203       return;
    204     }
    205 
    206     file_system->GetFile(
    207         drive::util::ExtractDrivePath(file_path_),
    208         base::Bind(&PlatformAppPathLauncher::OnGotDriveFile, this));
    209   }
    210 
    211   void OnGotDriveFile(drive::FileError error,
    212                       const base::FilePath& file_path,
    213                       scoped_ptr<drive::ResourceEntry> entry) {
    214     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    215 
    216     if (error != drive::FILE_ERROR_OK ||
    217         !entry || entry->file_specific_info().is_hosted_document()) {
    218       LaunchWithNoLaunchData();
    219       return;
    220     }
    221 
    222     const std::string& mime_type =
    223         entry->file_specific_info().content_mime_type();
    224     LaunchWithMimeType(mime_type.empty() ? kFallbackMimeType : mime_type);
    225   }
    226 #endif  // defined(OS_CHROMEOS)
    227 
    228   void LaunchWithNoLaunchData() {
    229     // This method is required as an entry point on the UI thread.
    230     LaunchPlatformAppWithNoData(profile_, extension_);
    231   }
    232 
    233   void LaunchWithMimeType(const std::string& mime_type) {
    234     // Find file handler from the platform app for the file being opened.
    235     const extensions::FileHandlerInfo* handler = NULL;
    236     if (!handler_id_.empty())
    237       handler = FileHandlerForId(*extension_, handler_id_);
    238     else
    239       handler = FirstFileHandlerForFile(*extension_, mime_type, file_path_);
    240     if (handler && !FileHandlerCanHandleFile(*handler, mime_type, file_path_)) {
    241       LOG(WARNING) << "Extension does not provide a valid file handler for "
    242                    << file_path_.value();
    243       LaunchWithNoLaunchData();
    244       return;
    245     }
    246 
    247     // If this app doesn't have a file handler that supports the file, launch
    248     // with no launch data.
    249     if (!handler) {
    250       LOG(WARNING) << "Extension does not provide a valid file handler for "
    251                    << file_path_.value();
    252       LaunchWithNoLaunchData();
    253       return;
    254     }
    255 
    256     if (handler_id_.empty())
    257       handler_id_ = handler->id;
    258 
    259     // Access needs to be granted to the file for the process associated with
    260     // the extension. To do this the ExtensionHost is needed. This might not be
    261     // available, or it might be in the process of being unloaded, in which case
    262     // the lazy background task queue is used to load the extension and then
    263     // call back to us.
    264     extensions::LazyBackgroundTaskQueue* queue =
    265         ExtensionSystem::Get(profile_)->lazy_background_task_queue();
    266     if (queue->ShouldEnqueueTask(profile_, extension_)) {
    267       queue->AddPendingTask(profile_, extension_->id(), base::Bind(
    268               &PlatformAppPathLauncher::GrantAccessToFileAndLaunch,
    269               this, mime_type));
    270       return;
    271     }
    272 
    273     extensions::ProcessManager* process_manager =
    274         ExtensionSystem::Get(profile_)->process_manager();
    275     ExtensionHost* host =
    276         process_manager->GetBackgroundHostForExtension(extension_->id());
    277     DCHECK(host);
    278     GrantAccessToFileAndLaunch(mime_type, host);
    279   }
    280 
    281   void GrantAccessToFileAndLaunch(const std::string& mime_type,
    282                                   ExtensionHost* host) {
    283     // If there was an error loading the app page, |host| will be NULL.
    284     if (!host) {
    285       LOG(ERROR) << "Could not load app page for " << extension_->id();
    286       return;
    287     }
    288 
    289     GrantedFileEntry file_entry =
    290         CreateFileEntry(profile_,
    291                         extension_,
    292                         host->render_process_host()->GetID(),
    293                         file_path_,
    294                         false);
    295     extensions::AppEventRouter::DispatchOnLaunchedEventWithFileEntry(
    296         profile_, extension_, handler_id_, mime_type, file_entry);
    297   }
    298 
    299   // The profile the app should be run in.
    300   Profile* profile_;
    301   // The extension providing the app.
    302   const Extension* extension_;
    303   // The path to be passed through to the app.
    304   const base::FilePath file_path_;
    305   // The ID of the file handler used to launch the app.
    306   std::string handler_id_;
    307 
    308   DISALLOW_COPY_AND_ASSIGN(PlatformAppPathLauncher);
    309 };
    310 
    311 }  // namespace
    312 
    313 void LaunchPlatformAppWithCommandLine(Profile* profile,
    314                                       const Extension* extension,
    315                                       const CommandLine* command_line,
    316                                       const base::FilePath& current_directory) {
    317   if (!AppsClient::Get()->CheckAppLaunch(profile, extension))
    318     return;
    319 
    320   // An app with "kiosk_only" should not be installed and launched
    321   // outside of ChromeOS kiosk mode in the first place. This is a defensive
    322   // check in case this scenario does occur.
    323   if (extensions::KioskModeInfo::IsKioskOnly(extension)) {
    324     bool in_kiosk_mode = false;
    325 #if defined(OS_CHROMEOS)
    326     chromeos::UserManager* user_manager = chromeos::UserManager::Get();
    327     in_kiosk_mode = user_manager && user_manager->IsLoggedInAsKioskApp();
    328 #endif
    329     if (!in_kiosk_mode) {
    330       LOG(ERROR) << "App with 'kiosk_only' attribute must be run in "
    331           << " ChromeOS kiosk mode.";
    332       NOTREACHED();
    333       return;
    334     }
    335   }
    336 
    337   base::FilePath path;
    338   if (!GetAbsolutePathFromCommandLine(command_line, current_directory, &path)) {
    339     LaunchPlatformAppWithNoData(profile, extension);
    340     return;
    341   }
    342 
    343   // TODO(benwells): add a command-line argument to provide a handler ID.
    344   LaunchPlatformAppWithPath(profile, extension, path);
    345 }
    346 
    347 void LaunchPlatformAppWithPath(Profile* profile,
    348                                const Extension* extension,
    349                                const base::FilePath& file_path) {
    350   // launcher will be freed when nothing has a reference to it. The message
    351   // queue will retain a reference for any outstanding task, so when the
    352   // launcher has finished it will be freed.
    353   scoped_refptr<PlatformAppPathLauncher> launcher =
    354       new PlatformAppPathLauncher(profile, extension, file_path);
    355   launcher->Launch();
    356 }
    357 
    358 void LaunchPlatformApp(Profile* profile, const Extension* extension) {
    359   LaunchPlatformAppWithCommandLine(profile, extension, NULL, base::FilePath());
    360 }
    361 
    362 void LaunchPlatformAppWithFileHandler(Profile* profile,
    363                                       const Extension* extension,
    364                                       const std::string& handler_id,
    365                                       const base::FilePath& file_path) {
    366   scoped_refptr<PlatformAppPathLauncher> launcher =
    367       new PlatformAppPathLauncher(profile, extension, file_path);
    368   launcher->LaunchWithHandler(handler_id);
    369 }
    370 
    371 void RestartPlatformApp(Profile* profile, const Extension* extension) {
    372 #if defined(OS_WIN)
    373   // On Windows 8's single window Metro mode we can not launch platform apps.
    374   // In restart we are just making sure launch doesn't slip through.
    375   if (win8::IsSingleWindowMetroMode())
    376     return;
    377 #endif
    378   extensions::EventRouter* event_router =
    379       ExtensionSystem::Get(profile)->event_router();
    380   bool listening_to_restart = event_router->
    381       ExtensionHasEventListener(extension->id(),
    382                                 app_runtime::OnRestarted::kEventName);
    383 
    384   if (listening_to_restart) {
    385     extensions::AppEventRouter::DispatchOnRestartedEvent(profile, extension);
    386     return;
    387   }
    388 
    389   extensions::ExtensionPrefs* extension_prefs = ExtensionSystem::Get(profile)->
    390       extension_service()->extension_prefs();
    391   bool had_windows = extension_prefs->IsActive(extension->id());
    392   extension_prefs->SetIsActive(extension->id(), false);
    393   bool listening_to_launch = event_router->
    394       ExtensionHasEventListener(extension->id(),
    395                                 app_runtime::OnLaunched::kEventName);
    396 
    397   if (listening_to_launch && had_windows)
    398     LaunchPlatformAppWithNoData(profile, extension);
    399 }
    400 
    401 void LaunchPlatformAppWithUrl(Profile* profile,
    402                               const Extension* extension,
    403                               const std::string& handler_id,
    404                               const GURL& url,
    405                               const GURL& referrer_url) {
    406   extensions::AppEventRouter::DispatchOnLaunchedEventWithUrl(
    407       profile, extension, handler_id, url, referrer_url);
    408 }
    409 
    410 }  // namespace apps
    411