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/browser/api/app_runtime/app_runtime_api.h"
      8 #include "apps/browser/file_handler_util.h"
      9 #include "apps/common/api/app_runtime.h"
     10 #include "base/command_line.h"
     11 #include "base/file_util.h"
     12 #include "base/files/file_path.h"
     13 #include "base/logging.h"
     14 #include "base/memory/ref_counted.h"
     15 #include "base/strings/string_util.h"
     16 #include "base/strings/utf_string_conversions.h"
     17 #include "chrome/browser/extensions/api/file_handlers/app_file_handler_util.h"
     18 #include "chrome/browser/extensions/api/file_system/file_system_api.h"
     19 #include "chrome/browser/profiles/profile.h"
     20 #include "content/public/browser/browser_thread.h"
     21 #include "content/public/browser/render_process_host.h"
     22 #include "content/public/browser/web_contents.h"
     23 #include "content/public/common/content_switches.h"
     24 #include "content/public/common/url_constants.h"
     25 #include "extensions/browser/event_router.h"
     26 #include "extensions/browser/extension_host.h"
     27 #include "extensions/browser/extension_prefs.h"
     28 #include "extensions/browser/extension_system.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/extension_messages.h"
     33 #include "extensions/common/manifest_handlers/kiosk_mode_info.h"
     34 #include "net/base/filename_util.h"
     35 #include "net/base/mime_sniffer.h"
     36 #include "net/base/mime_util.h"
     37 #include "net/base/net_util.h"
     38 #include "url/gurl.h"
     39 
     40 #if defined(OS_CHROMEOS)
     41 #include "chrome/browser/chromeos/file_manager/filesystem_api_util.h"
     42 #include "chrome/browser/chromeos/login/users/user_manager.h"
     43 #endif
     44 
     45 namespace app_runtime = apps::api::app_runtime;
     46 
     47 using apps::file_handler_util::GrantedFileEntry;
     48 using content::BrowserThread;
     49 using extensions::app_file_handler_util::PrepareFilesForWritableApp;
     50 using extensions::app_file_handler_util::FileHandlerForId;
     51 using extensions::app_file_handler_util::FileHandlerCanHandleFile;
     52 using extensions::app_file_handler_util::FirstFileHandlerForFile;
     53 using extensions::app_file_handler_util::CreateFileEntry;
     54 using extensions::app_file_handler_util::HasFileSystemWritePermission;
     55 using extensions::EventRouter;
     56 using extensions::Extension;
     57 using extensions::ExtensionHost;
     58 using extensions::ExtensionSystem;
     59 
     60 namespace apps {
     61 
     62 namespace {
     63 
     64 const char kFallbackMimeType[] = "application/octet-stream";
     65 
     66 bool DoMakePathAbsolute(const base::FilePath& current_directory,
     67                         base::FilePath* file_path) {
     68   DCHECK(file_path);
     69   if (file_path->IsAbsolute())
     70     return true;
     71 
     72   if (current_directory.empty()) {
     73     *file_path = base::MakeAbsoluteFilePath(*file_path);
     74     return !file_path->empty();
     75   }
     76 
     77   if (!current_directory.IsAbsolute())
     78     return false;
     79 
     80   *file_path = current_directory.Append(*file_path);
     81   return true;
     82 }
     83 
     84 // Helper method to launch the platform app |extension| with no data. This
     85 // should be called in the fallback case, where it has been impossible to
     86 // load or obtain file launch data.
     87 void LaunchPlatformAppWithNoData(Profile* profile, const Extension* extension) {
     88   DCHECK_CURRENTLY_ON(BrowserThread::UI);
     89   AppEventRouter::DispatchOnLaunchedEvent(profile, extension);
     90 }
     91 
     92 // Class to handle launching of platform apps to open specific paths.
     93 // An instance of this class is created for each launch. The lifetime of these
     94 // instances is managed by reference counted pointers. As long as an instance
     95 // has outstanding tasks on a message queue it will be retained; once all
     96 // outstanding tasks are completed it will be deleted.
     97 class PlatformAppPathLauncher
     98     : public base::RefCountedThreadSafe<PlatformAppPathLauncher> {
     99  public:
    100   PlatformAppPathLauncher(Profile* profile,
    101                           const Extension* extension,
    102                           const std::vector<base::FilePath>& file_paths)
    103       : profile_(profile), extension_(extension), file_paths_(file_paths) {}
    104 
    105   PlatformAppPathLauncher(Profile* profile,
    106                           const Extension* extension,
    107                           const base::FilePath& file_path)
    108       : profile_(profile), extension_(extension) {
    109     if (!file_path.empty())
    110       file_paths_.push_back(file_path);
    111   }
    112 
    113   void Launch() {
    114     DCHECK_CURRENTLY_ON(BrowserThread::UI);
    115     if (file_paths_.empty()) {
    116       LaunchPlatformAppWithNoData(profile_, extension_);
    117       return;
    118     }
    119 
    120     for (size_t i = 0; i < file_paths_.size(); ++i) {
    121       DCHECK(file_paths_[i].IsAbsolute());
    122     }
    123 
    124     if (HasFileSystemWritePermission(extension_)) {
    125       PrepareFilesForWritableApp(
    126           file_paths_,
    127           profile_,
    128           false,
    129           base::Bind(&PlatformAppPathLauncher::OnFileValid, this),
    130           base::Bind(&PlatformAppPathLauncher::OnFileInvalid, this));
    131       return;
    132     }
    133 
    134     OnFileValid();
    135   }
    136 
    137   void LaunchWithHandler(const std::string& handler_id) {
    138     handler_id_ = handler_id;
    139     Launch();
    140   }
    141 
    142   void LaunchWithRelativePath(const base::FilePath& current_directory) {
    143     BrowserThread::PostTask(
    144         BrowserThread::FILE,
    145         FROM_HERE,
    146         base::Bind(&PlatformAppPathLauncher::MakePathAbsolute,
    147                    this,
    148                    current_directory));
    149   }
    150 
    151  private:
    152   friend class base::RefCountedThreadSafe<PlatformAppPathLauncher>;
    153 
    154   virtual ~PlatformAppPathLauncher() {}
    155 
    156   void MakePathAbsolute(const base::FilePath& current_directory) {
    157     DCHECK_CURRENTLY_ON(BrowserThread::FILE);
    158 
    159     for (std::vector<base::FilePath>::iterator it = file_paths_.begin();
    160          it != file_paths_.end();
    161          ++it) {
    162       if (!DoMakePathAbsolute(current_directory, &*it)) {
    163         LOG(WARNING) << "Cannot make absolute path from " << it->value();
    164         BrowserThread::PostTask(
    165             BrowserThread::UI,
    166             FROM_HERE,
    167             base::Bind(&PlatformAppPathLauncher::LaunchWithNoLaunchData, this));
    168         return;
    169       }
    170     }
    171 
    172     BrowserThread::PostTask(BrowserThread::UI,
    173                             FROM_HERE,
    174                             base::Bind(&PlatformAppPathLauncher::Launch, this));
    175   }
    176 
    177   void OnFileValid() {
    178     mime_types_.resize(file_paths_.size());
    179 #if defined(OS_CHROMEOS)
    180     GetNextNonNativeMimeType();
    181 #else
    182     BrowserThread::PostTask(
    183         BrowserThread::FILE,
    184         FROM_HERE,
    185         base::Bind(&PlatformAppPathLauncher::GetMimeTypesAndLaunch, this));
    186 #endif
    187   }
    188 
    189   void OnFileInvalid(const base::FilePath& /* error_path */) {
    190     LaunchWithNoLaunchData();
    191   }
    192 
    193 #if defined(OS_CHROMEOS)
    194   void GetNextNonNativeMimeType() {
    195     DCHECK_CURRENTLY_ON(BrowserThread::UI);
    196 
    197     bool any_native_files = false;
    198     for (size_t i = 0; i < mime_types_.size(); ++i) {
    199       if (!mime_types_[i].empty())
    200         continue;
    201       const base::FilePath& file_path = file_paths_[i];
    202       if (file_manager::util::IsUnderNonNativeLocalPath(profile_, file_path)) {
    203         file_manager::util::GetNonNativeLocalPathMimeType(
    204             profile_,
    205             file_path,
    206             base::Bind(&PlatformAppPathLauncher::OnGotMimeType, this, i));
    207         return;
    208       }
    209       any_native_files = true;
    210     }
    211 
    212     // If there are any native files, we need to call GetMimeTypesAndLaunch to
    213     // obtain mime types for the files.
    214     if (any_native_files) {
    215       BrowserThread::PostTask(
    216           BrowserThread::FILE,
    217           FROM_HERE,
    218           base::Bind(&PlatformAppPathLauncher::GetMimeTypesAndLaunch, this));
    219       return;
    220     }
    221 
    222     // Otherwise, we can call LaunchWithMimeTypes directly.
    223     LaunchWithMimeTypes();
    224   }
    225 
    226   void OnGotMimeType(size_t index, bool success, const std::string& mime_type) {
    227     if (!success) {
    228       LaunchWithNoLaunchData();
    229       return;
    230     }
    231     mime_types_[index] = mime_type.empty() ? kFallbackMimeType : mime_type;
    232     GetNextNonNativeMimeType();
    233   }
    234 #endif
    235 
    236   void GetMimeTypesAndLaunch() {
    237     DCHECK_CURRENTLY_ON(BrowserThread::FILE);
    238 
    239     for (size_t i = 0; i < mime_types_.size(); ++i) {
    240       if (!this->mime_types_[i].empty())
    241         continue;
    242       const base::FilePath& file_path = file_paths_[i];
    243 
    244       // If the file doesn't exist, or is a directory, launch with no launch
    245       // data.
    246       if (!base::PathExists(file_path) || base::DirectoryExists(file_path)) {
    247         LOG(WARNING) << "No file exists with path " << file_path.value();
    248         BrowserThread::PostTask(
    249             BrowserThread::UI,
    250             FROM_HERE,
    251             base::Bind(&PlatformAppPathLauncher::LaunchWithNoLaunchData, this));
    252         return;
    253       }
    254 
    255       std::string mime_type;
    256       if (!net::GetMimeTypeFromFile(file_path, &mime_type)) {
    257         // If MIME type of the file can't be determined by its path,
    258         // try to sniff it by its content.
    259         std::vector<char> content(net::kMaxBytesToSniff);
    260         int bytes_read = base::ReadFile(file_path, &content[0], content.size());
    261         if (bytes_read >= 0) {
    262           net::SniffMimeType(&content[0],
    263                              bytes_read,
    264                              net::FilePathToFileURL(file_path),
    265                              std::string(),  // type_hint (passes no hint)
    266                              &mime_type);
    267         }
    268         if (mime_type.empty())
    269           mime_type = kFallbackMimeType;
    270       }
    271       mime_types_[i] = mime_type;
    272     }
    273 
    274     BrowserThread::PostTask(
    275         BrowserThread::UI,
    276         FROM_HERE,
    277         base::Bind(&PlatformAppPathLauncher::LaunchWithMimeTypes, this));
    278   }
    279 
    280   void LaunchWithNoLaunchData() {
    281     // This method is required as an entry point on the UI thread.
    282     LaunchPlatformAppWithNoData(profile_, extension_);
    283   }
    284 
    285   void LaunchWithMimeTypes() {
    286     DCHECK(file_paths_.size() == mime_types_.size());
    287 
    288     // Find file handler from the platform app for the file being opened.
    289     const extensions::FileHandlerInfo* handler = NULL;
    290     if (!handler_id_.empty()) {
    291       handler = FileHandlerForId(*extension_, handler_id_);
    292       if (handler) {
    293         for (size_t i = 0; i < file_paths_.size(); ++i) {
    294           if (!FileHandlerCanHandleFile(
    295                   *handler, mime_types_[i], file_paths_[i])) {
    296             LOG(WARNING)
    297                 << "Extension does not provide a valid file handler for "
    298                 << file_paths_[i].value();
    299             handler = NULL;
    300             break;
    301           }
    302         }
    303       }
    304     } else {
    305       std::set<std::pair<base::FilePath, std::string> > path_and_file_type_set;
    306       for (size_t i = 0; i < file_paths_.size(); ++i) {
    307         path_and_file_type_set.insert(
    308             std::make_pair(file_paths_[i], mime_types_[i]));
    309       }
    310       const std::vector<const extensions::FileHandlerInfo*>& handlers =
    311           extensions::app_file_handler_util::FindFileHandlersForFiles(
    312               *extension_, path_and_file_type_set);
    313       if (!handlers.empty())
    314         handler = handlers[0];
    315     }
    316 
    317     // If this app doesn't have a file handler that supports the file, launch
    318     // with no launch data.
    319     if (!handler) {
    320       LOG(WARNING) << "Extension does not provide a valid file handler.";
    321       LaunchWithNoLaunchData();
    322       return;
    323     }
    324 
    325     if (handler_id_.empty())
    326       handler_id_ = handler->id;
    327 
    328     // Access needs to be granted to the file for the process associated with
    329     // the extension. To do this the ExtensionHost is needed. This might not be
    330     // available, or it might be in the process of being unloaded, in which case
    331     // the lazy background task queue is used to load the extension and then
    332     // call back to us.
    333     extensions::LazyBackgroundTaskQueue* const queue =
    334         ExtensionSystem::Get(profile_)->lazy_background_task_queue();
    335     if (queue->ShouldEnqueueTask(profile_, extension_)) {
    336       queue->AddPendingTask(
    337           profile_,
    338           extension_->id(),
    339           base::Bind(&PlatformAppPathLauncher::GrantAccessToFilesAndLaunch,
    340                      this));
    341       return;
    342     }
    343 
    344     extensions::ProcessManager* const process_manager =
    345         ExtensionSystem::Get(profile_)->process_manager();
    346     ExtensionHost* const host =
    347         process_manager->GetBackgroundHostForExtension(extension_->id());
    348     DCHECK(host);
    349     GrantAccessToFilesAndLaunch(host);
    350   }
    351 
    352   void GrantAccessToFilesAndLaunch(ExtensionHost* host) {
    353     // If there was an error loading the app page, |host| will be NULL.
    354     if (!host) {
    355       LOG(ERROR) << "Could not load app page for " << extension_->id();
    356       return;
    357     }
    358 
    359     std::vector<GrantedFileEntry> file_entries;
    360     for (size_t i = 0; i < file_paths_.size(); ++i) {
    361       file_entries.push_back(
    362           CreateFileEntry(profile_,
    363                           extension_,
    364                           host->render_process_host()->GetID(),
    365                           file_paths_[i],
    366                           false));
    367     }
    368 
    369     AppEventRouter::DispatchOnLaunchedEventWithFileEntries(
    370         profile_, extension_, handler_id_, mime_types_, file_entries);
    371   }
    372 
    373   // The profile the app should be run in.
    374   Profile* profile_;
    375   // The extension providing the app.
    376   // TODO(benwells): Hold onto the extension ID instead of a pointer as it
    377   // is possible the extension will be unloaded while we're doing our thing.
    378   // See http://crbug.com/372270 for details.
    379   const Extension* extension_;
    380   // The path to be passed through to the app.
    381   std::vector<base::FilePath> file_paths_;
    382   std::vector<std::string> mime_types_;
    383   // The ID of the file handler used to launch the app.
    384   std::string handler_id_;
    385 
    386   DISALLOW_COPY_AND_ASSIGN(PlatformAppPathLauncher);
    387 };
    388 
    389 }  // namespace
    390 
    391 void LaunchPlatformAppWithCommandLine(Profile* profile,
    392                                       const Extension* extension,
    393                                       const CommandLine& command_line,
    394                                       const base::FilePath& current_directory) {
    395   // An app with "kiosk_only" should not be installed and launched
    396   // outside of ChromeOS kiosk mode in the first place. This is a defensive
    397   // check in case this scenario does occur.
    398   if (extensions::KioskModeInfo::IsKioskOnly(extension)) {
    399     bool in_kiosk_mode = false;
    400 #if defined(OS_CHROMEOS)
    401     chromeos::UserManager* user_manager = chromeos::UserManager::Get();
    402     in_kiosk_mode = user_manager && user_manager->IsLoggedInAsKioskApp();
    403 #endif
    404     if (!in_kiosk_mode) {
    405       LOG(ERROR) << "App with 'kiosk_only' attribute must be run in "
    406           << " ChromeOS kiosk mode.";
    407       NOTREACHED();
    408       return;
    409     }
    410   }
    411 
    412 #if defined(OS_WIN)
    413   base::CommandLine::StringType about_blank_url(
    414       base::ASCIIToWide(url::kAboutBlankURL));
    415 #else
    416   base::CommandLine::StringType about_blank_url(url::kAboutBlankURL);
    417 #endif
    418   CommandLine::StringVector args = command_line.GetArgs();
    419   // Browser tests will add about:blank to the command line. This should
    420   // never be interpreted as a file to open, as doing so with an app that
    421   // has write access will result in a file 'about' being created, which
    422   // causes problems on the bots.
    423   if (args.empty() || (command_line.HasSwitch(switches::kTestType) &&
    424                        args[0] == about_blank_url)) {
    425     LaunchPlatformAppWithNoData(profile, extension);
    426     return;
    427   }
    428 
    429   base::FilePath file_path(command_line.GetArgs()[0]);
    430   scoped_refptr<PlatformAppPathLauncher> launcher =
    431       new PlatformAppPathLauncher(profile, extension, file_path);
    432   launcher->LaunchWithRelativePath(current_directory);
    433 }
    434 
    435 void LaunchPlatformAppWithPath(Profile* profile,
    436                                const Extension* extension,
    437                                const base::FilePath& file_path) {
    438   scoped_refptr<PlatformAppPathLauncher> launcher =
    439       new PlatformAppPathLauncher(profile, extension, file_path);
    440   launcher->Launch();
    441 }
    442 
    443 void LaunchPlatformApp(Profile* profile, const Extension* extension) {
    444   LaunchPlatformAppWithCommandLine(profile,
    445                                    extension,
    446                                    CommandLine(CommandLine::NO_PROGRAM),
    447                                    base::FilePath());
    448 }
    449 
    450 void LaunchPlatformAppWithFileHandler(
    451     Profile* profile,
    452     const Extension* extension,
    453     const std::string& handler_id,
    454     const std::vector<base::FilePath>& file_paths) {
    455   scoped_refptr<PlatformAppPathLauncher> launcher =
    456       new PlatformAppPathLauncher(profile, extension, file_paths);
    457   launcher->LaunchWithHandler(handler_id);
    458 }
    459 
    460 void RestartPlatformApp(Profile* profile, const Extension* extension) {
    461   EventRouter* event_router = EventRouter::Get(profile);
    462   bool listening_to_restart = event_router->
    463       ExtensionHasEventListener(extension->id(),
    464                                 app_runtime::OnRestarted::kEventName);
    465 
    466   if (listening_to_restart) {
    467     AppEventRouter::DispatchOnRestartedEvent(profile, extension);
    468     return;
    469   }
    470 
    471   extensions::ExtensionPrefs* extension_prefs =
    472       extensions::ExtensionPrefs::Get(profile);
    473   bool had_windows = extension_prefs->IsActive(extension->id());
    474   extension_prefs->SetIsActive(extension->id(), false);
    475   bool listening_to_launch = event_router->
    476       ExtensionHasEventListener(extension->id(),
    477                                 app_runtime::OnLaunched::kEventName);
    478 
    479   if (listening_to_launch && had_windows)
    480     LaunchPlatformAppWithNoData(profile, extension);
    481 }
    482 
    483 void LaunchPlatformAppWithUrl(Profile* profile,
    484                               const Extension* extension,
    485                               const std::string& handler_id,
    486                               const GURL& url,
    487                               const GURL& referrer_url) {
    488   AppEventRouter::DispatchOnLaunchedEventWithUrl(
    489       profile, extension, handler_id, url, referrer_url);
    490 }
    491 
    492 }  // namespace apps
    493