// 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 "apps/browser/api/app_runtime/app_runtime_api.h" #include "apps/browser/file_handler_util.h" #include "apps/common/api/app_runtime.h" #include "base/command_line.h" #include "base/file_util.h" #include "base/files/file_path.h" #include "base/logging.h" #include "base/memory/ref_counted.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "chrome/browser/extensions/api/file_handlers/app_file_handler_util.h" #include "chrome/browser/extensions/api/file_system/file_system_api.h" #include "chrome/browser/profiles/profile.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/event_router.h" #include "extensions/browser/extension_host.h" #include "extensions/browser/extension_prefs.h" #include "extensions/browser/extension_system.h" #include "extensions/browser/lazy_background_task_queue.h" #include "extensions/browser/process_manager.h" #include "extensions/common/extension.h" #include "extensions/common/extension_messages.h" #include "extensions/common/manifest_handlers/kiosk_mode_info.h" #include "net/base/filename_util.h" #include "net/base/mime_sniffer.h" #include "net/base/mime_util.h" #include "net/base/net_util.h" #include "url/gurl.h" #if defined(OS_CHROMEOS) #include "chrome/browser/chromeos/file_manager/filesystem_api_util.h" #include "chrome/browser/chromeos/login/users/user_manager.h" #endif namespace app_runtime = apps::api::app_runtime; using apps::file_handler_util::GrantedFileEntry; using content::BrowserThread; using extensions::app_file_handler_util::PrepareFilesForWritableApp; using extensions::app_file_handler_util::FileHandlerForId; using extensions::app_file_handler_util::FileHandlerCanHandleFile; using extensions::app_file_handler_util::FirstFileHandlerForFile; using extensions::app_file_handler_util::CreateFileEntry; using extensions::app_file_handler_util::HasFileSystemWritePermission; using extensions::EventRouter; using extensions::Extension; using extensions::ExtensionHost; using extensions::ExtensionSystem; 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()) { *file_path = base::MakeAbsoluteFilePath(*file_path); return !file_path->empty(); } if (!current_directory.IsAbsolute()) return false; *file_path = current_directory.Append(*file_path); return true; } // Helper method to launch the platform app |extension| with no data. This // should be called in the fallback case, where it has been impossible to // load or obtain file launch data. void LaunchPlatformAppWithNoData(Profile* profile, const Extension* extension) { DCHECK_CURRENTLY_ON(BrowserThread::UI); AppEventRouter::DispatchOnLaunchedEvent(profile, extension); } // 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(Profile* profile, const Extension* extension, const std::vector<base::FilePath>& file_paths) : profile_(profile), extension_(extension), file_paths_(file_paths) {} PlatformAppPathLauncher(Profile* profile, const Extension* extension, const base::FilePath& file_path) : profile_(profile), extension_(extension) { if (!file_path.empty()) file_paths_.push_back(file_path); } void Launch() { DCHECK_CURRENTLY_ON(BrowserThread::UI); if (file_paths_.empty()) { LaunchPlatformAppWithNoData(profile_, extension_); return; } for (size_t i = 0; i < file_paths_.size(); ++i) { DCHECK(file_paths_[i].IsAbsolute()); } if (HasFileSystemWritePermission(extension_)) { PrepareFilesForWritableApp( file_paths_, profile_, false, base::Bind(&PlatformAppPathLauncher::OnFileValid, this), base::Bind(&PlatformAppPathLauncher::OnFileInvalid, this)); return; } OnFileValid(); } void LaunchWithHandler(const std::string& handler_id) { handler_id_ = handler_id; Launch(); } void LaunchWithRelativePath(const base::FilePath& current_directory) { BrowserThread::PostTask( BrowserThread::FILE, FROM_HERE, base::Bind(&PlatformAppPathLauncher::MakePathAbsolute, this, current_directory)); } private: friend class base::RefCountedThreadSafe<PlatformAppPathLauncher>; virtual ~PlatformAppPathLauncher() {} void MakePathAbsolute(const base::FilePath& current_directory) { DCHECK_CURRENTLY_ON(BrowserThread::FILE); for (std::vector<base::FilePath>::iterator it = file_paths_.begin(); it != file_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 OnFileValid() { mime_types_.resize(file_paths_.size()); #if defined(OS_CHROMEOS) GetNextNonNativeMimeType(); #else BrowserThread::PostTask( BrowserThread::FILE, FROM_HERE, base::Bind(&PlatformAppPathLauncher::GetMimeTypesAndLaunch, this)); #endif } void OnFileInvalid(const base::FilePath& /* error_path */) { LaunchWithNoLaunchData(); } #if defined(OS_CHROMEOS) void GetNextNonNativeMimeType() { DCHECK_CURRENTLY_ON(BrowserThread::UI); bool any_native_files = false; for (size_t i = 0; i < mime_types_.size(); ++i) { if (!mime_types_[i].empty()) continue; const base::FilePath& file_path = file_paths_[i]; if (file_manager::util::IsUnderNonNativeLocalPath(profile_, file_path)) { file_manager::util::GetNonNativeLocalPathMimeType( profile_, file_path, base::Bind(&PlatformAppPathLauncher::OnGotMimeType, this, i)); return; } any_native_files = true; } // If there are any native files, we need to call GetMimeTypesAndLaunch to // obtain mime types for the files. if (any_native_files) { BrowserThread::PostTask( BrowserThread::FILE, FROM_HERE, base::Bind(&PlatformAppPathLauncher::GetMimeTypesAndLaunch, this)); return; } // Otherwise, we can call LaunchWithMimeTypes directly. LaunchWithMimeTypes(); } void OnGotMimeType(size_t index, bool success, const std::string& mime_type) { if (!success) { LaunchWithNoLaunchData(); return; } mime_types_[index] = mime_type.empty() ? kFallbackMimeType : mime_type; GetNextNonNativeMimeType(); } #endif void GetMimeTypesAndLaunch() { DCHECK_CURRENTLY_ON(BrowserThread::FILE); for (size_t i = 0; i < mime_types_.size(); ++i) { if (!this->mime_types_[i].empty()) continue; const base::FilePath& file_path = file_paths_[i]; // If the file doesn't exist, or is a directory, launch with no launch // data. if (!base::PathExists(file_path) || base::DirectoryExists(file_path)) { LOG(WARNING) << "No file exists with path " << file_path.value(); BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(&PlatformAppPathLauncher::LaunchWithNoLaunchData, this)); return; } std::string mime_type; if (!net::GetMimeTypeFromFile(file_path, &mime_type)) { // If MIME type of the file can't be determined by its path, // try to sniff it by its content. std::vector<char> content(net::kMaxBytesToSniff); int bytes_read = base::ReadFile(file_path, &content[0], content.size()); if (bytes_read >= 0) { net::SniffMimeType(&content[0], bytes_read, net::FilePathToFileURL(file_path), std::string(), // type_hint (passes no hint) &mime_type); } if (mime_type.empty()) mime_type = kFallbackMimeType; } mime_types_[i] = mime_type; } BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(&PlatformAppPathLauncher::LaunchWithMimeTypes, this)); } void LaunchWithNoLaunchData() { // This method is required as an entry point on the UI thread. LaunchPlatformAppWithNoData(profile_, extension_); } void LaunchWithMimeTypes() { DCHECK(file_paths_.size() == mime_types_.size()); // Find file handler from the platform app for the file being opened. const extensions::FileHandlerInfo* handler = NULL; if (!handler_id_.empty()) { handler = FileHandlerForId(*extension_, handler_id_); if (handler) { for (size_t i = 0; i < file_paths_.size(); ++i) { if (!FileHandlerCanHandleFile( *handler, mime_types_[i], file_paths_[i])) { LOG(WARNING) << "Extension does not provide a valid file handler for " << file_paths_[i].value(); handler = NULL; break; } } } } else { std::set<std::pair<base::FilePath, std::string> > path_and_file_type_set; for (size_t i = 0; i < file_paths_.size(); ++i) { path_and_file_type_set.insert( std::make_pair(file_paths_[i], mime_types_[i])); } const std::vector<const extensions::FileHandlerInfo*>& handlers = extensions::app_file_handler_util::FindFileHandlersForFiles( *extension_, path_and_file_type_set); 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 = ExtensionSystem::Get(profile_)->lazy_background_task_queue(); if (queue->ShouldEnqueueTask(profile_, extension_)) { queue->AddPendingTask( profile_, extension_->id(), base::Bind(&PlatformAppPathLauncher::GrantAccessToFilesAndLaunch, this)); return; } extensions::ProcessManager* const process_manager = ExtensionSystem::Get(profile_)->process_manager(); ExtensionHost* const host = process_manager->GetBackgroundHostForExtension(extension_->id()); DCHECK(host); GrantAccessToFilesAndLaunch(host); } void GrantAccessToFilesAndLaunch(ExtensionHost* host) { // 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> file_entries; for (size_t i = 0; i < file_paths_.size(); ++i) { file_entries.push_back( CreateFileEntry(profile_, extension_, host->render_process_host()->GetID(), file_paths_[i], false)); } AppEventRouter::DispatchOnLaunchedEventWithFileEntries( profile_, extension_, handler_id_, mime_types_, file_entries); } // The profile the app should be run in. Profile* profile_; // The extension providing the app. // TODO(benwells): Hold onto the extension ID instead of a pointer as it // is possible the extension will be unloaded while we're doing our thing. // See http://crbug.com/372270 for details. const Extension* extension_; // The path to be passed through to the app. std::vector<base::FilePath> file_paths_; std::vector<std::string> mime_types_; // The ID of the file handler used to launch the app. std::string handler_id_; DISALLOW_COPY_AND_ASSIGN(PlatformAppPathLauncher); }; } // namespace void LaunchPlatformAppWithCommandLine(Profile* profile, const Extension* extension, const CommandLine& command_line, const base::FilePath& current_directory) { // 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(extension)) { bool in_kiosk_mode = false; #if defined(OS_CHROMEOS) chromeos::UserManager* user_manager = chromeos::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::ASCIIToWide(url::kAboutBlankURL)); #else base::CommandLine::StringType about_blank_url(url::kAboutBlankURL); #endif 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)) { LaunchPlatformAppWithNoData(profile, extension); return; } base::FilePath file_path(command_line.GetArgs()[0]); scoped_refptr<PlatformAppPathLauncher> launcher = new PlatformAppPathLauncher(profile, extension, file_path); launcher->LaunchWithRelativePath(current_directory); } void LaunchPlatformAppWithPath(Profile* profile, const Extension* extension, const base::FilePath& file_path) { scoped_refptr<PlatformAppPathLauncher> launcher = new PlatformAppPathLauncher(profile, extension, file_path); launcher->Launch(); } void LaunchPlatformApp(Profile* profile, const Extension* extension) { LaunchPlatformAppWithCommandLine(profile, extension, CommandLine(CommandLine::NO_PROGRAM), base::FilePath()); } void LaunchPlatformAppWithFileHandler( Profile* profile, const Extension* extension, const std::string& handler_id, const std::vector<base::FilePath>& file_paths) { scoped_refptr<PlatformAppPathLauncher> launcher = new PlatformAppPathLauncher(profile, extension, file_paths); launcher->LaunchWithHandler(handler_id); } void RestartPlatformApp(Profile* profile, const Extension* extension) { EventRouter* event_router = EventRouter::Get(profile); bool listening_to_restart = event_router-> ExtensionHasEventListener(extension->id(), app_runtime::OnRestarted::kEventName); if (listening_to_restart) { AppEventRouter::DispatchOnRestartedEvent(profile, extension); return; } extensions::ExtensionPrefs* extension_prefs = extensions::ExtensionPrefs::Get(profile); bool had_windows = extension_prefs->IsActive(extension->id()); extension_prefs->SetIsActive(extension->id(), false); bool listening_to_launch = event_router-> ExtensionHasEventListener(extension->id(), app_runtime::OnLaunched::kEventName); if (listening_to_launch && had_windows) LaunchPlatformAppWithNoData(profile, extension); } void LaunchPlatformAppWithUrl(Profile* profile, const Extension* extension, const std::string& handler_id, const GURL& url, const GURL& referrer_url) { AppEventRouter::DispatchOnLaunchedEventWithUrl( profile, extension, handler_id, url, referrer_url); } } // namespace apps