diff options
author | finnur@chromium.org <finnur@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-06-05 21:53:19 +0000 |
---|---|---|
committer | finnur@chromium.org <finnur@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-06-05 21:53:19 +0000 |
commit | 25b3433897bab5e94dac9ab8a2f09b356792ab7d (patch) | |
tree | 50addd81b87cf947d52b489af4edeae4614013bc /chrome | |
parent | c333ede219e0e33b5df85f02bb9d261d7033c9c1 (diff) | |
download | chromium_src-25b3433897bab5e94dac9ab8a2f09b356792ab7d.zip chromium_src-25b3433897bab5e94dac9ab8a2f09b356792ab7d.tar.gz chromium_src-25b3433897bab5e94dac9ab8a2f09b356792ab7d.tar.bz2 |
Add this ability to install Extensions using preferences. Also known as: port the installation mechanism to other platforms.
We already have the ability to install extensions using a registry key. That works only on Windows so this new change adds the same but using preferences instead of the Registry. This will eventually allow us to pre-install certain extensions when we install Chrome.
BUG=12060
TEST=Covered by unit tests, but to test manually: close Chrome, open your Preferences file (in your profile) and add this (after substituting all <values> in elbow brackets):
"extensions": {
"settings": {
"<your_extension_id_lowercased>": {
"external_crx": "<path_to_crx>",
"external_version": "<crx version>"
}
},
},
... then start Chrome. Your extension should get installed.
Review URL: http://codereview.chromium.org/119195
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@17777 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
-rw-r--r-- | chrome/browser/extensions/extensions_service.cc | 308 | ||||
-rw-r--r-- | chrome/browser/extensions/extensions_service.h | 45 | ||||
-rw-r--r-- | chrome/browser/extensions/extensions_service_unittest.cc | 289 | ||||
-rw-r--r-- | chrome/common/extensions/extension.cc | 46 | ||||
-rw-r--r-- | chrome/common/extensions/extension.h | 34 | ||||
-rw-r--r-- | chrome/common/extensions/extension_unpacker.cc | 13 |
6 files changed, 626 insertions, 109 deletions
diff --git a/chrome/browser/extensions/extensions_service.cc b/chrome/browser/extensions/extensions_service.cc index 87c14a2..2d8b039 100644 --- a/chrome/browser/extensions/extensions_service.cc +++ b/chrome/browser/extensions/extensions_service.cc @@ -30,6 +30,7 @@ #include "chrome/common/extensions/extension_unpacker.h" #include "chrome/common/json_value_serializer.h" #include "chrome/common/notification_service.h" +#include "chrome/common/pref_names.h" #include "chrome/common/pref_service.h" #include "chrome/common/zip.h" #include "chrome/common/url_constants.h" @@ -43,7 +44,7 @@ #include "base/win_util.h" #endif -// ExtensionsService +// ExtensionsService. const char* ExtensionsService::kInstallDirectoryName = "Extensions"; const char* ExtensionsService::kCurrentVersionFileName = "Current Version"; @@ -63,10 +64,13 @@ struct ExtensionHeader { const size_t kZipHashBytes = 32; // SHA-256 const size_t kZipHashHexBytes = kZipHashBytes * 2; // Hex string is 2x size. -// A preference that keeps track of external extensions the user has -// uninstalled. -const wchar_t kUninstalledExternalPref[] = - L"extensions.uninstalled_external_ids"; +// A preference that keeps track of extension settings. This is a dictionary +// object read from the Preferences file, keyed off of extension id's. +const wchar_t kExternalExtensionsPref[] = L"extensions.settings"; + +// A preference keeping track of how the extension was installed. +const wchar_t kLocation[] = L"location"; +const wchar_t kState[] = L"state"; // Registry key where registry defined extension installers live. // TODO(port): Assuming this becomes a similar key into the appropriate @@ -226,7 +230,7 @@ ExtensionsService::ExtensionsService(Profile* profile, backend_(new ExtensionsServiceBackend( install_directory_, g_browser_process->resource_dispatcher_host(), frontend_loop, registry_path)) { - prefs_->RegisterListPref(kUninstalledExternalPref); + prefs_->RegisterDictionaryPref(kExternalExtensionsPref); } ExtensionsService::~ExtensionsService() { @@ -240,34 +244,23 @@ bool ExtensionsService::Init() { // Start up the extension event routers. ExtensionBrowserEventRouter::GetInstance()->Init(); -#if defined(OS_WIN) + scoped_ptr<DictionaryValue> external_extensions(new DictionaryValue); + GetExternalExtensions(external_extensions.get(), NULL); - std::set<std::string> uninstalled_external_ids; - const ListValue* list = prefs_->GetList(kUninstalledExternalPref); - if (list) { - for (size_t i = 0; i < list->GetSize(); ++i) { - std::string val; - bool ok = list->GetString(i, &val); - DCHECK(ok); - DCHECK(uninstalled_external_ids.find(val) == - uninstalled_external_ids.end()); - uninstalled_external_ids.insert(val); - } - } + scoped_ptr< std::set<std::string> > + killed_extensions(new std::set<std::string>); + GetExternalExtensions(NULL, killed_extensions.get()); - // TODO(erikkay): Should we monitor the registry during run as well? backend_loop_->PostTask(FROM_HERE, NewRunnableMethod(backend_.get(), &ExtensionsServiceBackend::CheckForExternalUpdates, - uninstalled_external_ids, scoped_refptr<ExtensionsService>(this))); -#else - - // TODO(port) - -#endif + *killed_extensions.get(), + external_extensions.get(), + scoped_refptr<ExtensionsService>(this))); backend_loop_->PostTask(FROM_HERE, NewRunnableMethod(backend_.get(), &ExtensionsServiceBackend::LoadExtensionsFromInstallDirectory, - scoped_refptr<ExtensionsService>(this))); + scoped_refptr<ExtensionsService>(this), + external_extensions.release())); return true; } @@ -303,15 +296,17 @@ void ExtensionsService::UninstallExtension(const std::string& extension_id) { // For external extensions, we save a preference reminding ourself not to try // and install the extension anymore. - if (extension->location() == Extension::EXTERNAL) { - ListValue* list = prefs_->GetMutableList(kUninstalledExternalPref); - list->Append(Value::CreateStringValue(extension->id())); - prefs_->ScheduleSavePersistentPrefs(); + if (Extension::IsExternalLocation(extension->location())) { + UpdateExtensionPref(ASCIIToWide(extension->id()), kState, + Value::CreateIntegerValue(Extension::KILLBIT), true); + } else { + UpdateExtensionPref(ASCIIToWide(extension->id()), kState, + Value::CreateIntegerValue(Extension::DISABLED), true); } // Tell the backend to start deleting installed extensions on the file thread. if (extension->location() == Extension::INTERNAL || - extension->location() == Extension::EXTERNAL) { + Extension::IsExternalLocation(extension->location())) { backend_loop_->PostTask(FROM_HERE, NewRunnableMethod(backend_.get(), &ExtensionsServiceBackend::UninstallExtension, extension_id)); } @@ -326,6 +321,32 @@ void ExtensionsService::LoadExtension(const FilePath& extension_path) { } void ExtensionsService::OnExtensionsLoaded(ExtensionList* new_extensions) { + // Sync with manually loaded extensions. Otherwise we won't know about them + // since they aren't installed in the normal way. Eventually, we want to not + // load extensions at all from directory, but use the Extension preferences + // as the truth for what is installed. + DictionaryValue* pref = NULL; + for (ExtensionList::const_iterator iter = new_extensions->begin(); + iter != new_extensions->end(); ++iter) { + std::wstring extension_id = ASCIIToWide((*iter)->id()); + pref = GetOrCreateExtensionPref(extension_id); + Extension::Location location; + Extension::State state; + if (!pref->GetInteger(kLocation, reinterpret_cast<int*>(&location)) || + !pref->GetInteger(kState, reinterpret_cast<int*>(&state))) { + UpdateExtensionPref(extension_id, + kLocation, Value::CreateIntegerValue(Extension::INTERNAL), false); + UpdateExtensionPref(extension_id, + kState, Value::CreateIntegerValue(Extension::ENABLED), false); + } else { + // The kill-bit only applies to External extensions so this check fails + // for internal locations that have the kill-bit set. In other words, + // the kill-bit cannot be set unless the extension is external. + DCHECK(state != Extension::KILLBIT || + Extension::IsExternalLocation(location)); + } + } + // If extensions aren't enabled, we still want to add themes. However, themes // should not trigger EXTENSIONS_LOADED. // TODO(aa): This can be re-enabled when BUG 13128 is fixed. @@ -350,6 +371,11 @@ void ExtensionsService::OnExtensionsLoaded(ExtensionList* new_extensions) { void ExtensionsService::OnExtensionInstalled(Extension* extension, bool update) { + UpdateExtensionPref(ASCIIToWide(extension->id()), kState, + Value::CreateIntegerValue(Extension::ENABLED), false); + UpdateExtensionPref(ASCIIToWide(extension->id()), kLocation, + Value::CreateIntegerValue(Extension::INTERNAL), true); + // If the extension is a theme, tell the profile (and therefore ThemeProvider) // to apply it. if (extension->IsTheme()) { @@ -365,6 +391,15 @@ void ExtensionsService::OnExtensionInstalled(Extension* extension, } } +void ExtensionsService::OnExternalExtensionInstalled( + const std::string& id, Extension::Location location) { + DCHECK(Extension::IsExternalLocation(location)); + UpdateExtensionPref(ASCIIToWide(id), kState, + Value::CreateIntegerValue(Extension::ENABLED), false); + UpdateExtensionPref(ASCIIToWide(id), kLocation, + Value::CreateIntegerValue(location), true); +} + void ExtensionsService::OnExtensionVersionReinstalled(const std::string& id) { Extension* extension = GetExtensionByID(id); if (extension && extension->IsTheme()) { @@ -384,6 +419,70 @@ Extension* ExtensionsService::GetExtensionByID(std::string id) { return NULL; } +void ExtensionsService::GetExternalExtensions( + DictionaryValue* external_extensions, + std::set<std::string>* killed_extensions) { + const DictionaryValue* dict = prefs_->GetDictionary(kExternalExtensionsPref); + if (!dict || dict->GetSize() == 0) + return; + + for (DictionaryValue::key_iterator i = dict->begin_keys(); + i != dict->end_keys(); ++i) { + std::wstring key_name = *i; + DCHECK(Extension::IdIsValid(WideToASCII(key_name))); + DictionaryValue* extension = NULL; + if (!dict->GetDictionary(key_name, &extension)) { + NOTREACHED(); + continue; + } + + // Check to see if the extension has been killed. + Extension::State state; + if (extension->GetInteger(kState, reinterpret_cast<int*>(&state)) && + state == static_cast<int>(Extension::KILLBIT)) { + if (killed_extensions) { + StringToLowerASCII(&key_name); + killed_extensions->insert(WideToASCII(key_name)); + } + } + // Return all extensions found. + if (external_extensions) { + DictionaryValue* result = + static_cast<DictionaryValue*>(extension->DeepCopy()); + StringToLowerASCII(&key_name); + external_extensions->Set(key_name, result); + } + } +} + +DictionaryValue* ExtensionsService::GetOrCreateExtensionPref( + const std::wstring& extension_id) { + DictionaryValue* dict = prefs_->GetMutableDictionary(kExternalExtensionsPref); + DictionaryValue* extension = NULL; + if (!dict->GetDictionary(extension_id, &extension)) { + // Extension pref does not exist, create it. + extension = new DictionaryValue(); + dict->Set(extension_id, extension); + } + + return extension; +} + +bool ExtensionsService::UpdateExtensionPref(const std::wstring& extension_id, + const std::wstring& key, + Value* data_value, + bool schedule_save) { + DictionaryValue* extension = GetOrCreateExtensionPref(extension_id); + if (!extension->Set(key, data_value)) { + NOTREACHED() << L"Cannot modify key: '" << key.c_str() + << "' for extension: '" << extension_id.c_str() << "'"; + return false; + } + + if (schedule_save) + prefs_->ScheduleSavePersistentPrefs(); + return true; +} // ExtensionsServicesBackend @@ -403,9 +502,11 @@ ExtensionsServiceBackend::ExtensionsServiceBackend( } void ExtensionsServiceBackend::LoadExtensionsFromInstallDirectory( - scoped_refptr<ExtensionsService> frontend) { + scoped_refptr<ExtensionsService> frontend, + DictionaryValue* extension_prefs) { frontend_ = frontend; alert_on_error_ = false; + scoped_ptr<DictionaryValue> external_extensions(extension_prefs); #if defined(OS_WIN) // On POSIX, AbsolutePath() calls realpath() which returns NULL if @@ -457,7 +558,7 @@ void ExtensionsServiceBackend::LoadExtensionsFromInstallDirectory( if (!file_util::PathExists(current_version_path)) { LOG(INFO) << "Deleting incomplete install for directory " << WideToASCII(extension_path.ToWStringHack()) << "."; - file_util::Delete(extension_path, true); // recursive; + file_util::Delete(extension_path, true); // Recursive. continue; } @@ -465,8 +566,17 @@ void ExtensionsServiceBackend::LoadExtensionsFromInstallDirectory( if (!ReadCurrentVersion(extension_path, ¤t_version)) continue; + Extension::Location location; + DictionaryValue* pref = NULL; + external_extensions->GetDictionary(ASCIIToWide(extension_id), &pref); + if (!pref || + !pref->GetInteger(kLocation, reinterpret_cast<int*>(&location))) { + location = Extension::INTERNAL; + } FilePath version_path = extension_path.AppendASCII(current_version); - if (CheckExternalUninstall(version_path, extension_id)) { + if (Extension::IsExternalLocation(location) && + CheckExternalUninstall(external_extensions.get(), + version_path, extension_id)) { // TODO(erikkay): Possibly defer this operation to avoid slowing initial // load of extensions. UninstallExtension(extension_id); @@ -539,10 +649,12 @@ Extension* ExtensionsServiceBackend::LoadExtension( } FilePath external_marker = extension_path.AppendASCII(kExternalInstallFile); - if (file_util::PathExists(external_marker)) - extension->set_location(Extension::EXTERNAL); - else + if (file_util::PathExists(external_marker)) { + extension->set_location( + extension->ExternalExtensionInstallType(registry_path_)); + } else { extension->set_location(Extension::INTERNAL); + } // Theme resource validation. if (extension->IsTheme()) { @@ -550,7 +662,7 @@ Extension* ExtensionsServiceBackend::LoadExtension( DictionaryValue::key_iterator iter = images_value->begin_keys(); while (iter != images_value->end_keys()) { std::string val; - if (images_value->GetString(*iter, &val)) { + if (images_value->GetString(*iter , &val)) { FilePath image_path = extension->path().AppendASCII(val); if (!file_util::PathExists(image_path)) { ReportExtensionLoadError(extension_path, @@ -810,8 +922,12 @@ void ExtensionsServiceBackend::OnExtensionUnpacked( // If an expected id was provided, make sure it matches. if (!expected_id.empty() && expected_id != extension.id()) { - ReportExtensionInstallError(extension_path, - "ID in new extension manifest does not match expected ID."); + std::string error_msg = "ID in new extension manifest ("; + error_msg += extension.id(); + error_msg += ") does not match expected ID ("; + error_msg += expected_id; + error_msg += ")"; + ReportExtensionInstallError(extension_path, error_msg); return; } @@ -894,8 +1010,9 @@ void ExtensionsServiceBackend::OnExtensionUnpacked( // Load the extension immediately and then report installation success. We // don't load extensions for external installs because external installation // occurs before the normal startup so we just let startup pick them up. We - // don't notify installation because there is no UI for external install so - // there is nobody to notify. + // notify on installation of external extensions because we need to update + // the preferences for these extensions to reflect that they've just been + // installed. if (!from_external) { Extension* extension = LoadExtension(version_dir, true); // require id CHECK(extension); @@ -911,6 +1028,11 @@ void ExtensionsServiceBackend::OnExtensionUnpacked( LOG(INFO) << "Done."; // Hand off ownership of the loaded extensions to the frontend. ReportExtensionsLoaded(extensions.release()); + } else { + frontend_loop_->PostTask(FROM_HERE, NewRunnableMethod( + frontend_, &ExtensionsService::OnExternalExtensionInstalled, + extension.id(), + extension.ExternalExtensionInstallType(registry_path_))); } scoped_version_dir.Take(); @@ -933,16 +1055,33 @@ void ExtensionsServiceBackend::ReportExtensionVersionReinstalled( frontend_, &ExtensionsService::OnExtensionVersionReinstalled, id)); } +bool ExtensionsServiceBackend::ShouldSkipInstallingExtension( + const std::set<std::string>& ids_to_ignore, + const std::string& id) { + if (ids_to_ignore.find(id) != ids_to_ignore.end()) { + LOG(INFO) << "Skipping uninstalled external extension " << id; + return true; + } + return false; +} + +void ExtensionsServiceBackend::CheckVersionAndInstallExtension( + const std::string& id, const std::string& extension_version, + const FilePath& extension_path, bool from_external) { + if (ShouldInstall(id, extension_version)) + InstallOrUpdateExtension(FilePath(extension_path), id, from_external); +} + // Some extensions will autoupdate themselves externally from Chrome. These // are typically part of some larger client application package. To support -// these, the extension will register its location in the registry on Windows -// (TODO(port): what about on other platforms?) and this code will periodically +// these, the extension will register its location in the the preferences file +// (and also, on Windows, in the registry) and this code will periodically // check that location for a .crx file, which it will then install locally if // a new version is available. void ExtensionsServiceBackend::CheckForExternalUpdates( std::set<std::string> ids_to_ignore, + DictionaryValue* extension_prefs, scoped_refptr<ExtensionsService> frontend) { - // Note that this installation is intentionally silent (since it didn't // go through the front-end). Extensions that are registered in this // way are effectively considered 'pre-bundled', and so implicitly @@ -951,6 +1090,44 @@ void ExtensionsServiceBackend::CheckForExternalUpdates( alert_on_error_ = false; frontend_ = frontend; + for (DictionaryValue::key_iterator i = extension_prefs->begin_keys(); + i != extension_prefs->end_keys(); ++i) { + const std::wstring& extension_id = *i; + if (ShouldSkipInstallingExtension(ids_to_ignore, WideToASCII(extension_id))) + continue; + + DictionaryValue* extension = NULL; + if (!extension_prefs->GetDictionary(extension_id, &extension)) { + NOTREACHED() << "Cannot read extension " << extension_id.c_str() + << " from dictionary."; + continue; + } + + Extension::Location location; + if (extension->GetInteger(kLocation, reinterpret_cast<int*>(&location)) && + location != Extension::EXTERNAL_PREF) { + continue; + } + Extension::State state; + if (extension->GetInteger(kState, reinterpret_cast<int*>(&state)) && + state == Extension::KILLBIT) { + continue; + } + + FilePath::StringType external_crx; + std::string external_version; + if (!extension->GetString(L"external_crx", &external_crx) || + !extension->GetString(L"external_version", &external_version)) { + LOG(WARNING) << "Malformed extension dictionary for extension: " + << extension_id.c_str(); + continue; + } + + bool from_external = true; + CheckVersionAndInstallExtension(WideToASCII(extension_id), external_version, + FilePath(external_crx), from_external); + } + #if defined(OS_WIN) // TODO(port): Pull this out into an interface. That will also allow us to // test the behavior of external extensions. @@ -959,8 +1136,7 @@ void ExtensionsServiceBackend::CheckForExternalUpdates( while (iterator.Valid()) { // Fold std::string id = StringToLowerASCII(WideToASCII(iterator.Name())); - if (ids_to_ignore.find(id) != ids_to_ignore.end()) { - LOG(INFO) << "Skipping uninstalled external extension " << id; + if (ShouldSkipInstallingExtension(ids_to_ignore, id)) { ++iterator; continue; } @@ -974,11 +1150,10 @@ void ExtensionsServiceBackend::CheckForExternalUpdates( if (key.ReadValue(kRegistryExtensionPath, &extension_path)) { std::wstring extension_version; if (key.ReadValue(kRegistryExtensionVersion, &extension_version)) { - if (ShouldInstall(id, WideToASCII(extension_version))) { - bool from_external = true; - InstallOrUpdateExtension(FilePath(extension_path), id, - from_external); - } + bool from_external = true; + CheckVersionAndInstallExtension( + id, WideToASCII(extension_version), FilePath(extension_path), + from_external); } else { // TODO(erikkay): find a way to get this into about:extensions LOG(WARNING) << "Missing value " << kRegistryExtensionVersion << @@ -992,16 +1167,26 @@ void ExtensionsServiceBackend::CheckForExternalUpdates( } ++iterator; } -#else - NOTREACHED(); #endif } -bool ExtensionsServiceBackend::CheckExternalUninstall(const FilePath& version_path, - const std::string& id) { - FilePath external_file = version_path.AppendASCII(kExternalInstallFile); - if (file_util::PathExists(external_file)) { +bool ExtensionsServiceBackend::CheckExternalUninstall( + DictionaryValue* extension_prefs, const FilePath& version_path, + const std::string& id) { + // First check the preferences for the kill-bit. + Extension::Location location = Extension::INVALID; + DictionaryValue* extension = NULL; + if (extension_prefs->GetDictionary(ASCIIToWide(id), &extension)) { + Extension::State state; + if (extension->GetInteger(kLocation, reinterpret_cast<int*>(&location)) && + location == Extension::EXTERNAL_PREF) { + return extension->GetInteger(kState, reinterpret_cast<int*>(&state)) && + state == Extension::KILLBIT; + } + } + #if defined(OS_WIN) + if (location == Extension::EXTERNAL_REGISTRY) { HKEY reg_root = HKEY_LOCAL_MACHINE; RegKey key; std::wstring key_path = ASCIIToWide(registry_path_); @@ -1010,10 +1195,9 @@ bool ExtensionsServiceBackend::CheckExternalUninstall(const FilePath& version_pa // If the key doesn't exist, then we should uninstall. return !key.Open(reg_root, key_path.c_str()); -#else - // TODO(port) -#endif } +#endif + return false; } diff --git a/chrome/browser/extensions/extensions_service.h b/chrome/browser/extensions/extensions_service.h index 09c9623..2d149f1 100644 --- a/chrome/browser/extensions/extensions_service.h +++ b/chrome/browser/extensions/extensions_service.h @@ -16,8 +16,10 @@ #include "base/task.h" #include "base/values.h" #include "chrome/common/chrome_switches.h" +#include "chrome/common/extensions/extension.h" class Browser; +class DictionaryValue; class Extension; class ExtensionsServiceBackend; class GURL; @@ -63,6 +65,26 @@ class ExtensionsService // Lookup an extension by |id|. Extension* GetExtensionByID(std::string id); + // Gets a list of external extensions. If |external_extensions| is non-null, + // a dictionary with all external extensions (including extensions installed + // through the registry on Windows builds) and their preferences are + // returned. If |killed_extensions| is non-null, a set of string IDs + // containing all external extension IDs with the killbit set are returned. + void GetExternalExtensions(DictionaryValue* external_extensions, + std::set<std::string>* killed_extensions); + + // Gets the settings for an extension from preferences. If the key doesn't + // exist, this function creates it (don't need to check return for NULL). + DictionaryValue* GetOrCreateExtensionPref(const std::wstring& extension_id); + + // Writes a preference value for a particular extension |extension_id| under + // the |key| specified. If |schedule_save| is true, it will also ask the + // preference system to schedule a save to disk. + bool UpdateExtensionPref(const std::wstring& extension_id, + const std::wstring& key, + Value* data_value, + bool schedule_save); + // The name of the file that the current active version number is stored in. static const char* kCurrentVersionFileName; @@ -87,6 +109,10 @@ class ExtensionsService // Called by the backend when an extensoin hsa been installed. void OnExtensionInstalled(Extension* extension, bool is_update); + // Called by the backend when an external extension has been installed. + void OnExternalExtensionInstalled( + const std::string& id, Extension::Location location); + // Called by the backend when an extension has been reinstalled. void OnExtensionVersionReinstalled(const std::string& id); @@ -136,7 +162,8 @@ class ExtensionsServiceBackend // Errors are reported through ExtensionErrorReporter. On completion, // OnExtensionsLoaded() is called with any successfully loaded extensions. void LoadExtensionsFromInstallDirectory( - scoped_refptr<ExtensionsService> frontend); + scoped_refptr<ExtensionsService> frontend, + DictionaryValue* extension_prefs); // Loads a single extension from |path| where |path| is the top directory of // a specific extension where its manifest file lives. @@ -158,6 +185,7 @@ class ExtensionsServiceBackend // Errors are reported through ExtensionErrorReporter. Succcess is not // reported. void CheckForExternalUpdates(std::set<std::string> ids_to_ignore, + DictionaryValue* extension_prefs, scoped_refptr<ExtensionsService> frontend); // Deletes all versions of the extension from the filesystem. Note that only @@ -214,6 +242,18 @@ class ExtensionsServiceBackend // Notify the frontend that the extension had already been installed. void ReportExtensionVersionReinstalled(const std::string& id); + // Checks a set of strings (containing id's to ignore) in order to determine + // if the extension should be installed. + bool ShouldSkipInstallingExtension(const std::set<std::string>& ids_to_ignore, + const std::string& id); + + // Installs the extension if the extension is a newer version or if the + // extension hasn't been installed before. + void CheckVersionAndInstallExtension(const std::string& id, + const std::string& extension_version, + const FilePath& extension_path, + bool from_external); + // Read the manifest from the front of the extension file. // Caller takes ownership of return value. DictionaryValue* ReadManifest(const FilePath& extension_path); @@ -238,7 +278,8 @@ class ExtensionsServiceBackend // For the extension in |version_path| with |id|, check to see if it's an // externally managed extension. If so return true if it should be // uninstalled. - bool CheckExternalUninstall(const FilePath& version_path, + bool CheckExternalUninstall(DictionaryValue* extension_prefs, + const FilePath& version_path, const std::string& id); // Should an extension of |id| and |version| be installed? diff --git a/chrome/browser/extensions/extensions_service_unittest.cc b/chrome/browser/extensions/extensions_service_unittest.cc index 371dd8a..e7cdd6b 100644 --- a/chrome/browser/extensions/extensions_service_unittest.cc +++ b/chrome/browser/extensions/extensions_service_unittest.cc @@ -30,6 +30,17 @@ #include "base/registry.h" #endif +// Extension ids used during testing. +static const char* all_zero = "0000000000000000000000000000000000000000"; +static const char* zero_n_one = "0000000000000000000000000000000000000001"; +static const char* good0 = "00123456789abcdef0123456789abcdef0123456"; +static const char* good1 = "10123456789abcdef0123456789abcdef0123456"; +static const char* good2 = "20123456789abcdef0123456789abcdef0123456"; +static const char* good_crx = "00123456789abcdef0123456789abcdef0123456"; +static const char* page_action = "8a5e4cb023c61b431e9b603a97c293429ce057c8"; +static const char* theme_crx = "f0123456789abcdef0123456789abcdef0126456"; +static const char* theme2_crx = "f0123456789adddef0123456789abcdef0126456"; + namespace { struct ExtensionsOrder { @@ -177,6 +188,35 @@ class ExtensionsServiceTest ExtensionErrorReporter::GetInstance()->ClearErrors(); } + void ValidatePrefKeyCount(size_t count) { + DictionaryValue* dict = + profile_->GetPrefs()->GetMutableDictionary(L"extensions.settings"); + ASSERT_TRUE(dict != NULL); + EXPECT_EQ(count, dict->GetSize()); + } + + void ValidatePref(std::string extension_id, + std::wstring pref_path, + int must_equal) { + std::wstring msg = L" while checking: "; + msg += ASCIIToWide(extension_id); + msg += L" "; + msg += pref_path; + msg += L" == "; + msg += IntToWString(must_equal); + + const DictionaryValue* dict = + profile_->GetPrefs()->GetDictionary(L"extensions.settings"); + ASSERT_TRUE(dict != NULL) << msg; + DictionaryValue* pref = NULL; + ASSERT_TRUE(dict->GetDictionary(ASCIIToWide(extension_id), &pref)) << msg; + EXPECT_TRUE(pref != NULL) << msg; + int val; + pref->GetInteger(pref_path, &val); + EXPECT_EQ(must_equal, val) << msg; + } + + protected: scoped_ptr<TestingProfile> profile_; scoped_refptr<ExtensionsService> service_; size_t total_successes_; @@ -199,7 +239,7 @@ TEST_F(ExtensionsServiceTest, LoadAllExtensionsFromDirectorySuccess) { source_path = source_path.AppendASCII("good"); FilePath dest_path = profile_->GetPath().AppendASCII("Extensions"); - file_util::CopyDirectory(source_path, dest_path, true); // recursive + file_util::CopyDirectory(source_path, dest_path, true); // Recursive. ASSERT_TRUE(service_->Init()); loop_.RunAllPending(); @@ -211,8 +251,7 @@ TEST_F(ExtensionsServiceTest, LoadAllExtensionsFromDirectorySuccess) { } ASSERT_EQ(3u, loaded_.size()); - EXPECT_EQ(std::string("00123456789abcdef0123456789abcdef0123456"), - loaded_[0]->id()); + EXPECT_EQ(std::string(good_crx), loaded_[0]->id()); EXPECT_EQ(std::string("My extension 1"), loaded_[0]->name()); EXPECT_EQ(std::string("The first extension that I made."), @@ -221,6 +260,14 @@ TEST_F(ExtensionsServiceTest, LoadAllExtensionsFromDirectorySuccess) { EXPECT_TRUE(service_->GetExtensionByID(loaded_[0]->id())); EXPECT_EQ(3u, service_->extensions()->size()); + ValidatePrefKeyCount(3); + ValidatePref(good_crx, L"state", Extension::ENABLED); + ValidatePref(good_crx, L"location", Extension::INTERNAL); + ValidatePref(good1, L"state", Extension::ENABLED); + ValidatePref(good1, L"location", Extension::INTERNAL); + ValidatePref(good2, L"state", Extension::ENABLED); + ValidatePref(good2, L"location", Extension::INTERNAL); + Extension* extension = loaded_[0]; const UserScriptList& scripts = extension->content_scripts(); const std::vector<std::string>& toolstrips = extension->toolstrips(); @@ -248,8 +295,7 @@ TEST_F(ExtensionsServiceTest, LoadAllExtensionsFromDirectorySuccess) { EXPECT_EQ("toolstrip1.html", toolstrips[0]); EXPECT_EQ("toolstrip2.html", toolstrips[1]); - EXPECT_EQ(std::string("10123456789abcdef0123456789abcdef0123456"), - loaded_[1]->id()); + EXPECT_EQ(std::string(good1), loaded_[1]->id()); EXPECT_EQ(std::string("My extension 2"), loaded_[1]->name()); EXPECT_EQ(std::string(""), loaded_[1]->description()); EXPECT_EQ(loaded_[1]->GetResourceURL("background.html"), @@ -264,8 +310,7 @@ TEST_F(ExtensionsServiceTest, LoadAllExtensionsFromDirectorySuccess) { EXPECT_FALSE(loaded_[1]->plugins()[1].is_public); EXPECT_EQ(Extension::INTERNAL, loaded_[1]->location()); - EXPECT_EQ(std::string("20123456789abcdef0123456789abcdef0123456"), - loaded_[2]->id()); + EXPECT_EQ(std::string(good2), loaded_[2]->id()); EXPECT_EQ(std::string("My extension 3"), loaded_[2]->name()); EXPECT_EQ(std::string(""), loaded_[2]->description()); EXPECT_EQ(0u, loaded_[2]->content_scripts().size()); @@ -280,7 +325,7 @@ TEST_F(ExtensionsServiceTest, LoadAllExtensionsFromDirectoryFail) { source_path = source_path.AppendASCII("bad"); FilePath dest_path = profile_->GetPath().AppendASCII("Extensions"); - file_util::CopyDirectory(source_path, dest_path, true); // recursive + file_util::CopyDirectory(source_path, dest_path, true); // Recursive. ASSERT_TRUE(service_->Init()); loop_.RunAllPending(); @@ -288,6 +333,9 @@ TEST_F(ExtensionsServiceTest, LoadAllExtensionsFromDirectoryFail) { EXPECT_EQ(3u, GetErrors().size()); EXPECT_EQ(0u, loaded_.size()); + // Make sure the dictionary is empty. + ValidatePrefKeyCount(0); + EXPECT_TRUE(MatchPattern(GetErrors()[0], std::string("Could not load extension from '*'. * ") + JSONReader::kBadRootElementType)) << GetErrors()[0]; @@ -310,7 +358,7 @@ TEST_F(ExtensionsServiceTest, CleanupOnStartup) { source_path = source_path.AppendASCII("good"); FilePath dest_path = profile_->GetPath().AppendASCII("Extensions"); - file_util::CopyDirectory(source_path, dest_path, true); // recursive + file_util::CopyDirectory(source_path, dest_path, true); // Recursive. // Simulate that one of them got partially deleted by deling the // Current Version file. @@ -327,6 +375,12 @@ TEST_F(ExtensionsServiceTest, CleanupOnStartup) { // And extension1 dir should now be toast. dest_path = dest_path.DirName(); ASSERT_FALSE(file_util::PathExists(dest_path)); + + ValidatePrefKeyCount(2); + ValidatePref(good1, L"state", Extension::ENABLED); + ValidatePref(good1, L"location", Extension::INTERNAL); + ValidatePref(good2, L"state", Extension::ENABLED); + ValidatePref(good2, L"location", Extension::INTERNAL); } // Test installing extensions. @@ -341,30 +395,44 @@ TEST_F(ExtensionsServiceTest, InstallExtension) { TestInstallExtension(path, false); SetExtensionsEnabled(true); + ValidatePrefKeyCount(0); + // A simple extension that should install without error. path = extensions_path.AppendASCII("good.crx"); TestInstallExtension(path, true); // TODO(erikkay): verify the contents of the installed extension. + int pref_count = 0; + ValidatePrefKeyCount(++pref_count); + ValidatePref(good_crx, L"state", Extension::ENABLED); + ValidatePref(good_crx, L"location", Extension::INTERNAL); + // An extension with page actions. path = extensions_path.AppendASCII("page_action.crx"); TestInstallExtension(path, true); + ValidatePrefKeyCount(++pref_count); + ValidatePref(page_action, L"state", Extension::ENABLED); + ValidatePref(page_action, L"location", Extension::INTERNAL); // 0-length extension file. path = extensions_path.AppendASCII("not_an_extension.crx"); TestInstallExtension(path, false); + ValidatePrefKeyCount(pref_count); // Bad magic number. path = extensions_path.AppendASCII("bad_magic.crx"); TestInstallExtension(path, false); + ValidatePrefKeyCount(pref_count); // Poorly formed JSON. path = extensions_path.AppendASCII("bad_json.crx"); TestInstallExtension(path, false); + ValidatePrefKeyCount(pref_count); // Incorrect zip hash. path = extensions_path.AppendASCII("bad_hash.crx"); TestInstallExtension(path, false); + ValidatePrefKeyCount(pref_count); // TODO(erikkay): add more tests for many of the failure cases. // TODO(erikkay): add tests for upgrade cases. @@ -378,20 +446,31 @@ TEST_F(ExtensionsServiceTest, InstallTheme) { // A theme. FilePath path = extensions_path.AppendASCII("theme.crx"); TestInstallTheme(path, true); + int pref_count = 0; + ValidatePrefKeyCount(++pref_count); + ValidatePref(theme_crx, L"state", Extension::ENABLED); + ValidatePref(theme_crx, L"location", Extension::INTERNAL); - // A theme when extensions are disabled. + // A theme when extensions are disabled. Themes can be installed even though + // extensions are disabled. SetExtensionsEnabled(false); path = extensions_path.AppendASCII("theme2.crx"); TestInstallTheme(path, true); + ValidatePrefKeyCount(++pref_count); + ValidatePref(theme2_crx, L"state", Extension::ENABLED); + ValidatePref(theme2_crx, L"location", Extension::INTERNAL); SetExtensionsEnabled(true); - // A theme with extension elements. + // A theme with extension elements. Themes cannot have extension elements so + // this test should fail. path = extensions_path.AppendASCII("theme_with_extension.crx"); TestInstallTheme(path, false); + ValidatePrefKeyCount(pref_count); // A theme with image resources missing (misspelt path). path = extensions_path.AppendASCII("theme_missing_image.crx"); TestInstallTheme(path, false); + ValidatePrefKeyCount(pref_count); } // Test that when an extension version is reinstalled, nothing happens. @@ -408,6 +487,9 @@ TEST_F(ExtensionsServiceTest, Reinstall) { ASSERT_TRUE(installed_); ASSERT_EQ(1u, loaded_.size()); ASSERT_EQ(0u, GetErrors().size()); + ValidatePrefKeyCount(1); + ValidatePref(good_crx, L"state", Extension::ENABLED); + ValidatePref(good_crx, L"location", Extension::INTERNAL); installed_ = NULL; loaded_.clear(); @@ -420,6 +502,9 @@ TEST_F(ExtensionsServiceTest, Reinstall) { ASSERT_FALSE(installed_); ASSERT_EQ(0u, loaded_.size()); ASSERT_EQ(0u, GetErrors().size()); + ValidatePrefKeyCount(1); + ValidatePref(good_crx, L"state", Extension::ENABLED); + ValidatePref(good_crx, L"location", Extension::INTERNAL); } // Tests uninstalling normal extensions @@ -434,10 +519,14 @@ TEST_F(ExtensionsServiceTest, UninstallExtension) { // The directory should be there now. FilePath install_path = profile_->GetPath().AppendASCII("Extensions"); - const char* extension_id = "00123456789abcdef0123456789abcdef0123456"; + const char* extension_id = good_crx; FilePath extension_path = install_path.AppendASCII(extension_id); EXPECT_TRUE(file_util::PathExists(extension_path)); + ValidatePrefKeyCount(1); + ValidatePref(good_crx, L"state", Extension::ENABLED); + ValidatePref(good_crx, L"location", Extension::INTERNAL); + // Uninstall it. service_->UninstallExtension(extension_id); total_successes_ = 0; @@ -446,6 +535,10 @@ TEST_F(ExtensionsServiceTest, UninstallExtension) { ASSERT_TRUE(unloaded_id_.length()); EXPECT_EQ(extension_id, unloaded_id_); + ValidatePrefKeyCount(1); + ValidatePref(good_crx, L"state", Extension::DISABLED); + ValidatePref(good_crx, L"location", Extension::INTERNAL); + // The extension should not be in the service anymore. ASSERT_FALSE(service_->GetExtensionByID(extension_id)); loop_.RunAllPending(); @@ -463,6 +556,10 @@ TEST_F(ExtensionsServiceTest, UninstallExtension) { service_->UninstallExtension(extension_id); loop_.RunAllPending(); EXPECT_FALSE(file_util::PathExists(extension_path)); + + ValidatePrefKeyCount(1); + ValidatePref(good_crx, L"state", Extension::DISABLED); + ValidatePref(good_crx, L"location", Extension::INTERNAL); } // Tests loading single extensions (like --load-extension) @@ -479,6 +576,10 @@ TEST_F(ExtensionsServiceTest, LoadExtension) { ASSERT_EQ(1u, loaded_.size()); ASSERT_EQ(Extension::LOAD, loaded_[0]->location()); + ValidatePrefKeyCount(1); + ValidatePref(good0, L"state", Extension::ENABLED); + ValidatePref(good0, L"location", Extension::INTERNAL); + FilePath no_manifest = extensions_path.AppendASCII("bad") .AppendASCII("no_manifest").AppendASCII("1"); service_->LoadExtension(no_manifest); @@ -486,12 +587,18 @@ TEST_F(ExtensionsServiceTest, LoadExtension) { EXPECT_EQ(1u, GetErrors().size()); ASSERT_EQ(1u, loaded_.size()); - // Test uninstall + ValidatePrefKeyCount(1); + + // Test uninstall. std::string id = loaded_[0]->id(); EXPECT_FALSE(unloaded_id_.length()); service_->UninstallExtension(id); loop_.RunAllPending(); EXPECT_EQ(id, unloaded_id_); + + ValidatePrefKeyCount(1); + ValidatePref(good0, L"state", Extension::DISABLED); + ValidatePref(good0, L"location", Extension::INTERNAL); } // Tests that we generate IDs when they are not specified in the manifest for @@ -507,23 +614,31 @@ TEST_F(ExtensionsServiceTest, GenerateID) { EXPECT_EQ(0u, GetErrors().size()); ASSERT_EQ(1u, loaded_.size()); std::string id1 = loaded_[0]->id(); - ASSERT_EQ("0000000000000000000000000000000000000000", id1); + ASSERT_EQ(all_zero, id1); ASSERT_EQ("chrome-extension://0000000000000000000000000000000000000000/", loaded_[0]->url().spec()); + ValidatePrefKeyCount(1); + ValidatePref(all_zero, L"state", Extension::ENABLED); + ValidatePref(all_zero, L"location", Extension::INTERNAL); + service_->LoadExtension(no_id_ext); loop_.RunAllPending(); std::string id2 = loaded_[1]->id(); - ASSERT_EQ("0000000000000000000000000000000000000001", id2); + ASSERT_EQ(zero_n_one, id2); ASSERT_EQ("chrome-extension://0000000000000000000000000000000000000001/", loaded_[1]->url().spec()); + + ValidatePrefKeyCount(2); + ValidatePref(zero_n_one, L"state", Extension::ENABLED); + ValidatePref(zero_n_one, L"location", Extension::INTERNAL); } // Tests the external installation feature #if defined(OS_WIN) -TEST_F(ExtensionsServiceTest, ExternalInstall) { - // Register a test extension externally. +TEST_F(ExtensionsServiceTest, ExternalInstallRegistry) { + // Register a test extension externally using the registry. FilePath source_path; ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &source_path)); source_path = source_path.AppendASCII("extensions").AppendASCII("good.crx"); @@ -542,8 +657,11 @@ TEST_F(ExtensionsServiceTest, ExternalInstall) { ASSERT_EQ(0u, GetErrors().size()); ASSERT_EQ(1u, loaded_.size()); - ASSERT_EQ(Extension::EXTERNAL, loaded_[0]->location()); + ASSERT_EQ(Extension::EXTERNAL_REGISTRY, loaded_[0]->location()); ASSERT_EQ("1.0.0.0", loaded_[0]->version()->GetString()); + ValidatePrefKeyCount(1); + ValidatePref(good_crx, L"state", Extension::ENABLED); + ValidatePref(good_crx, L"location", Extension::EXTERNAL_REGISTRY); // Reinit the service without changing anything. The extension should be // loaded again. @@ -552,6 +670,9 @@ TEST_F(ExtensionsServiceTest, ExternalInstall) { loop_.RunAllPending(); ASSERT_EQ(0u, GetErrors().size()); ASSERT_EQ(1u, loaded_.size()); + ValidatePrefKeyCount(1); + ValidatePref(good_crx, L"state", Extension::ENABLED); + ValidatePref(good_crx, L"location", Extension::EXTERNAL_REGISTRY); // Now update the extension with a new version. We should get upgraded. source_path = source_path.DirName().AppendASCII("good2.crx"); @@ -564,6 +685,9 @@ TEST_F(ExtensionsServiceTest, ExternalInstall) { ASSERT_EQ(0u, GetErrors().size()); ASSERT_EQ(1u, loaded_.size()); ASSERT_EQ("1.0.0.1", loaded_[0]->version()->GetString()); + ValidatePrefKeyCount(1); + ValidatePref(good_crx, L"state", Extension::ENABLED); + ValidatePref(good_crx, L"location", Extension::EXTERNAL_REGISTRY); // Uninstall the extension and reinit. Nothing should happen because the // preference should prevent us from reinstalling. @@ -580,17 +704,28 @@ TEST_F(ExtensionsServiceTest, ExternalInstall) { service_->Init(); loop_.RunAllPending(); ASSERT_EQ(0u, loaded_.size()); + ValidatePrefKeyCount(1); + ValidatePref(good_crx, L"state", Extension::KILLBIT); // It is an ex-parrot. + ValidatePref(good_crx, L"location", Extension::EXTERNAL_REGISTRY); // Now clear the preference, reinstall, then remove the reg key. The extension // should be uninstalled. - profile_->GetPrefs()->GetMutableList(L"extensions.uninstalled_external_ids") - ->Clear(); + std::wstring pref_path = L"extensions.settings."; + pref_path += ASCIIToWide(good_crx); + profile_->GetPrefs()->RegisterDictionaryPref(pref_path.c_str()); + DictionaryValue* extension_prefs; + extension_prefs = + profile_->GetPrefs()->GetMutableDictionary(pref_path.c_str()); + extension_prefs->SetInteger(L"state", 0); profile_->GetPrefs()->ScheduleSavePersistentPrefs(); loaded_.clear(); service_->Init(); loop_.RunAllPending(); ASSERT_EQ(1u, loaded_.size()); + ValidatePrefKeyCount(1); + ValidatePref(good_crx, L"state", Extension::ENABLED); + ValidatePref(good_crx, L"location", Extension::EXTERNAL_REGISTRY); RegKey parent_key; key.Open(HKEY_LOCAL_MACHINE, ASCIIToWide(registry_path_).c_str(), KEY_WRITE); @@ -601,8 +736,116 @@ TEST_F(ExtensionsServiceTest, ExternalInstall) { ASSERT_EQ(0u, loaded_.size()); } -#else +#endif + +TEST_F(ExtensionsServiceTest, ExternalInstallPref) { + // Register a external extension using preinstalled preferences. + FilePath source_path; + ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &source_path)); + source_path = source_path.AppendASCII("extensions").AppendASCII("good.crx"); + + DictionaryValue* extension_prefs; + extension_prefs = + profile_->GetPrefs()->GetMutableDictionary(L"extensions.settings"); -// TODO(port) + DictionaryValue* extension = new DictionaryValue(); + ASSERT_TRUE(extension->SetString(L"external_crx", + source_path.ToWStringHack())); + ASSERT_TRUE(extension->SetString(L"external_version", L"1.0")); + ASSERT_TRUE(extension_prefs->Set(ASCIIToWide(good_crx), extension)); -#endif + // Start up the service, it should find our externally registered extension + // and install it. + service_->Init(); + loop_.RunAllPending(); + + ASSERT_EQ(0u, GetErrors().size()); + ASSERT_EQ(1u, loaded_.size()); + ASSERT_EQ(Extension::EXTERNAL_PREF, loaded_[0]->location()); + ASSERT_EQ("1.0.0.0", loaded_[0]->version()->GetString()); + + ValidatePrefKeyCount(1); + ValidatePref(good_crx, L"state", Extension::ENABLED); + ValidatePref(good_crx, L"location", Extension::EXTERNAL_PREF); + + // Reinit the service without changing anything. The extension should be + // loaded again. + loaded_.clear(); + service_->Init(); + loop_.RunAllPending(); + ASSERT_EQ(0u, GetErrors().size()); + ASSERT_EQ(1u, loaded_.size()); + + ValidatePrefKeyCount(1); + ValidatePref(good_crx, L"state", Extension::ENABLED); + ValidatePref(good_crx, L"location", Extension::EXTERNAL_PREF); + + // Now update the extension with a new version. We should get upgraded. + source_path = source_path.DirName().AppendASCII("good2.crx"); + ASSERT_TRUE(extension->SetString(L"external_crx", + source_path.ToWStringHack())); + ASSERT_TRUE(extension->SetString(L"external_version", L"1.0.0.1")); + + loaded_.clear(); + service_->Init(); + loop_.RunAllPending(); + ASSERT_EQ(0u, GetErrors().size()); + ASSERT_EQ(1u, loaded_.size()); + ASSERT_EQ("1.0.0.1", loaded_[0]->version()->GetString()); + + ValidatePrefKeyCount(1); + ValidatePref(good_crx, L"state", Extension::ENABLED); + ValidatePref(good_crx, L"location", Extension::EXTERNAL_PREF); + + // Uninstall the extension and re-init. Nothing should happen because the + // preference should prevent us from reinstalling. + std::string id = loaded_[0]->id(); + service_->UninstallExtension(id); + loop_.RunAllPending(); + + // The extension should also be gone from the install directory. + FilePath install_path = + profile_->GetPath().AppendASCII("Extensions").AppendASCII(id); + ASSERT_FALSE(file_util::PathExists(install_path)); + + loaded_.clear(); + service_->Init(); + loop_.RunAllPending(); + ASSERT_EQ(0u, loaded_.size()); + + ValidatePrefKeyCount(1); + ValidatePref(good_crx, L"state", Extension::KILLBIT); + ValidatePref(good_crx, L"location", Extension::EXTERNAL_PREF); + + // Now clear the preference and reinstall. + extension->SetInteger(L"state", Extension::ENABLED); + profile_->GetPrefs()->ScheduleSavePersistentPrefs(); + + loaded_.clear(); + service_->Init(); + loop_.RunAllPending(); + ASSERT_EQ(1u, loaded_.size()); + + ValidatePrefKeyCount(1); + ValidatePref(good_crx, L"state", Extension::ENABLED); + ValidatePref(good_crx, L"location", Extension::EXTERNAL_PREF); + + // Now set the kill bit and watch the extension go away. + extension->SetInteger(L"state", Extension::KILLBIT); + profile_->GetPrefs()->ScheduleSavePersistentPrefs(); + + loaded_.clear(); + service_->Init(); + loop_.RunAllPending(); + ASSERT_EQ(0u, loaded_.size()); + + ValidatePrefKeyCount(1); + ValidatePref(good_crx, L"state", Extension::KILLBIT); + ValidatePref(good_crx, L"location", Extension::EXTERNAL_PREF); + + // The extension should also be gone from disk. + FilePath extension_path = install_path.DirName(); + extension_path = extension_path.AppendASCII(good_crx); + EXPECT_FALSE(file_util::PathExists(extension_path)) << + extension_path.ToWStringHack(); +} diff --git a/chrome/common/extensions/extension.cc b/chrome/common/extensions/extension.cc index f6a579b..c641afc 100644 --- a/chrome/common/extensions/extension.cc +++ b/chrome/common/extensions/extension.cc @@ -15,6 +15,10 @@ #include "chrome/common/extensions/user_script.h" #include "chrome/common/url_constants.h" +#if defined(OS_WIN) +#include "base/registry.h" +#endif + const char Extension::kManifestFilename[] = "manifest.json"; const wchar_t* Extension::kContentScriptsKey = L"content_scripts"; @@ -142,6 +146,11 @@ const char* Extension::kInvalidThemeTintsError = const char* Extension::kThemesCannotContainExtensionsError = "A theme cannot contain extensions code."; +#if defined(OS_WIN) +const char* Extension::kExtensionRegistryPath = + "Software\\Google\\Chrome\\Extensions"; +#endif + const size_t Extension::kIdSize = 20; // SHA1 (160 bits) == 20 bytes Extension::~Extension() { @@ -155,6 +164,24 @@ const std::string Extension::VersionString() const { } // static +bool Extension::IdIsValid(const std::string& id) { + // Verify that the id is legal. The id is a hex string of the SHA-1 hash of + // the public key. + std::vector<uint8> id_bytes; + if (!HexStringToBytes(id, &id_bytes) || id_bytes.size() != kIdSize) + return false; + + // We only support lowercase IDs, because IDs can be used as URL components + // (where GURL will lowercase it). + std::string temp = id; + StringToLowerASCII(temp); + if (temp != id) + return false; + + return true; +} + +// static GURL Extension::GetResourceURL(const GURL& extension_url, const std::string& relative_path) { DCHECK(extension_url.SchemeIs(chrome::kExtensionScheme)); @@ -174,6 +201,19 @@ const PageAction* Extension::GetPageAction(std::string id) const { return it->second; } +Extension::Location Extension::ExternalExtensionInstallType( + std::string registry_path) { +#if defined(OS_WIN) + HKEY reg_root = HKEY_LOCAL_MACHINE; + RegKey key; + registry_path.append("\\"); + registry_path.append(id_); + if (key.Open(reg_root, ASCIIToWide(registry_path).c_str())) + return Extension::EXTERNAL_REGISTRY; +#endif + return Extension::EXTERNAL_PREF; +} + // Helper method that loads a UserScript object from a dictionary in the // content_script list of the manifest. bool Extension::LoadUserScriptHelper(const DictionaryValue* content_script, @@ -449,10 +489,8 @@ bool Extension::InitFromValue(const DictionaryValue& source, bool require_id, // (where GURL will lowercase it). StringToLowerASCII(&id_); - // Verify that the id is legal. The id is a hex string of the SHA-1 hash of - // the public key. - std::vector<uint8> id_bytes; - if (!HexStringToBytes(id_, &id_bytes) || id_bytes.size() != kIdSize) { + // Verify that the id is legal. + if (!IdIsValid(id_)) { *error = kInvalidIdError; return false; } diff --git a/chrome/common/extensions/extension.h b/chrome/common/extensions/extension.h index 76f4b20..9b79f30 100644 --- a/chrome/common/extensions/extension.h +++ b/chrome/common/extensions/extension.h @@ -24,10 +24,17 @@ class Extension { // What an extension was loaded from. enum Location { INVALID, - INTERNAL, // A crx file from the internal Extensions directory. - EXTERNAL, // A crx file from an external directory (via eg the registry - // on Windows). - LOAD // --load-extension. + INTERNAL, // A crx file from the internal Extensions directory. + EXTERNAL_PREF, // A crx file from an external directory (via prefs). + EXTERNAL_REGISTRY, // A crx file from an external directory (via eg the + // registry on Windows). + LOAD // --load-extension. + }; + + enum State { + DISABLED, + ENABLED, + KILLBIT, // Don't install/upgrade (applies to external extensions only). }; // An NPAPI plugin included in the extension. @@ -114,6 +121,10 @@ class Extension { static const char* kThemesCannotContainExtensionsError; static const char* kMissingFileError; +#if defined(OS_WIN) + static const char* kExtensionRegistryPath; +#endif + // The number of bytes in a legal id. static const size_t kIdSize; @@ -121,6 +132,15 @@ class Extension { explicit Extension(const FilePath& path); virtual ~Extension(); + // Checks to see if the extension has a valid ID. + static bool IdIsValid(const std::string& id); + + // Whether the |location| is external or not. + static inline bool IsExternalLocation(Location location) { + return location == Extension::EXTERNAL_PREF || + location == Extension::EXTERNAL_REGISTRY; + } + // Returns an absolute url to a resource inside of an extension. The // |extension_url| argument should be the url() from an Extension object. The // |relative_path| can be untrusted user input. The returned URL will either @@ -169,7 +189,11 @@ class Extension { // Retrieves a page action by |id|. const PageAction* GetPageAction(std::string id) const; - // Theme-related + // Returns the origin of this extension. This function takes a |registry_path| + // so that the registry location can be overwritten during testing. + Location ExternalExtensionInstallType(std::string registry_path); + + // Theme-related. DictionaryValue* GetThemeImages() const { return theme_images_.get(); } DictionaryValue* GetThemeColors() const { return theme_colors_.get(); } DictionaryValue* GetThemeTints() const { return theme_tints_.get(); } diff --git a/chrome/common/extensions/extension_unpacker.cc b/chrome/common/extensions/extension_unpacker.cc index f98e875..916b048 100644 --- a/chrome/common/extensions/extension_unpacker.cc +++ b/chrome/common/extensions/extension_unpacker.cc @@ -41,19 +41,6 @@ struct ExtensionHeader { const size_t kZipHashBytes = 32; // SHA-256 const size_t kZipHashHexBytes = kZipHashBytes * 2; // Hex string is 2x size. -#if defined(OS_WIN) - -// Registry key where registry defined extension installers live. -const wchar_t kRegistryExtensions[] = L"Software\\Google\\Chrome\\Extensions"; - -// Registry value of of that key that defines the path to the .crx file. -const wchar_t kRegistryExtensionPath[] = L"path"; - -// Registry value of that key that defines the current version of the .crx file. -const wchar_t kRegistryExtensionVersion[] = L"version"; - -#endif - // A marker file to indicate that an extension was installed from an external // source. const char kExternalInstallFile[] = "EXTERNAL_INSTALL"; |