diff options
-rw-r--r-- | chrome/browser/extensions/crx_installer.cc | 31 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_prefs.cc | 97 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_prefs.h | 21 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_updater.cc | 123 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_updater.h | 35 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_updater_unittest.cc | 165 | ||||
-rw-r--r-- | chrome/browser/extensions/extensions_service.cc | 32 | ||||
-rw-r--r-- | chrome/browser/extensions/extensions_service.h | 7 | ||||
-rw-r--r-- | chrome/browser/extensions/extensions_service_unittest.cc | 260 | ||||
-rw-r--r-- | chrome/common/pref_names.cc | 3 | ||||
-rw-r--r-- | chrome/common/pref_names.h | 2 |
11 files changed, 675 insertions, 101 deletions
diff --git a/chrome/browser/extensions/crx_installer.cc b/chrome/browser/extensions/crx_installer.cc index 5fede2e..c6d4590 100644 --- a/chrome/browser/extensions/crx_installer.cc +++ b/chrome/browser/extensions/crx_installer.cc @@ -116,14 +116,10 @@ void CrxInstaller::OnUnpackSuccess(const FilePath& temp_dir, expected_id_.c_str())); return; } + if (client_.get()) DecodeInstallIcon(); - if (client_.get()) { - DecodeInstallIcon(); - ui_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, - &CrxInstaller::ConfirmInstall)); - } else { - CompleteInstall(); - } + ui_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, + &CrxInstaller::ConfirmInstall)); } void CrxInstaller::DecodeInstallIcon() { @@ -146,7 +142,7 @@ void CrxInstaller::DecodeInstallIcon() { webkit_glue::ImageDecoder decoder; scoped_ptr<SkBitmap> decoded(new SkBitmap()); *decoded = decoder.Decode(data, file_contents.length()); - if(decoded->empty()) { + if (decoded->empty()) { LOG(ERROR) << "Could not decode icon file: " << WideToUTF8(path.ToWStringHack()); return; @@ -163,9 +159,24 @@ void CrxInstaller::DecodeInstallIcon() { } void CrxInstaller::ConfirmInstall() { - AddRef(); // balanced in ContinueInstall() and AbortInstall(). + DCHECK(MessageLoop::current() == ui_loop_); + if (frontend_->extension_prefs()->IsExtensionBlacklisted(extension_->id())) { + LOG(INFO) << "This extension: " << extension_->id() + << " is blacklisted. Install failed."; + if (client_) { + client_->OnInstallFailure("This extension is blacklisted."); + } + return; + } - client_->ConfirmInstall(this, extension_.get(), install_icon_.get()); + if (client_) { + AddRef(); // balanced in ContinueInstall() and AbortInstall(). + client_->ConfirmInstall(this, extension_.get(), install_icon_.get()); + } else { + file_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, + &CrxInstaller::CompleteInstall)); + } + return; } void CrxInstaller::ContinueInstall() { diff --git a/chrome/browser/extensions/extension_prefs.cc b/chrome/browser/extensions/extension_prefs.cc index 7b4d061..bc88831 100644 --- a/chrome/browser/extensions/extension_prefs.cc +++ b/chrome/browser/extensions/extension_prefs.cc @@ -24,6 +24,9 @@ const wchar_t kPrefState[] = L"state"; // The path to the current version's manifest file. const wchar_t kPrefPath[] = L"path"; +// Indicates if an extension is blacklisted: +const wchar_t kPrefBlacklist[] = L"blacklist"; + // A preference that tracks extension shelf configuration. This is a list // object read from the Preferences file, containing a list of toolstrip URLs. const wchar_t kExtensionShelf[] = L"extensions.shelf"; @@ -48,6 +51,17 @@ void InstalledExtensions::VisitInstalledExtensions( NOTREACHED(); continue; } + if (ext->HasKey(kPrefBlacklist)) { + bool is_blacklisted = false; + if (!ext->GetBoolean(kPrefBlacklist, &is_blacklisted)) { + NOTREACHED() << "Invalid blacklist pref:" << *extension_id; + continue; + } + if (is_blacklisted) { + LOG(WARNING) << "Blacklisted extension: " << *extension_id; + continue; + } + } FilePath::StringType path; if (!ext->GetString(kPrefPath, &path)) { LOG(WARNING) << "Missing path pref for extension " << *extension_id; @@ -131,7 +145,10 @@ void ExtensionPrefs::MakePathsAbsolute(DictionaryValue* dict) { } FilePath::StringType path_string; if (!extension_dict->GetString(kPrefPath, &path_string)) { - NOTREACHED(); + if (!IsBlacklistBitSet(extension_dict)) { + // We expect the kPrefPath for non-blacklisted extensions. + NOTREACHED(); + } continue; } DCHECK(!FilePath(path_string).IsAbsolute()); @@ -151,6 +168,83 @@ DictionaryValue* ExtensionPrefs::CopyCurrentExtensions() { return new DictionaryValue; } +bool ExtensionPrefs::IsBlacklistBitSet(DictionaryValue* ext) { + if (!ext->HasKey(kPrefBlacklist)) return false; + bool is_blacklisted = false; + if (!ext->GetBoolean(kPrefBlacklist, &is_blacklisted)) { + NOTREACHED() << "Failed to fetch blacklist flag."; + // In case we could not fetch the flag, we consider the extension + // is NOT blacklisted. + return false; + } + return is_blacklisted; +} + +bool ExtensionPrefs::IsExtensionBlacklisted(const std::string& extension_id) { + const DictionaryValue* extensions = prefs_->GetDictionary(kExtensionsPref); + DCHECK(extensions); + DictionaryValue* ext = NULL; + if (!extensions->GetDictionary(ASCIIToWide(extension_id), &ext)) { + // No such extension yet. + return false; + } + return IsBlacklistBitSet(ext); +} + +void ExtensionPrefs::UpdateBlacklist( + const std::set<std::string>& blacklist_set) { + std::vector<std::string> remove_pref_ids; + std::set<std::string> used_id_set; + const DictionaryValue* extensions = prefs_->GetDictionary(kExtensionsPref); + DCHECK(extensions); + DictionaryValue::key_iterator extension_id = extensions->begin_keys(); + for (; extension_id != extensions->end_keys(); ++extension_id) { + DictionaryValue* ext; + std::string id = WideToASCII(*extension_id); + if (!extensions->GetDictionary(*extension_id, &ext)) { + NOTREACHED() << "Invalid pref for extension " << *extension_id; + continue; + } + if (blacklist_set.find(id) == blacklist_set.end()) { + if (!IsBlacklistBitSet(ext)) { + // This extension is not in blacklist. And it was not blacklisted + // before. + continue; + } else { + if (ext->GetSize() == 1) { + // We should remove the entry if the only flag here is blacklist. + remove_pref_ids.push_back(id); + } else { + // Remove the blacklist bit. + ext->Remove(kPrefBlacklist, NULL); + } + } + } else { + if (!IsBlacklistBitSet(ext)) { + // Only set the blacklist if it was not set. + ext->SetBoolean(kPrefBlacklist, true); + } + // Keep the record if this extension is already processed. + used_id_set.insert(id); + } + } + + // Iterate the leftovers to set blacklist in pref + std::set<std::string>::const_iterator set_itr = blacklist_set.begin(); + for (; set_itr != blacklist_set.end(); ++set_itr) { + if (used_id_set.find(*set_itr) == used_id_set.end()) { + UpdateExtensionPref(*set_itr, kPrefBlacklist, + Value::CreateBooleanValue(true)); + } + } + for (unsigned int i = 0; i < remove_pref_ids.size(); ++i) { + DeleteExtensionPrefs(remove_pref_ids[i]); + } + // Update persistent registry + prefs_->ScheduleSavePersistentPrefs(); + return; +} + void ExtensionPrefs::GetKilledExtensionIds(std::set<std::string>* killed_ids) { const DictionaryValue* dict = prefs_->GetDictionary(kExtensionsPref); if (!dict || dict->GetSize() == 0) @@ -265,4 +359,3 @@ DictionaryValue* ExtensionPrefs::GetOrCreateExtensionPref( } return extension; } - diff --git a/chrome/browser/extensions/extension_prefs.h b/chrome/browser/extensions/extension_prefs.h index 277b9c3..c819bcd 100644 --- a/chrome/browser/extensions/extension_prefs.h +++ b/chrome/browser/extensions/extension_prefs.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef CHROME_BROWSER_EXTENSIONS_EXTENSION_PREFS_H -#define CHROME_BROWSER_EXTENSIONS_EXTENSION_PREFS_H +#ifndef CHROME_BROWSER_EXTENSIONS_EXTENSION_PREFS_H_ +#define CHROME_BROWSER_EXTENSIONS_EXTENSION_PREFS_H_ #include <set> #include <string> @@ -46,6 +46,12 @@ class ExtensionPrefs { // Returns base extensions install directory. const FilePath& install_directory() const { return install_directory_; } + // Updates the prefs based on the blacklist. + void UpdateBlacklist(const std::set<std::string>& blacklist_set); + + // Based on extension id, checks prefs to see if it is blacklisted. + bool IsExtensionBlacklisted(const std::string& id); + private: // Converts absolute paths in the pref to paths relative to the @@ -67,6 +73,11 @@ class ExtensionPrefs { // Ensures and returns a mutable dictionary for extension |id|'s prefs. DictionaryValue* GetOrCreateExtensionPref(const std::string& id); + // Checks if kPrefBlacklist is set to true in the DictionaryValue. + // Return false if the value is false or kPrefBlacklist does not exist. + // This is used to decide if an extension is blacklisted. + bool IsBlacklistBitSet(DictionaryValue* ext); + // The pref service specific to this set of extension prefs. PrefService* prefs_; @@ -91,7 +102,8 @@ class InstalledExtensions { Extension::Location>::Type Callback; // Runs |callback| for each installed extension with the path to the - // version directory and the location. + // version directory and the location. Blacklisted extensions won't trigger + // the callback. void VisitInstalledExtensions(Callback *callback); private: @@ -102,4 +114,5 @@ class InstalledExtensions { DISALLOW_COPY_AND_ASSIGN(InstalledExtensions); }; -#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_PREFS_H +#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_PREFS_H_ + diff --git a/chrome/browser/extensions/extension_updater.cc b/chrome/browser/extensions/extension_updater.cc index de553c3..6ae956d 100644 --- a/chrome/browser/extensions/extension_updater.cc +++ b/chrome/browser/extensions/extension_updater.cc @@ -11,6 +11,7 @@ #include "base/file_util.h" #include "base/file_version_info.h" #include "base/rand_util.h" +#include "base/sha2.h" #include "base/string_util.h" #include "base/time.h" #include "base/thread.h" @@ -31,6 +32,7 @@ using base::RandDouble; using base::RandInt; using base::Time; using base::TimeDelta; +using prefs::kExtensionBlacklistUpdateVersion; using prefs::kLastExtensionsUpdateCheck; using prefs::kNextExtensionsUpdateCheck; @@ -38,6 +40,15 @@ const char* ExtensionUpdater::kExpectedGupdateProtocol = "2.0"; const char* ExtensionUpdater::kExpectedGupdateXmlns = "http://www.google.com/update2/response"; +// NOTE: HTTPS is used here to ensure the response from omaha can be trusted. +// The response contains a url for fetching the blacklist and a hash value +// for validation. +const char* ExtensionUpdater::kBlacklistUpdateUrl = + "https://clients2.google.com/service/update2/crx"; + +// Update AppID for extesnion blacklist. +const char* ExtensionUpdater::kBlacklistAppID = "com.google.crx.blacklist"; + // Wait at least 5 minutes after browser startup before we do any checks. If you // change this value, make sure to update comments where it is used. const int kStartupWaitSeconds = 60 * 5; @@ -132,6 +143,10 @@ static void EnsureInt64PrefRegistered(PrefService* prefs, prefs->RegisterInt64Pref(name, 0); } +static void EnsureBlacklistVersionPrefRegistered(PrefService* prefs) { + if (!prefs->IsPrefRegistered(kExtensionBlacklistUpdateVersion)) + prefs->RegisterStringPref(kExtensionBlacklistUpdateVersion, L"0"); +} // The overall goal here is to balance keeping clients up to date while // avoiding a thundering herd against update servers. @@ -181,6 +196,7 @@ void ExtensionUpdater::Start() { // Make sure our prefs are registered, then schedule the first check. EnsureInt64PrefRegistered(prefs_, kLastExtensionsUpdateCheck); EnsureInt64PrefRegistered(prefs_, kNextExtensionsUpdateCheck); + EnsureBlacklistVersionPrefRegistered(prefs_); ScheduleNextCheck(DetermineFirstCheckDelay()); } @@ -196,7 +212,6 @@ void ExtensionUpdater::OnURLFetchComplete( const URLFetcher* source, const GURL& url, const URLRequestStatus& status, int response_code, const ResponseCookies& cookies, const std::string& data) { - if (source == manifest_fetcher_.get()) { OnManifestFetchComplete(url, status, response_code, data); } else if (source == extension_fetcher_.get()) { @@ -220,7 +235,8 @@ void ExtensionUpdater::OnManifestFetchComplete(const GURL& url, std::vector<int> updates = DetermineUpdates(parsed.get()); for (size_t i = 0; i < updates.size(); i++) { ParseResult* update = parsed[updates[i]]; - FetchUpdatedExtension(update->extension_id, update->crx_url); + FetchUpdatedExtension(update->extension_id, update->crx_url, + update->package_hash, update->version->GetString()); } } } else { @@ -238,6 +254,30 @@ void ExtensionUpdater::OnManifestFetchComplete(const GURL& url, } } +void ExtensionUpdater::ProcessBlacklist(const std::string& data) { + // Verify sha256 hash value. + char sha256_hash_value[base::SHA256_LENGTH]; + base::SHA256HashString(data, sha256_hash_value, base::SHA256_LENGTH); + std::string hash_in_hex = HexEncode(sha256_hash_value, base::SHA256_LENGTH); + + if (current_extension_fetch_.package_hash != hash_in_hex) { + NOTREACHED() << "Fetched blacklist checksum is not as expected. " + << "Expected: " << current_extension_fetch_.package_hash + << " Actual: " << hash_in_hex; + return; + } + std::vector<std::string> blacklist; + SplitString(data, '\n', &blacklist); + + // Tell ExtensionService to update prefs. + service_->UpdateExtensionBlacklist(blacklist); + + // Update the pref value for blacklist version + prefs_->SetString(kExtensionBlacklistUpdateVersion, + ASCIIToWide(current_extension_fetch_.version)); + prefs_->ScheduleSavePersistentPrefs(); +} + void ExtensionUpdater::OnCRXFetchComplete(const GURL& url, const URLRequestStatus& status, int response_code, @@ -248,11 +288,15 @@ void ExtensionUpdater::OnCRXFetchComplete(const GURL& url, NOTREACHED(); } else if (status.status() == URLRequestStatus::SUCCESS && response_code == 200) { - // Successfully fetched - now write crx to a file so we can have the - // ExtensionsService install it. - file_io_loop_->PostTask(FROM_HERE, NewRunnableMethod( - file_handler_.get(), &ExtensionUpdaterFileHandler::WriteTempFile, - current_extension_fetch_.id, data, this)); + if (current_extension_fetch_.id == kBlacklistAppID) { + ProcessBlacklist(data); + } else { + // Successfully fetched - now write crx to a file so we can have the + // ExtensionsService install it. + file_io_loop_->PostTask(FROM_HERE, NewRunnableMethod( + file_handler_.get(), &ExtensionUpdaterFileHandler::WriteTempFile, + current_extension_fetch_.id, data, this)); + } } else { // TODO(asargent) do things like exponential backoff, handling // 503 Service Unavailable / Retry-After headers, etc. here. @@ -267,7 +311,7 @@ void ExtensionUpdater::OnCRXFetchComplete(const GURL& url, if (extensions_pending_.size() > 0) { ExtensionFetch next = extensions_pending_.front(); extensions_pending_.pop_front(); - FetchUpdatedExtension(next.id, next.url); + FetchUpdatedExtension(next.id, next.url, next.package_hash, next.version); } } @@ -318,6 +362,14 @@ void AppendExtensionInfo(std::string* str, const Extension& extension) { str->append("x=" + EscapeQueryParamValue(JoinString(parts, '&'))); } +// Creates a blacklist update url. +GURL ExtensionUpdater::GetBlacklistUpdateUrl(const std::wstring& version) { + std::string blklist_info = StringPrintf("id=%s&v=%s&uc", kBlacklistAppID, + WideToASCII(version).c_str()); + return GURL(StringPrintf("%s?x=%s", kBlacklistUpdateUrl, + EscapeQueryParamValue(blklist_info).c_str())); +} + void ExtensionUpdater::ScheduleNextCheck(const TimeDelta& target_delay) { DCHECK(!timer_.IsRunning()); DCHECK(target_delay >= TimeDelta::FromSeconds(1)); @@ -340,6 +392,11 @@ void ExtensionUpdater::ScheduleNextCheck(const TimeDelta& target_delay) { void ExtensionUpdater::TimerFired() { // Generate a set of update urls for loaded extensions. std::set<GURL> urls; + + // We always check blacklist update url + urls.insert(GetBlacklistUpdateUrl( + prefs_->GetString(kExtensionBlacklistUpdateVersion))); + const ExtensionList* extensions = service_->extensions(); for (ExtensionList::const_iterator iter = extensions->begin(); iter != extensions->end(); ++iter) { @@ -365,7 +422,6 @@ void ExtensionUpdater::TimerFired() { urls.insert(full_url); } } - // Now do an update check for each url we found. for (std::set<GURL>::iterator iter = urls.begin(); iter != urls.end(); ++iter) { @@ -373,7 +429,6 @@ void ExtensionUpdater::TimerFired() { // scheduled, so we don't need to check before calling it. StartUpdateCheck(*iter); } - // Save the last check time, and schedule the next check. int64 now = Time::Now().ToInternalValue(); prefs_->SetInt64(kLastExtensionsUpdateCheck, now); @@ -518,6 +573,10 @@ class ExtensionUpdater::ParseHelper { return false; } } + + // package_hash is optional. It is only required for blacklist. It is a + // sha256 hash of the package in hex format. + result->package_hash = GetAttribute(updatecheck, "hash"); return true; } }; @@ -577,6 +636,21 @@ bool ExtensionUpdater::Parse(const std::string& manifest_xml, return true; } +bool ExtensionUpdater::GetExistingVersion(const std::string& id, + std::string* version) { + if (id == kBlacklistAppID) { + *version = + WideToASCII(prefs_->GetString(kExtensionBlacklistUpdateVersion)); + return true; + } + Extension* extension = service_->GetExtensionById(id); + if (!extension) { + return false; + } + *version = extension->version()->GetString(); + return true; +} + std::vector<int> ExtensionUpdater::DetermineUpdates( const ParseResultList& possible_updates) { @@ -589,33 +663,36 @@ std::vector<int> ExtensionUpdater::DetermineUpdates( for (size_t i = 0; i < possible_updates.size(); i++) { ParseResult* update = possible_updates[i]; - Extension* extension = service_->GetExtensionById(update->extension_id); - if (!extension) + std::string version; + if (!GetExistingVersion(update->extension_id, &version)) { continue; - + } // If the update version is the same or older than what's already installed, // we don't want it. - if (update->version.get()->CompareTo(*(extension->version())) <= 0) + scoped_ptr<Version> existing_version( + Version::GetVersionFromString(version)); + if (update->version.get()->CompareTo(*(existing_version.get())) <= 0) { continue; + } // If the update specifies a browser minimum version, do we qualify? if (update->browser_min_version.get()) { // First determine the browser version if we haven't already. if (!browser_version.get()) { scoped_ptr<FileVersionInfo> version_info( - FileVersionInfo::CreateFileVersionInfoForCurrentModule()); + FileVersionInfo::CreateFileVersionInfoForCurrentModule()); if (version_info.get()) { browser_version.reset(Version::GetVersionFromString( - version_info->product_version())); + version_info->product_version())); } } if (browser_version.get() && update->browser_min_version->CompareTo(*browser_version.get()) > 0) { // TODO(asargent) - We may want this to show up in the extensions UI // eventually. (http://crbug.com/12547). - LOG(WARNING) << "Updated version of extension " << extension->id() << - " available, but requires chrome version " << - update->browser_min_version->GetString(); + LOG(WARNING) << "Updated version of extension " << update->extension_id + << " available, but requires chrome version " + << update->browser_min_version->GetString(); continue; } } @@ -643,7 +720,9 @@ void ExtensionUpdater::StartUpdateCheck(const GURL& url) { } void ExtensionUpdater::FetchUpdatedExtension(const std::string& id, - const GURL& url) { + const GURL& url, + const std::string& hash, + const std::string& version) { for (std::deque<ExtensionFetch>::const_iterator iter = extensions_pending_.begin(); iter != extensions_pending_.end(); ++iter) { @@ -654,7 +733,7 @@ void ExtensionUpdater::FetchUpdatedExtension(const std::string& id, if (extension_fetcher_.get() != NULL) { if (extension_fetcher_->url() != url) { - extensions_pending_.push_back(ExtensionFetch(id, url)); + extensions_pending_.push_back(ExtensionFetch(id, url, hash, version)); } } else { extension_fetcher_.reset( @@ -662,6 +741,6 @@ void ExtensionUpdater::FetchUpdatedExtension(const std::string& id, extension_fetcher_->set_request_context( Profile::GetDefaultRequestContext()); extension_fetcher_->Start(); - current_extension_fetch_ = ExtensionFetch(id, url); + current_extension_fetch_ = ExtensionFetch(id, url, hash, version); } } diff --git a/chrome/browser/extensions/extension_updater.h b/chrome/browser/extensions/extension_updater.h index 52b4542..c450e8e 100644 --- a/chrome/browser/extensions/extension_updater.h +++ b/chrome/browser/extensions/extension_updater.h @@ -68,7 +68,8 @@ class ExtensionUpdater // <gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'> // <app appid='12345'> // <updatecheck codebase='http://example.com/extension_1.2.3.4.crx' - // version='1.2.3.4' prodversionmin='2.0.143.0' /> + // version='1.2.3.4' prodversionmin='2.0.143.0' + // hash="12345"/> // </app> // </gupdate> // @@ -76,22 +77,29 @@ class ExtensionUpdater // extension. The "codebase" attribute of the <updatecheck> tag is the url to // fetch the updated crx file, and the "prodversionmin" attribute refers to // the minimum version of the chrome browser that the update applies to. + // The hash is only required for blacklist. It is a sha256 hash value against + // the payload in hex format. // The result of parsing one <app> tag in an xml update check manifest. struct ParseResult { std::string extension_id; scoped_ptr<Version> version; scoped_ptr<Version> browser_min_version; + std::string package_hash; GURL crx_url; }; - // We need to keep track of the extension id associated with a url when - // doing a fetch. + // We need to keep track of some information associated with a url + // when doing a fetch. struct ExtensionFetch { std::string id; GURL url; - ExtensionFetch() : id(""), url() {} - ExtensionFetch(const std::string& i, const GURL& u) : id(i), url(u) {} + std::string package_hash; + std::string version; + ExtensionFetch() : id(""), url(), package_hash(""), version("") {} + ExtensionFetch(const std::string& i, const GURL& u, + const std::string& h, const std::string& v) + : id(i), url(u), package_hash(h), version(v) {} }; // These are needed for unit testing, to help identify the correct mock @@ -103,6 +111,9 @@ class ExtensionUpdater static const char* kExpectedGupdateProtocol; static const char* kExpectedGupdateXmlns; + static const char* kBlacklistUpdateUrl; + static const char* kBlacklistAppID; + // Does common work from constructors. void Init(); @@ -134,6 +145,10 @@ class ExtensionUpdater // Callback for when ExtensionsService::Install is finished. void OnExtensionInstallFinished(const FilePath& path, Extension* extension); + // Verifies downloaded blacklist. Based on the blacklist, calls extension + // service to unload blacklisted extensions and update pref. + void ProcessBlacklist(const std::string& data); + // Sets the timer to call TimerFired after roughly |target_delay| from now. // To help spread load evenly on servers, this method adds some random // jitter. It also saves the scheduled time so it can be reloaded on @@ -147,7 +162,12 @@ class ExtensionUpdater void StartUpdateCheck(const GURL& url); // Begins (or queues up) download of an updated extension. - void FetchUpdatedExtension(const std::string& id, const GURL& url); + void FetchUpdatedExtension(const std::string& id, const GURL& url, + const std::string& hash, const std::string& version); + + // Determines the version of an existing extension. + // Returns true on success and false on failures. + bool GetExistingVersion(const std::string& id, std::string* version); typedef std::vector<ParseResult*> ParseResultList; @@ -160,6 +180,9 @@ class ExtensionUpdater // it returns false and puts nothing into |result|. static bool Parse(const std::string& manifest_xml, ParseResultList* result); + // Creates a blacklist update url. + static GURL GetBlacklistUpdateUrl(const std::wstring& version); + // Outstanding url fetch requests for manifests and updates. scoped_ptr<URLFetcher> manifest_fetcher_; scoped_ptr<URLFetcher> extension_fetcher_; diff --git a/chrome/browser/extensions/extension_updater_unittest.cc b/chrome/browser/extensions/extension_updater_unittest.cc index 7c8c2ed..9a55af1 100644 --- a/chrome/browser/extensions/extension_updater_unittest.cc +++ b/chrome/browser/extensions/extension_updater_unittest.cc @@ -5,6 +5,7 @@ #include <map> #include "base/file_util.h" +#include "base/rand_util.h" #include "base/string_util.h" #include "chrome/browser/extensions/extension_updater.h" #include "chrome/browser/extensions/extensions_service.h" @@ -12,6 +13,8 @@ #include "chrome/common/extensions/extension.h" #include "chrome/common/extensions/extension_constants.h" #include "chrome/common/extensions/extension_error_reporter.h" +#include "chrome/common/pref_names.h" +#include "chrome/common/pref_service.h" #include "net/base/escape.h" #include "net/url_request/url_request_status.h" #include "testing/gtest/include/gtest/gtest.h" @@ -25,6 +28,16 @@ const char *valid_xml = " </app>" "</gupdate>"; +const char *valid_xml_with_hash = +"<?xml version='1.0' encoding='UTF-8'?>" +"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>" +" <app appid='12345'>" +" <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'" +" version='1.2.3.4' prodversionmin='2.0.143.0' " +" hash='1234'/>" +" </app>" +"</gupdate>"; + const char* missing_appid = "<?xml version='1.0'?>" "<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>" @@ -105,6 +118,10 @@ class MockService : public ExtensionUpdateService { return NULL; } + virtual void UpdateExtensionBlacklist( + const std::vector<std::string>& blacklist) { + EXPECT_TRUE(false); + } private: DISALLOW_COPY_AND_ASSIGN(MockService); }; @@ -114,7 +131,10 @@ class MockService : public ExtensionUpdateService { class ScopedTempPrefService { public: ScopedTempPrefService() { - FilePath pref_file = temp_dir_.path().AppendASCII("prefs"); + // Make sure different tests won't use the same prefs file. It will cause + // problem when different tests are running in parallel. + FilePath pref_file = temp_dir_.path().AppendASCII( + StringPrintf("prefs_%lld", base::RandUint64())); prefs_.reset(new PrefService(pref_file, NULL)); } @@ -193,6 +213,27 @@ class ServiceForDownloadTests : public MockService { FilePath install_path_; }; +class ServiceForBlacklistTests : public MockService { + public: + ServiceForBlacklistTests() + : MockService(), + processed_blacklist_(false) { + } + virtual void UpdateExtensionBlacklist( + const std::vector<std::string>& blacklist) { + processed_blacklist_ = true; + return; + } + bool processed_blacklist() { return processed_blacklist_; } + const std::string& extension_id() { return extension_id_; } + + private: + bool processed_blacklist_; + std::string extension_id_; + FilePath install_path_; +}; + + static const int kUpdateFrequencySecs = 15; // Takes a string with KEY=VALUE parameters separated by '&' in |params| and @@ -260,6 +301,7 @@ class ExtensionUpdaterTest : public testing::Test { ExtensionUpdater::ParseResult* firstResult = results->at(0); EXPECT_EQ(GURL("http://example.com/extension_1.2.3.4.crx"), firstResult->crx_url); + EXPECT_TRUE(firstResult->package_hash.empty()); scoped_ptr<Version> version(Version::GetVersionFromString("1.2.3.4")); EXPECT_EQ(0, version->CompareTo(*firstResult->version.get())); version.reset(Version::GetVersionFromString("2.0.143.0")); @@ -269,9 +311,16 @@ class ExtensionUpdaterTest : public testing::Test { results.reset(); EXPECT_TRUE(ExtensionUpdater::Parse(uses_namespace_prefix, &results.get())); EXPECT_TRUE(ExtensionUpdater::Parse(similar_tagnames, &results.get())); + + // Parse xml with hash value + results.reset(); + EXPECT_TRUE(ExtensionUpdater::Parse(valid_xml_with_hash, &results.get())); + EXPECT_FALSE(results->empty()); + firstResult = results->at(0); + EXPECT_EQ("1234", firstResult->package_hash); } - static void TestUpdateCheckRequests() { + static void TestExtensionUpdateCheckRequests() { // Create an extension with an update_url. ServiceForManifestTests service; ExtensionList tmp; @@ -319,6 +368,48 @@ class ExtensionUpdaterTest : public testing::Test { STLDeleteElements(&tmp); } + static void TestBlacklistUpdateCheckRequests() { + ServiceForManifestTests service; + + // Setup and start the updater. + TestURLFetcherFactory factory; + URLFetcher::set_factory(&factory); + MessageLoop message_loop; + ScopedTempPrefService prefs; + scoped_refptr<ExtensionUpdater> updater = + new ExtensionUpdater(&service, prefs.get(), 60*60*24, &message_loop); + updater->Start(); + + // Tell the updater that it's time to do update checks. + SimulateTimerFired(updater.get()); + + // Get the url our mock fetcher was asked to fetch. + TestURLFetcher* fetcher = + factory.GetFetcherByID(ExtensionUpdater::kManifestFetcherId); + const GURL& url = fetcher->original_url(); + + EXPECT_FALSE(url.is_empty()); + EXPECT_TRUE(url.is_valid()); + EXPECT_TRUE(url.SchemeIs("https")); + EXPECT_EQ("clients2.google.com", url.host()); + EXPECT_EQ("/service/update2/crx", url.path()); + + // Validate the extension request parameters in the query. It should + // look something like "?x=id%3D<id>%26v%3D<version>%26uc". + EXPECT_TRUE(url.has_query()); + std::vector<std::string> parts; + SplitString(url.query(), '=', &parts); + EXPECT_EQ(2u, parts.size()); + EXPECT_EQ("x", parts[0]); + std::string decoded = UnescapeURLComponent(parts[1], + UnescapeRule::URL_SPECIAL_CHARS); + std::map<std::string, std::string> params; + ExtractParameters(decoded, ¶ms); + EXPECT_EQ("com.google.crx.blacklist", params["id"]); + EXPECT_EQ("0", params["v"]); + EXPECT_EQ("", params["uc"]); + } + static void TestDetermineUpdates() { // Create a set of test extensions ServiceForManifestTests service; @@ -403,7 +494,11 @@ class ExtensionUpdaterTest : public testing::Test { std::string id = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; - updater->FetchUpdatedExtension(id, test_url); + std::string hash = ""; + + std::string version = "0.0.1"; + + updater->FetchUpdatedExtension(id, test_url, hash, version); // Call back the ExtensionUpdater with a 200 response and some test data std::string extension_data("whatever"); @@ -427,6 +522,49 @@ class ExtensionUpdaterTest : public testing::Test { URLFetcher::set_factory(NULL); } + static void TestBlacklistDownloading() { + MessageLoop message_loop; + TestURLFetcherFactory factory; + TestURLFetcher* fetcher = NULL; + URLFetcher::set_factory(&factory); + ServiceForBlacklistTests service; + ScopedTempPrefService prefs; + scoped_refptr<ExtensionUpdater> updater = + new ExtensionUpdater(&service, prefs.get(), kUpdateFrequencySecs, + &message_loop); + prefs.get()-> + RegisterStringPref(prefs::kExtensionBlacklistUpdateVersion, L"0"); + GURL test_url("http://localhost/extension.crx"); + + std::string id = "com.google.crx.blacklist"; + + std::string hash = + "2CE109E9D0FAF820B2434E166297934E6177B65AB9951DBC3E204CAD4689B39C"; + + std::string version = "0.0.1"; + + updater->FetchUpdatedExtension(id, test_url, hash, version); + + // Call back the ExtensionUpdater with a 200 response and some test data + std::string extension_data("aaabbb"); + fetcher = factory.GetFetcherByID(ExtensionUpdater::kExtensionFetcherId); + EXPECT_TRUE(fetcher != NULL && fetcher->delegate() != NULL); + fetcher->delegate()->OnURLFetchComplete( + fetcher, test_url, URLRequestStatus(), 200, ResponseCookies(), + extension_data); + + message_loop.RunAllPending(); + + // The updater should have called extension service to process the + // blacklist. + EXPECT_TRUE(service.processed_blacklist()); + + EXPECT_EQ(version, WideToASCII(prefs.get()-> + GetString(prefs::kExtensionBlacklistUpdateVersion))); + + URLFetcher::set_factory(NULL); + } + static void TestMultipleExtensionDownloading() { MessageLoopForUI message_loop; TestURLFetcherFactory factory; @@ -444,9 +582,14 @@ class ExtensionUpdaterTest : public testing::Test { std::string id1 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; std::string id2 = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; + std::string hash1 = ""; + std::string hash2 = ""; + + std::string version1 = "0.1"; + std::string version2 = "0.1"; // Start two fetches - updater->FetchUpdatedExtension(id1, url1); - updater->FetchUpdatedExtension(id2, url2); + updater->FetchUpdatedExtension(id1, url1, hash1, version1); + updater->FetchUpdatedExtension(id2, url2, hash2, version2); // Make the first fetch complete. std::string extension_data1("whatever"); @@ -491,8 +634,12 @@ TEST(ExtensionUpdaterTest, TestXmlParsing) { ExtensionUpdaterTest::TestXmlParsing(); } -TEST(ExtensionUpdaterTest, TestUpdateCheckRequests) { - ExtensionUpdaterTest::TestUpdateCheckRequests(); +TEST(ExtensionUpdaterTest, TestExtensionUpdateCheckRequests) { + ExtensionUpdaterTest::TestExtensionUpdateCheckRequests(); +} + +TEST(ExtensionUpdaterTest, TestBlacklistUpdateCheckRequests) { + ExtensionUpdaterTest::TestBlacklistUpdateCheckRequests(); } TEST(ExtensionUpdaterTest, TestDetermineUpdates) { @@ -507,6 +654,10 @@ TEST(ExtensionUpdaterTest, TestSingleExtensionDownloading) { ExtensionUpdaterTest::TestSingleExtensionDownloading(); } +TEST(ExtensionUpdaterTest, TestBlacklistDownloading) { + ExtensionUpdaterTest::TestBlacklistDownloading(); +} + TEST(ExtensionUpdaterTest, TestMultipleExtensionDownloading) { ExtensionUpdaterTest::TestMultipleExtensionDownloading(); } diff --git a/chrome/browser/extensions/extensions_service.cc b/chrome/browser/extensions/extensions_service.cc index 1b83a61..70fa3db 100644 --- a/chrome/browser/extensions/extensions_service.cc +++ b/chrome/browser/extensions/extensions_service.cc @@ -63,10 +63,11 @@ ExtensionsService::ExtensionsService(Profile* profile, show_extensions_prompts_(true), ready_(false) { // Figure out if extension installation should be enabled. - if (command_line->HasSwitch(switches::kEnableExtensions)) + if (command_line->HasSwitch(switches::kEnableExtensions)) { extensions_enabled_ = true; - else if (profile->GetPrefs()->GetBoolean(prefs::kEnableExtensions)) + } else if (profile->GetPrefs()->GetBoolean(prefs::kEnableExtensions)) { extensions_enabled_ = true; + } // Set up the ExtensionUpdater if (autoupdate_enabled) { @@ -172,6 +173,33 @@ void ExtensionsService::LoadAllExtensions() { new InstalledExtensions(extension_prefs_.get()))); } +void ExtensionsService::UpdateExtensionBlacklist( + const std::vector<std::string>& blacklist) { + // Use this set to indicate if an extension in the blacklist has been used. + std::set<std::string> blacklist_set; + for (unsigned int i = 0; i < blacklist.size(); ++i) { + if (Extension::IdIsValid(blacklist[i])) { + blacklist_set.insert(blacklist[i]); + } + } + extension_prefs_->UpdateBlacklist(blacklist_set); + std::vector<std::string> to_be_removed; + // Loop current extensions, unload installed extensions. + for (ExtensionList::const_iterator iter = extensions_.begin(); + iter != extensions_.end(); ++iter) { + Extension* extension = (*iter); + if (blacklist_set.find(extension->id()) != blacklist_set.end()) { + to_be_removed.push_back(extension->id()); + } + } + + // UnloadExtension will change the extensions_ list. So, we should + // call it outside the iterator loop. + for (unsigned int i = 0; i < to_be_removed.size(); ++i) { + UnloadExtension(to_be_removed[i]); + } +} + void ExtensionsService::CheckForExternalUpdates() { // This installs or updates externally provided extensions. // TODO(aa): Why pass this list into the provider, why not just filter it diff --git a/chrome/browser/extensions/extensions_service.h b/chrome/browser/extensions/extensions_service.h index 640700e..fe0a545 100644 --- a/chrome/browser/extensions/extensions_service.h +++ b/chrome/browser/extensions/extensions_service.h @@ -45,6 +45,8 @@ class ExtensionUpdateService { virtual const ExtensionList* extensions() const = 0; virtual void UpdateExtension(const std::string& id, const FilePath& path) = 0; virtual Extension* GetExtensionById(const std::string& id) = 0; + virtual void UpdateExtensionBlacklist( + const std::vector<std::string>& blacklist) = 0; }; // Manages installed and running Chromium extensions. @@ -175,6 +177,11 @@ class ExtensionsService const FilePath& path, Extension::Location location); + // Go through each extensions in pref, unload blacklisted extensions + // and update the blacklist state in pref. + virtual void UpdateExtensionBlacklist( + const std::vector<std::string>& blacklist); + void set_extensions_enabled(bool enabled) { extensions_enabled_ = enabled; } bool extensions_enabled() { return extensions_enabled_; } diff --git a/chrome/browser/extensions/extensions_service_unittest.cc b/chrome/browser/extensions/extensions_service_unittest.cc index 0356ed6..3f464d5 100644 --- a/chrome/browser/extensions/extensions_service_unittest.cc +++ b/chrome/browser/extensions/extensions_service_unittest.cc @@ -386,9 +386,49 @@ class ExtensionsServiceTest EXPECT_EQ(count, dict->GetSize()); } - void ValidatePref(const std::string& extension_id, - const std::wstring& pref_path, - int must_equal) { + void ValidateBooleanPref(const std::string& extension_id, + const std::wstring& pref_path, + bool must_equal) { + std::wstring msg = L" while checking: "; + msg += ASCIIToWide(extension_id); + msg += L" "; + msg += pref_path; + msg += L" == "; + msg += must_equal ? L"true" : L"false"; + + const DictionaryValue* dict = + prefs_->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; + bool val; + pref->GetBoolean(pref_path, &val); + EXPECT_EQ(must_equal, val) << msg; + } + + bool IsPrefExist(const std::string& extension_id, + const std::wstring& pref_path) { + const DictionaryValue* dict = + prefs_->GetDictionary(L"extensions.settings"); + if (dict == NULL) return false; + DictionaryValue* pref = NULL; + if (!dict->GetDictionary(ASCIIToWide(extension_id), &pref)) { + return false; + } + if (pref == NULL) { + return false; + } + bool val; + if (!pref->GetBoolean(pref_path, &val)) { + return false; + } + return true; + } + + void ValidateIntegerPref(const std::string& extension_id, + const std::wstring& pref_path, + int must_equal) { std::wstring msg = L" while checking: "; msg += ASCIIToWide(extension_id); msg += L" "; @@ -407,9 +447,9 @@ class ExtensionsServiceTest EXPECT_EQ(must_equal, val) << msg; } - void SetPref(const std::string& extension_id, - const std::wstring& pref_path, - int value) { + void SetPrefInteg(const std::string& extension_id, + const std::wstring& pref_path, + int value) { std::wstring msg = L" while setting: "; msg += ASCIIToWide(extension_id); msg += L" "; @@ -485,12 +525,12 @@ TEST_F(ExtensionsServiceTest, LoadAllExtensionsFromDirectorySuccess) { EXPECT_EQ(3u, service_->extensions()->size()); ValidatePrefKeyCount(3); - ValidatePref(good0, L"state", Extension::ENABLED); - ValidatePref(good0, 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); + ValidateIntegerPref(good0, L"state", Extension::ENABLED); + ValidateIntegerPref(good0, L"location", Extension::INTERNAL); + ValidateIntegerPref(good1, L"state", Extension::ENABLED); + ValidateIntegerPref(good1, L"location", Extension::INTERNAL); + ValidateIntegerPref(good2, L"state", Extension::ENABLED); + ValidateIntegerPref(good2, L"location", Extension::INTERNAL); Extension* extension = loaded_[0]; const UserScriptList& scripts = extension->content_scripts(); @@ -658,15 +698,15 @@ TEST_F(ExtensionsServiceTest, InstallExtension) { int pref_count = 0; ValidatePrefKeyCount(++pref_count); - ValidatePref(good_crx, L"state", Extension::ENABLED); - ValidatePref(good_crx, L"location", Extension::INTERNAL); + ValidateIntegerPref(good_crx, L"state", Extension::ENABLED); + ValidateIntegerPref(good_crx, L"location", Extension::INTERNAL); // An extension with page actions. path = extensions_path.AppendASCII("page_action.crx"); InstallExtension(path, true); ValidatePrefKeyCount(++pref_count); - ValidatePref(page_action, L"state", Extension::ENABLED); - ValidatePref(page_action, L"location", Extension::INTERNAL); + ValidateIntegerPref(page_action, L"state", Extension::ENABLED); + ValidateIntegerPref(page_action, L"location", Extension::INTERNAL); // Bad signature. path = extensions_path.AppendASCII("bad_signature.crx"); @@ -764,8 +804,8 @@ TEST_F(ExtensionsServiceTest, InstallTheme) { InstallExtension(path, true); int pref_count = 0; ValidatePrefKeyCount(++pref_count); - ValidatePref(theme_crx, L"state", Extension::ENABLED); - ValidatePref(theme_crx, L"location", Extension::INTERNAL); + ValidateIntegerPref(theme_crx, L"state", Extension::ENABLED); + ValidateIntegerPref(theme_crx, L"location", Extension::INTERNAL); // A theme when extensions are disabled. Themes can be installed, even when // extensions are disabled. @@ -773,8 +813,8 @@ TEST_F(ExtensionsServiceTest, InstallTheme) { path = extensions_path.AppendASCII("theme2.crx"); InstallExtension(path, true); ValidatePrefKeyCount(++pref_count); - ValidatePref(theme2_crx, L"state", Extension::ENABLED); - ValidatePref(theme2_crx, L"location", Extension::INTERNAL); + ValidateIntegerPref(theme2_crx, L"state", Extension::ENABLED); + ValidateIntegerPref(theme2_crx, L"location", Extension::INTERNAL); // A theme with extension elements. Themes cannot have extension elements so // this test should fail. @@ -805,8 +845,8 @@ TEST_F(ExtensionsServiceTest, Reinstall) { 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); + ValidateIntegerPref(good_crx, L"state", Extension::ENABLED); + ValidateIntegerPref(good_crx, L"location", Extension::INTERNAL); installed_ = NULL; loaded_.clear(); @@ -820,8 +860,8 @@ TEST_F(ExtensionsServiceTest, Reinstall) { 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); + ValidateIntegerPref(good_crx, L"state", Extension::ENABLED); + ValidateIntegerPref(good_crx, L"location", Extension::INTERNAL); } // Test upgrading a signed extension. @@ -948,6 +988,130 @@ TEST_F(ExtensionsServiceTest, UpdateToSameVersionIsNoop) { UpdateExtension(good_crx, path, false, false); } +// Test pref settings for blacklist and unblacklist extensions. +TEST_F(ExtensionsServiceTest, SetUnsetBlacklistInPrefs) { + InitializeEmptyExtensionsService(); + std::vector<std::string> blacklist; + blacklist.push_back(good0); + blacklist.push_back("invalid_id"); // an invalid id + blacklist.push_back(good1); + service_->UpdateExtensionBlacklist(blacklist); + // Make sure pref is updated + loop_.RunAllPending(); + + // blacklist is set for good0,1,2 + ValidateBooleanPref(good0, L"blacklist", true); + ValidateBooleanPref(good1, L"blacklist", true); + // invalid_id should not be inserted to pref. + EXPECT_FALSE(IsPrefExist("invalid_id", L"blacklist")); + + // remove good1, add good2 + blacklist.pop_back(); + blacklist.push_back(good2); + + service_->UpdateExtensionBlacklist(blacklist); + // only good0 and good1 should be set + ValidateBooleanPref(good0, L"blacklist", true); + EXPECT_FALSE(IsPrefExist(good1, L"blacklist")); + ValidateBooleanPref(good2, L"blacklist", true); + EXPECT_FALSE(IsPrefExist("invalid_id", L"blacklist")); +} + +// Unload installed extension from blacklist. +TEST_F(ExtensionsServiceTest, UnloadBlacklistedExtension) { + InitializeEmptyExtensionsService(); + FilePath extensions_path; + EXPECT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path)); + extensions_path = extensions_path.AppendASCII("extensions"); + + FilePath path = extensions_path.AppendASCII("good.crx"); + + InstallExtension(path, true); + Extension* good = service_->extensions()->at(0); + EXPECT_EQ(good_crx, good->id()); + UpdateExtension(good_crx, path, false, false); + + std::vector<std::string> blacklist; + blacklist.push_back(good_crx); + service_->UpdateExtensionBlacklist(blacklist); + // Make sure pref is updated + loop_.RunAllPending(); + + // Now, the good_crx is blacklisted. + ValidateBooleanPref(good_crx, L"blacklist", true); + EXPECT_EQ(0u, service_->extensions()->size()); + + // Remove good_crx from blacklist + blacklist.pop_back(); + service_->UpdateExtensionBlacklist(blacklist); + // Make sure pref is updated + loop_.RunAllPending(); + // blacklist value should not be set for good_crx + EXPECT_FALSE(IsPrefExist(good_crx, L"blacklist")); +} + +// Unload installed extension from blacklist. +TEST_F(ExtensionsServiceTest, BlacklistedExtensionWillNotInstall) { + InitializeEmptyExtensionsService(); + std::vector<std::string> blacklist; + blacklist.push_back(good_crx); + service_->UpdateExtensionBlacklist(blacklist); + // Make sure pref is updated + loop_.RunAllPending(); + + // Now, the good_crx is blacklisted. + ValidateBooleanPref(good_crx, L"blacklist", true); + + // We can not install good_crx. + FilePath extensions_path; + ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path)); + extensions_path = extensions_path.AppendASCII("extensions"); + FilePath path = extensions_path.AppendASCII("good.crx"); + service_->InstallExtension(path); + loop_.RunAllPending(); + EXPECT_EQ(0u, service_->extensions()->size()); + ValidateBooleanPref(good_crx, L"blacklist", true); +} + +// Test loading extensions from the profile directory, except +// blacklisted ones. +TEST_F(ExtensionsServiceTest, WillNotLoadBlacklistedExtensionsFromDirectory) { + // Initialize the test dir with a good Preferences/extensions. + FilePath source_install_dir; + ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &source_install_dir)); + source_install_dir = source_install_dir + .AppendASCII("extensions") + .AppendASCII("good") + .AppendASCII("Extensions"); + FilePath pref_path = source_install_dir + .DirName() + .AppendASCII("Preferences"); + InitializeInstalledExtensionsService(pref_path, source_install_dir); + + // Blacklist good0. + std::vector<std::string> blacklist; + blacklist.push_back(good0); + service_->UpdateExtensionBlacklist(blacklist); + // Make sure pref is updated + loop_.RunAllPending(); + + ValidateBooleanPref(good0, L"blacklist", true); + + // Load extensions. + service_->Init(); + loop_.RunAllPending(); + + std::vector<std::string> errors = GetErrors(); + for (std::vector<std::string>::iterator err = errors.begin(); + err != errors.end(); ++err) { + LOG(ERROR) << *err; + } + EXPECT_EQ(2u, loaded_.size()); + + EXPECT_NE(std::string(good0), loaded_[0]->id()); + EXPECT_NE(std::string(good0), loaded_[1]->id()); +} + // Tests uninstalling normal extensions TEST_F(ExtensionsServiceTest, UninstallExtension) { InitializeEmptyExtensionsService(); @@ -965,8 +1129,8 @@ TEST_F(ExtensionsServiceTest, UninstallExtension) { EXPECT_TRUE(file_util::PathExists(extension_path)); ValidatePrefKeyCount(1); - ValidatePref(good_crx, L"state", Extension::ENABLED); - ValidatePref(good_crx, L"location", Extension::INTERNAL); + ValidateIntegerPref(good_crx, L"state", Extension::ENABLED); + ValidateIntegerPref(good_crx, L"location", Extension::INTERNAL); // Uninstall it. service_->UninstallExtension(extension_id, false); @@ -1113,8 +1277,8 @@ TEST_F(ExtensionsServiceTest, ExternalInstallRegistry) { 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); + ValidateIntegerPref(good_crx, L"state", Extension::ENABLED); + ValidateIntegerPref(good_crx, L"location", Extension::EXTERNAL_REGISTRY); // Reload extensions without changing anything. The extension should be // loaded again. @@ -1124,8 +1288,8 @@ TEST_F(ExtensionsServiceTest, ExternalInstallRegistry) { 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); + ValidateIntegerPref(good_crx, L"state", Extension::ENABLED); + ValidateIntegerPref(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"); @@ -1138,8 +1302,8 @@ TEST_F(ExtensionsServiceTest, ExternalInstallRegistry) { 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); + ValidateIntegerPref(good_crx, L"state", Extension::ENABLED); + ValidateIntegerPref(good_crx, L"location", Extension::EXTERNAL_REGISTRY); // Uninstall the extension and reload. Nothing should happen because the // preference should prevent us from reinstalling. @@ -1156,12 +1320,12 @@ TEST_F(ExtensionsServiceTest, ExternalInstallRegistry) { 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); + ValidateIntegerPref(good_crx, L"state", Extension::KILLBIT); + ValidateIntegerPref(good_crx, L"location", Extension::EXTERNAL_REGISTRY); // Now clear the preference, reinstall, then remove the reg key. The extension // should be uninstalled. - SetPref(good_crx, L"state", Extension::ENABLED); + SetPrefInteg(good_crx, L"state", Extension::ENABLED); prefs_->ScheduleSavePersistentPrefs(); loaded_.clear(); @@ -1169,8 +1333,8 @@ TEST_F(ExtensionsServiceTest, ExternalInstallRegistry) { loop_.RunAllPending(); ASSERT_EQ(1u, loaded_.size()); ValidatePrefKeyCount(1); - ValidatePref(good_crx, L"state", Extension::ENABLED); - ValidatePref(good_crx, L"location", Extension::EXTERNAL_REGISTRY); + ValidateIntegerPref(good_crx, L"state", Extension::ENABLED); + ValidateIntegerPref(good_crx, L"location", Extension::EXTERNAL_REGISTRY); // Now test an externally triggered uninstall (deleting the registry key). reg_provider->RemoveExtension(good_crx); @@ -1217,8 +1381,8 @@ TEST_F(ExtensionsServiceTest, ExternalInstallPref) { 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); + ValidateIntegerPref(good_crx, L"state", Extension::ENABLED); + ValidateIntegerPref(good_crx, L"location", Extension::EXTERNAL_PREF); // Reload extensions without changing anything. The extension should be // loaded again. @@ -1228,8 +1392,8 @@ TEST_F(ExtensionsServiceTest, ExternalInstallPref) { 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); + ValidateIntegerPref(good_crx, L"state", Extension::ENABLED); + ValidateIntegerPref(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"); @@ -1242,8 +1406,8 @@ TEST_F(ExtensionsServiceTest, ExternalInstallPref) { 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); + ValidateIntegerPref(good_crx, L"state", Extension::ENABLED); + ValidateIntegerPref(good_crx, L"location", Extension::EXTERNAL_PREF); // Uninstall the extension and reload. Nothing should happen because the // preference should prevent us from reinstalling. @@ -1260,11 +1424,11 @@ TEST_F(ExtensionsServiceTest, ExternalInstallPref) { loop_.RunAllPending(); ASSERT_EQ(0u, loaded_.size()); ValidatePrefKeyCount(1); - ValidatePref(good_crx, L"state", Extension::KILLBIT); - ValidatePref(good_crx, L"location", Extension::EXTERNAL_PREF); + ValidateIntegerPref(good_crx, L"state", Extension::KILLBIT); + ValidateIntegerPref(good_crx, L"location", Extension::EXTERNAL_PREF); // Now clear the preference and reinstall. - SetPref(good_crx, L"state", Extension::ENABLED); + SetPrefInteg(good_crx, L"state", Extension::ENABLED); prefs_->ScheduleSavePersistentPrefs(); loaded_.clear(); @@ -1272,8 +1436,8 @@ TEST_F(ExtensionsServiceTest, ExternalInstallPref) { loop_.RunAllPending(); ASSERT_EQ(1u, loaded_.size()); ValidatePrefKeyCount(1); - ValidatePref(good_crx, L"state", Extension::ENABLED); - ValidatePref(good_crx, L"location", Extension::EXTERNAL_PREF); + ValidateIntegerPref(good_crx, L"state", Extension::ENABLED); + ValidateIntegerPref(good_crx, L"location", Extension::EXTERNAL_PREF); // Now test an externally triggered uninstall (deleting id from json file). pref_provider->RemoveExtension(good_crx); diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc index 2414dc1..c86671f 100644 --- a/chrome/common/pref_names.cc +++ b/chrome/common/pref_names.cc @@ -532,6 +532,9 @@ const wchar_t kLastExtensionsUpdateCheck[] = L"extensions.autoupdate.last_check"; const wchar_t kNextExtensionsUpdateCheck[] = L"extensions.autoupdate.next_check"; +// Version number of last blacklist check +const wchar_t kExtensionBlacklistUpdateVersion[] = + L"extensions.blacklistupdate.version"; // New Tab Page URLs that should not be shown as most visited thumbnails. const wchar_t kNTPMostVisitedURLsBlacklist[] = L"ntp.most_visited_blacklist"; diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h index d620347..94e8a5e 100644 --- a/chrome/common/pref_names.h +++ b/chrome/common/pref_names.h @@ -200,6 +200,8 @@ extern const wchar_t kEnableUserScripts[]; extern const wchar_t kLastExtensionsUpdateCheck[]; extern const wchar_t kNextExtensionsUpdateCheck[]; +extern const wchar_t kExtensionBlacklistUpdateVersion[]; + extern const wchar_t kNTPMostVisitedURLsBlacklist[]; extern const wchar_t kNTPMostVisitedPinnedURLs[]; extern const wchar_t kNTPTipsCache[]; |