| // Copyright 2013 The Chromium Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "apps/launcher.h" | 
 |  | 
 | #include <memory> | 
 | #include <set> | 
 | #include <utility> | 
 |  | 
 | #include "base/command_line.h" | 
 | #include "base/files/file_path.h" | 
 | #include "base/files/file_util.h" | 
 | #include "base/logging.h" | 
 | #include "base/memory/ptr_util.h" | 
 | #include "base/memory/ref_counted.h" | 
 | #include "base/strings/string_util.h" | 
 | #include "base/strings/utf_string_conversions.h" | 
 | #include "base/task_scheduler/post_task.h" | 
 | #include "base/task_scheduler/task_traits.h" | 
 | #include "content/public/browser/browser_context.h" | 
 | #include "content/public/browser/browser_thread.h" | 
 | #include "content/public/browser/render_process_host.h" | 
 | #include "content/public/browser/web_contents.h" | 
 | #include "content/public/common/content_switches.h" | 
 | #include "content/public/common/url_constants.h" | 
 | #include "extensions/browser/api/app_runtime/app_runtime_api.h" | 
 | #include "extensions/browser/api/file_handlers/app_file_handler_util.h" | 
 | #include "extensions/browser/api/file_handlers/directory_util.h" | 
 | #include "extensions/browser/api/file_handlers/mime_util.h" | 
 | #include "extensions/browser/entry_info.h" | 
 | #include "extensions/browser/event_router.h" | 
 | #include "extensions/browser/extension_host.h" | 
 | #include "extensions/browser/extension_prefs.h" | 
 | #include "extensions/browser/extension_registry.h" | 
 | #include "extensions/browser/granted_file_entry.h" | 
 | #include "extensions/browser/lazy_background_task_queue.h" | 
 | #include "extensions/browser/process_manager.h" | 
 | #include "extensions/common/api/app_runtime.h" | 
 | #include "extensions/common/extension.h" | 
 | #include "extensions/common/manifest_handlers/kiosk_mode_info.h" | 
 | #include "extensions/common/permissions/api_permission.h" | 
 | #include "extensions/common/permissions/permissions_data.h" | 
 | #include "net/base/filename_util.h" | 
 | #include "url/gurl.h" | 
 |  | 
 | #if defined(OS_CHROMEOS) | 
 | #include "components/user_manager/user_manager.h" | 
 | #endif | 
 |  | 
 | namespace app_runtime = extensions::api::app_runtime; | 
 |  | 
 | using content::BrowserThread; | 
 | using extensions::AppRuntimeEventRouter; | 
 | using extensions::api::app_runtime::PlayStoreStatus; | 
 | using extensions::app_file_handler_util::CreateFileEntry; | 
 | using extensions::app_file_handler_util::FileHandlerCanHandleEntry; | 
 | using extensions::app_file_handler_util::FileHandlerForId; | 
 | using extensions::app_file_handler_util::HasFileSystemWritePermission; | 
 | using extensions::app_file_handler_util::PrepareFilesForWritableApp; | 
 | using extensions::EventRouter; | 
 | using extensions::Extension; | 
 | using extensions::ExtensionHost; | 
 | using extensions::GrantedFileEntry; | 
 |  | 
 | namespace apps { | 
 |  | 
 | namespace { | 
 |  | 
 | const char kFallbackMimeType[] = "application/octet-stream"; | 
 |  | 
 | bool DoMakePathAbsolute(const base::FilePath& current_directory, | 
 |                         base::FilePath* file_path) { | 
 |   DCHECK(file_path); | 
 |   if (file_path->IsAbsolute()) | 
 |     return true; | 
 |  | 
 |   if (current_directory.empty()) { | 
 |     base::FilePath absolute_path = base::MakeAbsoluteFilePath(*file_path); | 
 |     if (absolute_path.empty()) | 
 |       return false; | 
 |     *file_path = absolute_path; | 
 |     return true; | 
 |   } | 
 |  | 
 |   if (!current_directory.IsAbsolute()) | 
 |     return false; | 
 |  | 
 |   *file_path = current_directory.Append(*file_path); | 
 |   return true; | 
 | } | 
 |  | 
 | // Class to handle launching of platform apps to open specific paths. | 
 | // An instance of this class is created for each launch. The lifetime of these | 
 | // instances is managed by reference counted pointers. As long as an instance | 
 | // has outstanding tasks on a message queue it will be retained; once all | 
 | // outstanding tasks are completed it will be deleted. | 
 | class PlatformAppPathLauncher | 
 |     : public base::RefCountedThreadSafe<PlatformAppPathLauncher> { | 
 |  public: | 
 |   PlatformAppPathLauncher(content::BrowserContext* context, | 
 |                           const Extension* app, | 
 |                           const std::vector<base::FilePath>& entry_paths) | 
 |       : context_(context), | 
 |         extension_id(app->id()), | 
 |         entry_paths_(entry_paths), | 
 |         mime_type_collector_(context), | 
 |         is_directory_collector_(context) {} | 
 |  | 
 |   PlatformAppPathLauncher(content::BrowserContext* context, | 
 |                           const Extension* app, | 
 |                           const base::FilePath& file_path) | 
 |       : context_(context), | 
 |         extension_id(app->id()), | 
 |         mime_type_collector_(context), | 
 |         is_directory_collector_(context) { | 
 |     if (!file_path.empty()) | 
 |       entry_paths_.push_back(file_path); | 
 |   } | 
 |  | 
 |   void set_action_data(std::unique_ptr<app_runtime::ActionData> action_data) { | 
 |     action_data_ = std::move(action_data); | 
 |   } | 
 |  | 
 |   void set_launch_source(extensions::AppLaunchSource launch_source) { | 
 |     launch_source_ = launch_source; | 
 |   } | 
 |  | 
 |   void Launch() { | 
 |     DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
 |  | 
 |     const Extension* app = GetExtension(); | 
 |     if (!app) | 
 |       return; | 
 |  | 
 |     if (entry_paths_.empty()) { | 
 |       LaunchWithNoLaunchData(); | 
 |       return; | 
 |     } | 
 |  | 
 |     for (size_t i = 0; i < entry_paths_.size(); ++i) { | 
 |       DCHECK(entry_paths_[i].IsAbsolute()); | 
 |     } | 
 |  | 
 |     is_directory_collector_.CollectForEntriesPaths( | 
 |         entry_paths_, | 
 |         base::Bind(&PlatformAppPathLauncher::OnAreDirectoriesCollected, this, | 
 |                    HasFileSystemWritePermission(app))); | 
 |   } | 
 |  | 
 |   void LaunchWithHandler(const std::string& handler_id) { | 
 |     handler_id_ = handler_id; | 
 |     Launch(); | 
 |   } | 
 |  | 
 |   void LaunchWithRelativePath(const base::FilePath& current_directory) { | 
 |     base::PostTaskWithTraits( | 
 |         FROM_HERE, | 
 |         {base::TaskPriority::USER_VISIBLE, base::MayBlock(), | 
 |          base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}, | 
 |         base::Bind(&PlatformAppPathLauncher::MakePathAbsolute, this, | 
 |                    current_directory)); | 
 |   } | 
 |  | 
 |  private: | 
 |   friend class base::RefCountedThreadSafe<PlatformAppPathLauncher>; | 
 |  | 
 |   virtual ~PlatformAppPathLauncher() {} | 
 |  | 
 |   void MakePathAbsolute(const base::FilePath& current_directory) { | 
 |     for (std::vector<base::FilePath>::iterator it = entry_paths_.begin(); | 
 |          it != entry_paths_.end(); ++it) { | 
 |       if (!DoMakePathAbsolute(current_directory, &*it)) { | 
 |         LOG(WARNING) << "Cannot make absolute path from " << it->value(); | 
 |         BrowserThread::PostTask( | 
 |             BrowserThread::UI, | 
 |             FROM_HERE, | 
 |             base::Bind(&PlatformAppPathLauncher::LaunchWithNoLaunchData, this)); | 
 |         return; | 
 |       } | 
 |     } | 
 |  | 
 |     BrowserThread::PostTask(BrowserThread::UI, | 
 |                             FROM_HERE, | 
 |                             base::Bind(&PlatformAppPathLauncher::Launch, this)); | 
 |   } | 
 |  | 
 |   void OnFilesValid(std::unique_ptr<std::set<base::FilePath>> directory_paths) { | 
 |     mime_type_collector_.CollectForLocalPaths( | 
 |         entry_paths_, | 
 |         base::Bind( | 
 |             &PlatformAppPathLauncher::OnAreDirectoriesAndMimeTypesCollected, | 
 |             this, base::Passed(std::move(directory_paths)))); | 
 |   } | 
 |  | 
 |   void OnFilesInvalid(const base::FilePath& /* error_path */) { | 
 |     LaunchWithNoLaunchData(); | 
 |   } | 
 |  | 
 |   void LaunchWithNoLaunchData() { | 
 |     // This method is required as an entry point on the UI thread. | 
 |     DCHECK_CURRENTLY_ON(BrowserThread::UI); | 
 |  | 
 |     const Extension* app = GetExtension(); | 
 |     if (!app) | 
 |       return; | 
 |  | 
 |     std::unique_ptr<app_runtime::LaunchData> launch_data = | 
 |         base::MakeUnique<app_runtime::LaunchData>(); | 
 |     launch_data->action_data = std::move(action_data_); | 
 |  | 
 |     AppRuntimeEventRouter::DispatchOnLaunchedEvent( | 
 |         context_, app, launch_source_, std::move(launch_data)); | 
 |   } | 
 |  | 
 |   void OnAreDirectoriesCollected( | 
 |       bool has_file_system_write_permission, | 
 |       std::unique_ptr<std::set<base::FilePath>> directory_paths) { | 
 |     if (has_file_system_write_permission) { | 
 |       std::set<base::FilePath>* const directory_paths_ptr = | 
 |           directory_paths.get(); | 
 |       PrepareFilesForWritableApp( | 
 |           entry_paths_, context_, *directory_paths_ptr, | 
 |           base::Bind(&PlatformAppPathLauncher::OnFilesValid, this, | 
 |                      base::Passed(std::move(directory_paths))), | 
 |           base::Bind(&PlatformAppPathLauncher::OnFilesInvalid, this)); | 
 |       return; | 
 |     } | 
 |  | 
 |     OnFilesValid(std::move(directory_paths)); | 
 |   } | 
 |  | 
 |   void OnAreDirectoriesAndMimeTypesCollected( | 
 |       std::unique_ptr<std::set<base::FilePath>> directory_paths, | 
 |       std::unique_ptr<std::vector<std::string>> mime_types) { | 
 |     DCHECK(entry_paths_.size() == mime_types->size()); | 
 |     // If fetching a mime type failed, then use a fallback one. | 
 |     for (size_t i = 0; i < entry_paths_.size(); ++i) { | 
 |       const std::string mime_type = | 
 |           !(*mime_types)[i].empty() ? (*mime_types)[i] : kFallbackMimeType; | 
 |       bool is_directory = | 
 |           directory_paths->find(entry_paths_[i]) != directory_paths->end(); | 
 |       entries_.push_back( | 
 |           extensions::EntryInfo(entry_paths_[i], mime_type, is_directory)); | 
 |     } | 
 |  | 
 |     const Extension* app = GetExtension(); | 
 |     if (!app) | 
 |       return; | 
 |  | 
 |     // Find file handler from the platform app for the file being opened. | 
 |     const extensions::FileHandlerInfo* handler = NULL; | 
 |     if (!handler_id_.empty()) { | 
 |       handler = FileHandlerForId(*app, handler_id_); | 
 |       if (handler) { | 
 |         for (size_t i = 0; i < entry_paths_.size(); ++i) { | 
 |           if (!FileHandlerCanHandleEntry(*handler, entries_[i])) { | 
 |             LOG(WARNING) | 
 |                 << "Extension does not provide a valid file handler for " | 
 |                 << entry_paths_[i].value(); | 
 |             handler = NULL; | 
 |             break; | 
 |           } | 
 |         } | 
 |       } | 
 |     } else { | 
 |       const std::vector<const extensions::FileHandlerInfo*>& handlers = | 
 |           extensions::app_file_handler_util::FindFileHandlersForEntries( | 
 |               *app, entries_); | 
 |       if (!handlers.empty()) | 
 |         handler = handlers[0]; | 
 |     } | 
 |  | 
 |     // If this app doesn't have a file handler that supports the file, launch | 
 |     // with no launch data. | 
 |     if (!handler) { | 
 |       LOG(WARNING) << "Extension does not provide a valid file handler."; | 
 |       LaunchWithNoLaunchData(); | 
 |       return; | 
 |     } | 
 |  | 
 |     if (handler_id_.empty()) | 
 |       handler_id_ = handler->id; | 
 |  | 
 |     // Access needs to be granted to the file for the process associated with | 
 |     // the extension. To do this the ExtensionHost is needed. This might not be | 
 |     // available, or it might be in the process of being unloaded, in which case | 
 |     // the lazy background task queue is used to load the extension and then | 
 |     // call back to us. | 
 |     extensions::LazyBackgroundTaskQueue* const queue = | 
 |         extensions::LazyBackgroundTaskQueue::Get(context_); | 
 |     if (queue->ShouldEnqueueTask(context_, app)) { | 
 |       queue->AddPendingTask( | 
 |           context_, extension_id, | 
 |           base::Bind(&PlatformAppPathLauncher::GrantAccessToFilesAndLaunch, | 
 |                      this)); | 
 |       return; | 
 |     } | 
 |  | 
 |     extensions::ProcessManager* const process_manager = | 
 |         extensions::ProcessManager::Get(context_); | 
 |     ExtensionHost* const host = | 
 |         process_manager->GetBackgroundHostForExtension(extension_id); | 
 |     DCHECK(host); | 
 |     GrantAccessToFilesAndLaunch(host); | 
 |   } | 
 |  | 
 |   void GrantAccessToFilesAndLaunch(ExtensionHost* host) { | 
 |     const Extension* app = GetExtension(); | 
 |     if (!app) | 
 |       return; | 
 |  | 
 |     // If there was an error loading the app page, |host| will be NULL. | 
 |     if (!host) { | 
 |       LOG(ERROR) << "Could not load app page for " << extension_id; | 
 |       return; | 
 |     } | 
 |  | 
 |     std::vector<GrantedFileEntry> granted_entries; | 
 |     for (size_t i = 0; i < entry_paths_.size(); ++i) { | 
 |       granted_entries.push_back( | 
 |           CreateFileEntry(context_, app, host->render_process_host()->GetID(), | 
 |                           entries_[i].path, entries_[i].is_directory)); | 
 |     } | 
 |  | 
 |     AppRuntimeEventRouter::DispatchOnLaunchedEventWithFileEntries( | 
 |         context_, app, launch_source_, handler_id_, entries_, granted_entries, | 
 |         std::move(action_data_)); | 
 |   } | 
 |  | 
 |   const Extension* GetExtension() const { | 
 |     return extensions::ExtensionRegistry::Get(context_)->GetExtensionById( | 
 |         extension_id, extensions::ExtensionRegistry::EVERYTHING); | 
 |   } | 
 |  | 
 |   // The browser context the app should be run in. | 
 |   content::BrowserContext* context_; | 
 |   // The id of the extension providing the app. A pointer to the extension is | 
 |   // not kept as the extension may be unloaded and deleted during the course of | 
 |   // the launch. | 
 |   const std::string extension_id; | 
 |   extensions::AppLaunchSource launch_source_ = extensions::SOURCE_FILE_HANDLER; | 
 |   std::unique_ptr<app_runtime::ActionData> action_data_; | 
 |   // A list of files and directories to be passed through to the app. | 
 |   std::vector<base::FilePath> entry_paths_; | 
 |   // A corresponding list with EntryInfo for every base::FilePath in | 
 |   // entry_paths_. | 
 |   std::vector<extensions::EntryInfo> entries_; | 
 |   // The ID of the file handler used to launch the app. | 
 |   std::string handler_id_; | 
 |   extensions::app_file_handler_util::MimeTypeCollector mime_type_collector_; | 
 |   extensions::app_file_handler_util::IsDirectoryCollector | 
 |       is_directory_collector_; | 
 |  | 
 |   DISALLOW_COPY_AND_ASSIGN(PlatformAppPathLauncher); | 
 | }; | 
 |  | 
 | }  // namespace | 
 |  | 
 | void LaunchPlatformAppWithCommandLine(content::BrowserContext* context, | 
 |                                       const extensions::Extension* app, | 
 |                                       const base::CommandLine& command_line, | 
 |                                       const base::FilePath& current_directory, | 
 |                                       extensions::AppLaunchSource source, | 
 |                                       PlayStoreStatus play_store_status) { | 
 |   LaunchPlatformAppWithCommandLineAndLaunchId(context, app, "", command_line, | 
 |                                               current_directory, source, | 
 |                                               play_store_status); | 
 | } | 
 |  | 
 | void LaunchPlatformAppWithCommandLineAndLaunchId( | 
 |     content::BrowserContext* context, | 
 |     const extensions::Extension* app, | 
 |     const std::string& launch_id, | 
 |     const base::CommandLine& command_line, | 
 |     const base::FilePath& current_directory, | 
 |     extensions::AppLaunchSource source, | 
 |     PlayStoreStatus play_store_status) { | 
 |   // An app with "kiosk_only" should not be installed and launched | 
 |   // outside of ChromeOS kiosk mode in the first place. This is a defensive | 
 |   // check in case this scenario does occur. | 
 |   if (extensions::KioskModeInfo::IsKioskOnly(app)) { | 
 |     bool in_kiosk_mode = false; | 
 | #if defined(OS_CHROMEOS) | 
 |     user_manager::UserManager* user_manager = user_manager::UserManager::Get(); | 
 |     in_kiosk_mode = user_manager && user_manager->IsLoggedInAsKioskApp(); | 
 | #endif | 
 |     if (!in_kiosk_mode) { | 
 |       LOG(ERROR) << "App with 'kiosk_only' attribute must be run in " | 
 |                  << " ChromeOS kiosk mode."; | 
 |       NOTREACHED(); | 
 |       return; | 
 |     } | 
 |   } | 
 |  | 
 | #if defined(OS_WIN) | 
 |   base::CommandLine::StringType about_blank_url( | 
 |       base::ASCIIToUTF16(url::kAboutBlankURL)); | 
 | #else | 
 |   base::CommandLine::StringType about_blank_url(url::kAboutBlankURL); | 
 | #endif | 
 |   base::CommandLine::StringVector args = command_line.GetArgs(); | 
 |   // Browser tests will add about:blank to the command line. This should | 
 |   // never be interpreted as a file to open, as doing so with an app that | 
 |   // has write access will result in a file 'about' being created, which | 
 |   // causes problems on the bots. | 
 |   if (args.empty() || (command_line.HasSwitch(switches::kTestType) && | 
 |                        args[0] == about_blank_url)) { | 
 |     std::unique_ptr<app_runtime::LaunchData> launch_data = | 
 |         base::MakeUnique<app_runtime::LaunchData>(); | 
 |     if (play_store_status != PlayStoreStatus::PLAY_STORE_STATUS_UNKNOWN) | 
 |       launch_data->play_store_status = play_store_status; | 
 |     if (!launch_id.empty()) | 
 |       launch_data->id.reset(new std::string(launch_id)); | 
 |     AppRuntimeEventRouter::DispatchOnLaunchedEvent(context, app, source, | 
 |                                                    std::move(launch_data)); | 
 |     return; | 
 |   } | 
 |  | 
 |   base::FilePath file_path(command_line.GetArgs()[0]); | 
 |   scoped_refptr<PlatformAppPathLauncher> launcher = | 
 |       new PlatformAppPathLauncher(context, app, file_path); | 
 |   launcher->LaunchWithRelativePath(current_directory); | 
 | } | 
 |  | 
 | void LaunchPlatformAppWithPath(content::BrowserContext* context, | 
 |                                const Extension* app, | 
 |                                const base::FilePath& file_path) { | 
 |   scoped_refptr<PlatformAppPathLauncher> launcher = | 
 |       new PlatformAppPathLauncher(context, app, file_path); | 
 |   launcher->Launch(); | 
 | } | 
 |  | 
 | void LaunchPlatformAppWithAction( | 
 |     content::BrowserContext* context, | 
 |     const extensions::Extension* app, | 
 |     std::unique_ptr<app_runtime::ActionData> action_data, | 
 |     const base::FilePath& file_path) { | 
 |   CHECK(!action_data || !action_data->is_lock_screen_action || | 
 |         !*action_data->is_lock_screen_action || | 
 |         app->permissions_data()->HasAPIPermission( | 
 |             extensions::APIPermission::kLockScreen)) | 
 |       << "Launching lock screen action handler requires lockScreen permission."; | 
 |  | 
 |   scoped_refptr<PlatformAppPathLauncher> launcher = | 
 |       new PlatformAppPathLauncher(context, app, file_path); | 
 |   launcher->set_action_data(std::move(action_data)); | 
 |   launcher->set_launch_source(extensions::AppLaunchSource::SOURCE_UNTRACKED); | 
 |   launcher->Launch(); | 
 | } | 
 |  | 
 | void LaunchPlatformApp(content::BrowserContext* context, | 
 |                        const Extension* app, | 
 |                        extensions::AppLaunchSource source) { | 
 |   LaunchPlatformAppWithCommandLine( | 
 |       context, app, base::CommandLine(base::CommandLine::NO_PROGRAM), | 
 |       base::FilePath(), source); | 
 | } | 
 |  | 
 | void LaunchPlatformAppWithFileHandler( | 
 |     content::BrowserContext* context, | 
 |     const Extension* app, | 
 |     const std::string& handler_id, | 
 |     const std::vector<base::FilePath>& entry_paths) { | 
 |   scoped_refptr<PlatformAppPathLauncher> launcher = | 
 |       new PlatformAppPathLauncher(context, app, entry_paths); | 
 |   launcher->LaunchWithHandler(handler_id); | 
 | } | 
 |  | 
 | void RestartPlatformApp(content::BrowserContext* context, | 
 |                         const Extension* app) { | 
 |   EventRouter* event_router = EventRouter::Get(context); | 
 |   bool listening_to_restart = event_router->ExtensionHasEventListener( | 
 |       app->id(), app_runtime::OnRestarted::kEventName); | 
 |  | 
 |   if (listening_to_restart) { | 
 |     AppRuntimeEventRouter::DispatchOnRestartedEvent(context, app); | 
 |     return; | 
 |   } | 
 |  | 
 |   extensions::ExtensionPrefs* extension_prefs = | 
 |       extensions::ExtensionPrefs::Get(context); | 
 |   bool had_windows = extension_prefs->IsActive(app->id()); | 
 |   extension_prefs->SetIsActive(app->id(), false); | 
 |   bool listening_to_launch = event_router->ExtensionHasEventListener( | 
 |       app->id(), app_runtime::OnLaunched::kEventName); | 
 |  | 
 |   if (listening_to_launch && had_windows) { | 
 |     AppRuntimeEventRouter::DispatchOnLaunchedEvent( | 
 |         context, app, extensions::SOURCE_RESTART, nullptr); | 
 |   } | 
 | } | 
 |  | 
 | void LaunchPlatformAppWithUrl(content::BrowserContext* context, | 
 |                               const Extension* app, | 
 |                               const std::string& handler_id, | 
 |                               const GURL& url, | 
 |                               const GURL& referrer_url) { | 
 |   AppRuntimeEventRouter::DispatchOnLaunchedEventWithUrl( | 
 |       context, app, handler_id, url, referrer_url); | 
 | } | 
 |  | 
 | }  // namespace apps |