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