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