1 // Copyright (c) 2012 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 "chrome/browser/extensions/component_loader.h" 6 7 #include <map> 8 #include <string> 9 10 #include "base/command_line.h" 11 #include "base/file_util.h" 12 #include "base/json/json_string_value_serializer.h" 13 #include "base/metrics/field_trial.h" 14 #include "base/path_service.h" 15 #include "base/prefs/pref_change_registrar.h" 16 #include "chrome/browser/chrome_notification_types.h" 17 #include "chrome/browser/extensions/extension_service.h" 18 #include "chrome/browser/profiles/profile.h" 19 #include "chrome/common/chrome_paths.h" 20 #include "chrome/common/chrome_switches.h" 21 #include "chrome/common/extensions/extension.h" 22 #include "chrome/common/extensions/extension_file_util.h" 23 #include "chrome/common/extensions/extension_manifest_constants.h" 24 #include "chrome/common/pref_names.h" 25 #include "content/public/browser/notification_details.h" 26 #include "content/public/browser/notification_source.h" 27 #include "extensions/common/id_util.h" 28 #include "grit/browser_resources.h" 29 #include "grit/generated_resources.h" 30 #include "ui/base/l10n/l10n_util.h" 31 #include "ui/base/resource/resource_bundle.h" 32 33 #if defined(USE_AURA) 34 #include "grit/keyboard_resources.h" 35 #include "ui/keyboard/keyboard_util.h" 36 #endif 37 38 #if defined(GOOGLE_CHROME_BUILD) 39 #include "chrome/browser/defaults.h" 40 #endif 41 42 #if defined(OS_CHROMEOS) 43 #include "chrome/browser/chromeos/accessibility/accessibility_manager.h" 44 #include "chrome/browser/chromeos/login/user_manager.h" 45 #include "chrome/browser/extensions/extension_service.h" 46 #include "chrome/browser/extensions/extension_system.h" 47 #include "chrome/browser/profiles/profile.h" 48 #include "chrome/browser/profiles/profile_manager.h" 49 #include "chromeos/chromeos_switches.h" 50 #include "content/public/browser/storage_partition.h" 51 #include "webkit/browser/fileapi/file_system_context.h" 52 #include "webkit/browser/fileapi/sandbox_file_system_backend.h" 53 #endif 54 55 #if defined(ENABLE_APP_LIST) 56 #include "grit/chromium_strings.h" 57 #endif 58 59 namespace extensions { 60 61 namespace { 62 63 static bool enable_background_extensions_during_testing = false; 64 65 std::string LookupWebstoreName() { 66 const char kWebStoreNameFieldTrialName[] = "WebStoreName"; 67 const char kStoreControl[] = "StoreControl"; 68 const char kWebStore[] = "WebStore"; 69 const char kGetApps[] = "GetApps"; 70 const char kAddApps[] = "AddApps"; 71 const char kMoreApps[] = "MoreApps"; 72 73 typedef std::map<std::string, int> NameMap; 74 CR_DEFINE_STATIC_LOCAL(NameMap, names, ()); 75 if (names.empty()) { 76 names.insert(std::make_pair(kStoreControl, IDS_WEBSTORE_NAME_STORE)); 77 names.insert(std::make_pair(kWebStore, IDS_WEBSTORE_NAME_WEBSTORE)); 78 names.insert(std::make_pair(kGetApps, IDS_WEBSTORE_NAME_GET_APPS)); 79 names.insert(std::make_pair(kAddApps, IDS_WEBSTORE_NAME_ADD_APPS)); 80 names.insert(std::make_pair(kMoreApps, IDS_WEBSTORE_NAME_MORE_APPS)); 81 } 82 std::string field_trial_name = 83 base::FieldTrialList::FindFullName(kWebStoreNameFieldTrialName); 84 NameMap::iterator it = names.find(field_trial_name); 85 int string_id = it == names.end() ? names[kStoreControl] : it->second; 86 return l10n_util::GetStringUTF8(string_id); 87 } 88 89 std::string GenerateId(const DictionaryValue* manifest, 90 const base::FilePath& path) { 91 std::string raw_key; 92 std::string id_input; 93 CHECK(manifest->GetString(extension_manifest_keys::kPublicKey, &raw_key)); 94 CHECK(Extension::ParsePEMKeyBytes(raw_key, &id_input)); 95 std::string id = id_util::GenerateId(id_input); 96 return id; 97 } 98 99 } // namespace 100 101 ComponentLoader::ComponentExtensionInfo::ComponentExtensionInfo( 102 const DictionaryValue* manifest, const base::FilePath& directory) 103 : manifest(manifest), 104 root_directory(directory) { 105 if (!root_directory.IsAbsolute()) { 106 CHECK(PathService::Get(chrome::DIR_RESOURCES, &root_directory)); 107 root_directory = root_directory.Append(directory); 108 } 109 extension_id = GenerateId(manifest, root_directory); 110 } 111 112 ComponentLoader::ComponentLoader(ExtensionServiceInterface* extension_service, 113 PrefService* profile_prefs, 114 PrefService* local_state) 115 : profile_prefs_(profile_prefs), 116 local_state_(local_state), 117 extension_service_(extension_service) {} 118 119 ComponentLoader::~ComponentLoader() { 120 ClearAllRegistered(); 121 } 122 123 void ComponentLoader::LoadAll() { 124 for (RegisteredComponentExtensions::iterator it = 125 component_extensions_.begin(); 126 it != component_extensions_.end(); ++it) { 127 Load(*it); 128 } 129 } 130 131 DictionaryValue* ComponentLoader::ParseManifest( 132 const std::string& manifest_contents) const { 133 JSONStringValueSerializer serializer(manifest_contents); 134 scoped_ptr<Value> manifest(serializer.Deserialize(NULL, NULL)); 135 136 if (!manifest.get() || !manifest->IsType(Value::TYPE_DICTIONARY)) { 137 LOG(ERROR) << "Failed to parse extension manifest."; 138 return NULL; 139 } 140 // Transfer ownership to the caller. 141 return static_cast<DictionaryValue*>(manifest.release()); 142 } 143 144 void ComponentLoader::ClearAllRegistered() { 145 for (RegisteredComponentExtensions::iterator it = 146 component_extensions_.begin(); 147 it != component_extensions_.end(); ++it) { 148 delete it->manifest; 149 } 150 151 component_extensions_.clear(); 152 } 153 154 std::string ComponentLoader::Add(int manifest_resource_id, 155 const base::FilePath& root_directory) { 156 std::string manifest_contents = 157 ResourceBundle::GetSharedInstance().GetRawDataResource( 158 manifest_resource_id).as_string(); 159 return Add(manifest_contents, root_directory); 160 } 161 162 std::string ComponentLoader::Add(const std::string& manifest_contents, 163 const base::FilePath& root_directory) { 164 // The Value is kept for the lifetime of the ComponentLoader. This is 165 // required in case LoadAll() is called again. 166 DictionaryValue* manifest = ParseManifest(manifest_contents); 167 if (manifest) 168 return Add(manifest, root_directory); 169 return std::string(); 170 } 171 172 std::string ComponentLoader::Add(const DictionaryValue* parsed_manifest, 173 const base::FilePath& root_directory) { 174 ComponentExtensionInfo info(parsed_manifest, root_directory); 175 component_extensions_.push_back(info); 176 if (extension_service_->is_ready()) 177 Load(info); 178 return info.extension_id; 179 } 180 181 std::string ComponentLoader::AddOrReplace(const base::FilePath& path) { 182 base::FilePath absolute_path = base::MakeAbsoluteFilePath(path); 183 std::string error; 184 scoped_ptr<DictionaryValue> manifest( 185 extension_file_util::LoadManifest(absolute_path, &error)); 186 if (!manifest) { 187 LOG(ERROR) << "Could not load extension from '" << 188 absolute_path.value() << "'. " << error; 189 return std::string(); 190 } 191 Remove(GenerateId(manifest.get(), absolute_path)); 192 193 return Add(manifest.release(), absolute_path); 194 } 195 196 void ComponentLoader::Reload(const std::string& extension_id) { 197 for (RegisteredComponentExtensions::iterator it = 198 component_extensions_.begin(); it != component_extensions_.end(); 199 ++it) { 200 if (it->extension_id == extension_id) { 201 Load(*it); 202 break; 203 } 204 } 205 } 206 207 void ComponentLoader::Load(const ComponentExtensionInfo& info) { 208 // TODO(abarth): We should REQUIRE_MODERN_MANIFEST_VERSION once we've updated 209 // our component extensions to the new manifest version. 210 int flags = Extension::REQUIRE_KEY; 211 212 std::string error; 213 214 scoped_refptr<const Extension> extension(Extension::Create( 215 info.root_directory, 216 Manifest::COMPONENT, 217 *info.manifest, 218 flags, 219 &error)); 220 if (!extension.get()) { 221 LOG(ERROR) << error; 222 return; 223 } 224 225 CHECK_EQ(info.extension_id, extension->id()) << extension->name(); 226 extension_service_->AddComponentExtension(extension.get()); 227 } 228 229 void ComponentLoader::RemoveAll() { 230 RegisteredComponentExtensions::iterator it = component_extensions_.begin(); 231 for (; it != component_extensions_.end(); ++it) 232 UnloadComponent(&(*it)); 233 234 component_extensions_.clear(); 235 } 236 237 void ComponentLoader::Remove(const base::FilePath& root_directory) { 238 // Find the ComponentExtensionInfo for the extension. 239 RegisteredComponentExtensions::iterator it = component_extensions_.begin(); 240 for (; it != component_extensions_.end(); ++it) { 241 if (it->root_directory == root_directory) { 242 Remove(GenerateId(it->manifest, root_directory)); 243 break; 244 } 245 } 246 } 247 248 void ComponentLoader::Remove(const std::string& id) { 249 RegisteredComponentExtensions::iterator it = component_extensions_.begin(); 250 for (; it != component_extensions_.end(); ++it) { 251 if (it->extension_id == id) { 252 UnloadComponent(&(*it)); 253 it = component_extensions_.erase(it); 254 break; 255 } 256 } 257 } 258 259 bool ComponentLoader::Exists(const std::string& id) const { 260 RegisteredComponentExtensions::const_iterator it = 261 component_extensions_.begin(); 262 for (; it != component_extensions_.end(); ++it) 263 if (it->extension_id == id) 264 return true; 265 return false; 266 } 267 268 void ComponentLoader::AddFileManagerExtension() { 269 #if defined(FILE_MANAGER_EXTENSION) 270 #ifndef NDEBUG 271 const CommandLine* command_line = CommandLine::ForCurrentProcess(); 272 if (command_line->HasSwitch(switches::kFileManagerExtensionPath)) { 273 base::FilePath filemgr_extension_path( 274 command_line->GetSwitchValuePath(switches::kFileManagerExtensionPath)); 275 Add(IDR_FILEMANAGER_MANIFEST, filemgr_extension_path); 276 return; 277 } 278 #endif // NDEBUG 279 Add(IDR_FILEMANAGER_MANIFEST, 280 base::FilePath(FILE_PATH_LITERAL("file_manager"))); 281 #endif // defined(FILE_MANAGER_EXTENSION) 282 } 283 284 void ComponentLoader::AddImageLoaderExtension() { 285 #if defined(IMAGE_LOADER_EXTENSION) 286 #ifndef NDEBUG 287 const CommandLine* command_line = CommandLine::ForCurrentProcess(); 288 if (command_line->HasSwitch(switches::kImageLoaderExtensionPath)) { 289 base::FilePath image_loader_extension_path( 290 command_line->GetSwitchValuePath(switches::kImageLoaderExtensionPath)); 291 Add(IDR_IMAGE_LOADER_MANIFEST, image_loader_extension_path); 292 return; 293 } 294 #endif // NDEBUG 295 Add(IDR_IMAGE_LOADER_MANIFEST, 296 base::FilePath(FILE_PATH_LITERAL("image_loader"))); 297 #endif // defined(IMAGE_LOADER_EXTENSION) 298 } 299 300 void ComponentLoader::AddWithName(int manifest_resource_id, 301 const base::FilePath& root_directory, 302 const std::string& name) { 303 std::string manifest_contents = 304 ResourceBundle::GetSharedInstance().GetRawDataResource( 305 manifest_resource_id).as_string(); 306 307 // The Value is kept for the lifetime of the ComponentLoader. This is 308 // required in case LoadAll() is called again. 309 DictionaryValue* manifest = ParseManifest(manifest_contents); 310 311 if (manifest) { 312 // Update manifest to use a proper name. 313 manifest->SetString(extension_manifest_keys::kName, name); 314 Add(manifest, root_directory); 315 } 316 } 317 318 void ComponentLoader::AddChromeApp() { 319 #if defined(ENABLE_APP_LIST) 320 AddWithName(IDR_CHROME_APP_MANIFEST, 321 base::FilePath(FILE_PATH_LITERAL("chrome_app")), 322 l10n_util::GetStringUTF8(IDS_SHORT_PRODUCT_NAME)); 323 #endif 324 } 325 326 void ComponentLoader::AddKeyboardApp() { 327 #if defined(USE_AURA) 328 if (keyboard::IsKeyboardEnabled()) 329 Add(IDR_KEYBOARD_MANIFEST, base::FilePath(FILE_PATH_LITERAL("keyboard"))); 330 #endif 331 } 332 333 void ComponentLoader::AddWebStoreApp() { 334 AddWithName(IDR_WEBSTORE_MANIFEST, 335 base::FilePath(FILE_PATH_LITERAL("web_store")), 336 LookupWebstoreName()); 337 } 338 339 // static 340 void ComponentLoader::EnableBackgroundExtensionsForTesting() { 341 enable_background_extensions_during_testing = true; 342 } 343 344 void ComponentLoader::AddDefaultComponentExtensions( 345 bool skip_session_components) { 346 // Do not add component extensions that have background pages here -- add them 347 // to AddDefaultComponentExtensionsWithBackgroundPages. 348 #if defined(OS_CHROMEOS) 349 Add(IDR_MOBILE_MANIFEST, 350 base::FilePath(FILE_PATH_LITERAL("/usr/share/chromeos-assets/mobile"))); 351 352 #if defined(GOOGLE_CHROME_BUILD) 353 if (browser_defaults::enable_help_app) { 354 Add(IDR_HELP_MANIFEST, base::FilePath(FILE_PATH_LITERAL( 355 "/usr/share/chromeos-assets/helpapp"))); 356 } 357 #endif 358 359 // Skip all other extensions that require user session presence. 360 if (!skip_session_components) { 361 const CommandLine* command_line = CommandLine::ForCurrentProcess(); 362 if (!command_line->HasSwitch(chromeos::switches::kGuestSession)) 363 Add(IDR_BOOKMARKS_MANIFEST, 364 base::FilePath(FILE_PATH_LITERAL("bookmark_manager"))); 365 366 Add(IDR_CROSH_BUILTIN_MANIFEST, base::FilePath(FILE_PATH_LITERAL( 367 "/usr/share/chromeos-assets/crosh_builtin"))); 368 } 369 #else // !defined(OS_CHROMEOS) 370 DCHECK(!skip_session_components); 371 Add(IDR_BOOKMARKS_MANIFEST, 372 base::FilePath(FILE_PATH_LITERAL("bookmark_manager"))); 373 // Cloud Print component app. Not required on Chrome OS. 374 Add(IDR_CLOUDPRINT_MANIFEST, 375 base::FilePath(FILE_PATH_LITERAL("cloud_print"))); 376 #endif 377 378 if (!skip_session_components) { 379 AddWebStoreApp(); 380 AddChromeApp(); 381 } 382 383 AddKeyboardApp(); 384 385 AddDefaultComponentExtensionsWithBackgroundPages(skip_session_components); 386 } 387 388 void ComponentLoader::AddDefaultComponentExtensionsWithBackgroundPages( 389 bool skip_session_components) { 390 const CommandLine* command_line = CommandLine::ForCurrentProcess(); 391 392 // Component extensions with background pages are not enabled during tests 393 // because they generate a lot of background behavior that can interfere. 394 if (!enable_background_extensions_during_testing && 395 (command_line->HasSwitch(switches::kTestType) || 396 command_line->HasSwitch(switches::kMetricsRecordingOnly))) { 397 return; 398 } 399 400 if (!skip_session_components) { 401 // Apps Debugger 402 if (CommandLine::ForCurrentProcess()->HasSwitch( 403 switches::kAppsDevtool)) { 404 Add(IDR_APPS_DEBUGGER_MANIFEST, 405 base::FilePath(FILE_PATH_LITERAL("apps_debugger"))); 406 } 407 408 AddFileManagerExtension(); 409 AddImageLoaderExtension(); 410 411 #if defined(ENABLE_SETTINGS_APP) 412 Add(IDR_SETTINGS_APP_MANIFEST, 413 base::FilePath(FILE_PATH_LITERAL("settings_app"))); 414 #endif 415 } 416 417 #if defined(OS_CHROMEOS) 418 if (!skip_session_components) { 419 Add(IDR_WALLPAPERMANAGER_MANIFEST, 420 base::FilePath(FILE_PATH_LITERAL("chromeos/wallpaper_manager"))); 421 422 #if defined(GOOGLE_CHROME_BUILD) 423 if (!command_line->HasSwitch( 424 chromeos::switches::kDisableQuickofficeComponentApp)) { 425 int manifest_id = IDR_QUICKOFFICE_EDITOR_MANIFEST; 426 if (command_line->HasSwitch(switches::kEnableQuickofficeViewing)) { 427 manifest_id = IDR_QUICKOFFICE_VIEWING_MANIFEST; 428 } 429 std::string id = Add(manifest_id, base::FilePath( 430 FILE_PATH_LITERAL("/usr/share/chromeos-assets/quick_office"))); 431 if (command_line->HasSwitch(chromeos::switches::kGuestSession)) { 432 // TODO(dpolukhin): Hack to enable HTML5 temporary file system for 433 // Quickoffice. It doesn't work without temporary file system access. 434 Profile* profile = ProfileManager::GetDefaultProfileOrOffTheRecord(); 435 ExtensionService* service = 436 extensions::ExtensionSystem::Get(profile)->extension_service(); 437 GURL site = service->GetSiteForExtensionId(id); 438 fileapi::FileSystemContext* context = 439 content::BrowserContext::GetStoragePartitionForSite(profile, site)-> 440 GetFileSystemContext(); 441 context->EnableTemporaryFileSystemInIncognito(); 442 } 443 } 444 #endif // defined(GOOGLE_CHROME_BUILD) 445 446 base::FilePath echo_extension_path(FILE_PATH_LITERAL( 447 "/usr/share/chromeos-assets/echo")); 448 if (command_line->HasSwitch(chromeos::switches::kEchoExtensionPath)) { 449 echo_extension_path = command_line->GetSwitchValuePath( 450 chromeos::switches::kEchoExtensionPath); 451 } 452 Add(IDR_ECHO_MANIFEST, echo_extension_path); 453 454 Add(IDR_NETWORK_CONFIGURATION_MANIFEST, 455 base::FilePath(FILE_PATH_LITERAL("chromeos/network_configuration"))); 456 457 Add(IDR_CONNECTIVITY_DIAGNOSTICS_MANIFEST, 458 base::FilePath(extension_misc::kConnectivityDiagnosticsPath)); 459 Add(IDR_CONNECTIVITY_DIAGNOSTICS_LAUNCHER_MANIFEST, 460 base::FilePath(extension_misc::kConnectivityDiagnosticsLauncherPath)); 461 } 462 463 // Load ChromeVox extension now if spoken feedback is enabled. 464 if (chromeos::AccessibilityManager::Get() && 465 chromeos::AccessibilityManager::Get()->IsSpokenFeedbackEnabled()) { 466 base::FilePath path = 467 base::FilePath(extension_misc::kChromeVoxExtensionPath); 468 Add(IDR_CHROMEVOX_MANIFEST, path); 469 } 470 #endif // defined(OS_CHROMEOS) 471 472 #if defined(ENABLE_GOOGLE_NOW) 473 if (base::FieldTrialList::FindFullName("GoogleNow") == "Enable" || 474 CommandLine::ForCurrentProcess()->HasSwitch( 475 switches::kEnableGoogleNowIntegration)) { 476 Add(IDR_GOOGLE_NOW_MANIFEST, 477 base::FilePath(FILE_PATH_LITERAL("google_now"))); 478 } 479 #endif 480 } 481 482 void ComponentLoader::UnloadComponent(ComponentExtensionInfo* component) { 483 delete component->manifest; 484 if (extension_service_->is_ready()) { 485 extension_service_-> 486 UnloadExtension(component->extension_id, 487 extension_misc::UNLOAD_REASON_DISABLE); 488 } 489 } 490 491 } // namespace extensions 492