// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include <map> #include "base/file_util.h" #include "base/memory/scoped_ptr.h" #include "base/stl_util-inl.h" #include "base/string_number_conversions.h" #include "base/string_split.h" #include "base/string_util.h" #include "base/stringprintf.h" #include "base/threading/thread.h" #include "base/version.h" #include "chrome/browser/extensions/extension_error_reporter.h" #include "chrome/browser/extensions/extension_updater.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/test_extension_prefs.h" #include "chrome/browser/prefs/pref_service.h" #include "chrome/common/extensions/extension.h" #include "chrome/common/extensions/extension_constants.h" #include "chrome/common/pref_names.h" #include "chrome/common/net/test_url_fetcher_factory.h" #include "chrome/test/testing_profile.h" #include "content/browser/browser_thread.h" #include "net/base/escape.h" #include "net/base/load_flags.h" #include "net/url_request/url_request_status.h" #include "testing/gtest/include/gtest/gtest.h" #include "libxml/globals.h" using base::Time; using base::TimeDelta; namespace { const char kEmptyUpdateUrlData[] = ""; int expected_load_flags = net::LOAD_DO_NOT_SEND_COOKIES | net::LOAD_DO_NOT_SAVE_COOKIES | net::LOAD_DISABLE_CACHE; const ManifestFetchData::PingData kNeverPingedData( ManifestFetchData::kNeverPinged, ManifestFetchData::kNeverPinged); } // namespace // Base class for further specialized test classes. class MockService : public ExtensionServiceInterface { public: MockService() : pending_extension_manager_(ALLOW_THIS_IN_INITIALIZER_LIST(*this)) {} virtual ~MockService() {} virtual const ExtensionList* extensions() const { ADD_FAILURE(); return NULL; } virtual const ExtensionList* disabled_extensions() const { ADD_FAILURE(); return NULL; } virtual void UpdateExtension(const std::string& id, const FilePath& path, const GURL& download_url) { FAIL(); } virtual const Extension* GetExtensionById(const std::string& id, bool include_disabled) const { ADD_FAILURE(); return NULL; } virtual bool UninstallExtension(const std::string& extension_id, bool external_uninstall, std::string* error) { ADD_FAILURE(); return false; } virtual bool IsExtensionEnabled(const std::string& extension_id) const { ADD_FAILURE(); return false; } virtual bool IsExternalExtensionUninstalled( const std::string& extension_id) const { ADD_FAILURE(); return false; } virtual void EnableExtension(const std::string& extension_id) { FAIL(); } virtual void DisableExtension(const std::string& extension_id) { FAIL(); } virtual void UpdateExtensionBlacklist( const std::vector<std::string>& blacklist) { FAIL(); } virtual void CheckAdminBlacklist() { FAIL(); } virtual bool IsIncognitoEnabled(const std::string& id) const { ADD_FAILURE(); return false; } virtual void SetIsIncognitoEnabled(const std::string& id, bool enabled) { FAIL(); } virtual void CheckForUpdatesSoon() { FAIL(); } virtual PendingExtensionManager* pending_extension_manager() { ADD_FAILURE() << "Subclass should override this if it will " << "be accessed by a test."; return &pending_extension_manager_; } virtual void ProcessSyncData( const ExtensionSyncData& extension_sync_data, PendingExtensionInfo::ShouldAllowInstallPredicate should_allow_install) { FAIL(); } Profile* profile() { return &profile_; } ExtensionPrefs* extension_prefs() { return prefs_.prefs(); } PrefService* pref_service() { return prefs_.pref_service(); } // Creates test extensions and inserts them into list. The name and // version are all based on their index. If |update_url| is non-null, it // will be used as the update_url for each extension. // The |id| is used to distinguish extension names and make sure that // no two extensions share the same name. void CreateTestExtensions(int id, int count, ExtensionList *list, const std::string* update_url, Extension::Location location) { for (int i = 1; i <= count; i++) { DictionaryValue manifest; manifest.SetString(extension_manifest_keys::kVersion, base::StringPrintf("%d.0.0.0", i)); manifest.SetString(extension_manifest_keys::kName, base::StringPrintf("Extension %d.%d", id, i)); if (update_url) manifest.SetString(extension_manifest_keys::kUpdateURL, *update_url); scoped_refptr<Extension> e = prefs_.AddExtensionWithManifest(manifest, location); ASSERT_TRUE(e != NULL); list->push_back(e); } } protected: PendingExtensionManager pending_extension_manager_; TestExtensionPrefs prefs_; TestingProfile profile_; private: DISALLOW_COPY_AND_ASSIGN(MockService); }; std::string GenerateId(std::string input) { std::string result; EXPECT_TRUE(Extension::GenerateId(input, &result)); return result; } bool ShouldInstallExtensionsOnly(const Extension& extension) { return extension.GetType() == Extension::TYPE_EXTENSION; } bool ShouldInstallThemesOnly(const Extension& extension) { return extension.is_theme(); } bool ShouldAlwaysInstall(const Extension& extension) { return true; } // Loads some pending extension records into a pending extension manager. void SetupPendingExtensionManagerForTest( int count, const GURL& update_url, PendingExtensionManager* pending_extension_manager) { for (int i = 1; i <= count; i++) { PendingExtensionInfo::ShouldAllowInstallPredicate should_allow_install = (i % 2 == 0) ? &ShouldInstallThemesOnly : &ShouldInstallExtensionsOnly; const bool kIsFromSync = true; const bool kInstallSilently = true; const Extension::State kInitialState = Extension::ENABLED; const bool kInitialIncognitoEnabled = false; std::string id = GenerateId(base::StringPrintf("extension%i", i)); pending_extension_manager->AddForTesting( id, PendingExtensionInfo(update_url, should_allow_install, kIsFromSync, kInstallSilently, kInitialState, kInitialIncognitoEnabled, Extension::INTERNAL)); } } class ServiceForManifestTests : public MockService { public: ServiceForManifestTests() {} virtual ~ServiceForManifestTests() {} virtual const Extension* GetExtensionById(const std::string& id, bool include_disabled) const { for (ExtensionList::const_iterator iter = extensions_.begin(); iter != extensions_.end(); ++iter) { if ((*iter)->id() == id) { return *iter; } } return NULL; } virtual const ExtensionList* extensions() const { return &extensions_; } virtual PendingExtensionManager* pending_extension_manager() { return &pending_extension_manager_; } void set_extensions(ExtensionList extensions) { extensions_ = extensions; } private: ExtensionList extensions_; }; class ServiceForDownloadTests : public MockService { public: virtual void UpdateExtension(const std::string& id, const FilePath& extension_path, const GURL& download_url) { extension_id_ = id; install_path_ = extension_path; download_url_ = download_url; } virtual PendingExtensionManager* pending_extension_manager() { return &pending_extension_manager_; } virtual const Extension* GetExtensionById(const std::string& id, bool) const { last_inquired_extension_id_ = id; return NULL; } const std::string& extension_id() const { return extension_id_; } const FilePath& install_path() const { return install_path_; } const GURL& download_url() const { return download_url_; } const std::string& last_inquired_extension_id() const { return last_inquired_extension_id_; } private: std::string extension_id_; FilePath install_path_; GURL download_url_; // The last extension ID that GetExtensionById was called with. // Mutable because the method that sets it (GetExtensionById) is const // in the actual extension service, but must record the last extension // ID in this test class. mutable std::string last_inquired_extension_id_; }; 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 // puts the key/value pairs into |result|. For keys with no value, the empty // string is used. So for "a=1&b=foo&c", result would map "a" to "1", "b" to // "foo", and "c" to "". static void ExtractParameters(const std::string& params, std::map<std::string, std::string>* result) { std::vector<std::string> pairs; base::SplitString(params, '&', &pairs); for (size_t i = 0; i < pairs.size(); i++) { std::vector<std::string> key_val; base::SplitString(pairs[i], '=', &key_val); if (!key_val.empty()) { std::string key = key_val[0]; EXPECT_TRUE(result->find(key) == result->end()); (*result)[key] = (key_val.size() == 2) ? key_val[1] : ""; } else { NOTREACHED(); } } } // All of our tests that need to use private APIs of ExtensionUpdater live // inside this class (which is a friend to ExtensionUpdater). class ExtensionUpdaterTest : public testing::Test { public: static void SimulateTimerFired(ExtensionUpdater* updater) { EXPECT_TRUE(updater->timer_.IsRunning()); updater->timer_.Stop(); updater->TimerFired(); } static void SimulateCheckSoon(const ExtensionUpdater& updater, MessageLoop* message_loop) { EXPECT_TRUE(updater.will_check_soon_); message_loop->RunAllPending(); } // Adds a Result with the given data to results. static void AddParseResult( const std::string& id, const std::string& version, const std::string& url, UpdateManifest::Results* results) { UpdateManifest::Result result; result.extension_id = id; result.version = version; result.crx_url = GURL(url); results->list.push_back(result); } static void TestExtensionUpdateCheckRequests(bool pending) { MessageLoop message_loop; BrowserThread ui_thread(BrowserThread::UI, &message_loop); BrowserThread file_thread(BrowserThread::FILE, &message_loop); BrowserThread io_thread(BrowserThread::IO); io_thread.Start(); // Create an extension with an update_url. ServiceForManifestTests service; std::string update_url("http://foo.com/bar"); ExtensionList extensions; PendingExtensionManager* pending_extension_manager = service.pending_extension_manager(); if (pending) { SetupPendingExtensionManagerForTest(1, GURL(update_url), pending_extension_manager); } else { service.CreateTestExtensions(1, 1, &extensions, &update_url, Extension::INTERNAL); service.set_extensions(extensions); } // Set up and start the updater. TestURLFetcherFactory factory; URLFetcher::set_factory(&factory); ExtensionUpdater updater( &service, service.extension_prefs(), service.pref_service(), service.profile(), 60*60*24); updater.Start(); // Disable blacklist checks (tested elsewhere) so that we only see the // update HTTP request. updater.set_blacklist_checks_enabled(false); // Tell the update that it's time to do update checks. SimulateTimerFired(&updater); // 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("http")); EXPECT_EQ("foo.com", url.host()); EXPECT_EQ("/bar", 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; base::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); if (pending) { EXPECT_EQ(pending_extension_manager->begin()->first, params["id"]); EXPECT_EQ("0.0.0.0", params["v"]); } else { EXPECT_EQ(extensions[0]->id(), params["id"]); EXPECT_EQ(extensions[0]->VersionString(), params["v"]); } EXPECT_EQ("", params["uc"]); } static void TestBlacklistUpdateCheckRequests() { ServiceForManifestTests service; // Setup and start the updater. MessageLoop message_loop; BrowserThread ui_thread(BrowserThread::UI, &message_loop); BrowserThread io_thread(BrowserThread::IO); io_thread.Start(); TestURLFetcherFactory factory; URLFetcher::set_factory(&factory); ExtensionUpdater updater( &service, service.extension_prefs(), service.pref_service(), service.profile(), 60*60*24); updater.Start(); // Tell the updater that it's time to do update checks. SimulateTimerFired(&updater); // Get the url our mock fetcher was asked to fetch. TestURLFetcher* fetcher = factory.GetFetcherByID(ExtensionUpdater::kManifestFetcherId); ASSERT_FALSE(fetcher == NULL); 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; base::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"]); EXPECT_TRUE(ContainsKey(params, "ping")); } static void TestUpdateUrlDataEmpty() { const std::string id = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; const std::string version = "1.0"; // Make sure that an empty update URL data string does not cause a ap= // option to appear in the x= parameter. ManifestFetchData fetch_data(GURL("http://localhost/foo")); fetch_data.AddExtension(id, version, kNeverPingedData, ""); EXPECT_EQ("http://localhost/foo\?x=id%3Daaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" "%26v%3D1.0%26uc", fetch_data.full_url().spec()); } static void TestUpdateUrlDataSimple() { const std::string id = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; const std::string version = "1.0"; // Make sure that an update URL data string causes an appropriate ap= // option to appear in the x= parameter. ManifestFetchData fetch_data(GURL("http://localhost/foo")); fetch_data.AddExtension(id, version, kNeverPingedData, "bar"); EXPECT_EQ("http://localhost/foo\?x=id%3Daaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" "%26v%3D1.0%26uc%26ap%3Dbar", fetch_data.full_url().spec()); } static void TestUpdateUrlDataCompound() { const std::string id = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; const std::string version = "1.0"; // Make sure that an update URL data string causes an appropriate ap= // option to appear in the x= parameter. ManifestFetchData fetch_data(GURL("http://localhost/foo")); fetch_data.AddExtension(id, version, kNeverPingedData, "a=1&b=2&c"); EXPECT_EQ("http://localhost/foo\?x=id%3Daaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" "%26v%3D1.0%26uc%26ap%3Da%253D1%2526b%253D2%2526c", fetch_data.full_url().spec()); } static void TestUpdateUrlDataFromGallery(const std::string& gallery_url) { MockService service; ManifestFetchesBuilder builder(&service, service.extension_prefs()); ExtensionList extensions; std::string url(gallery_url); service.CreateTestExtensions(1, 1, &extensions, &url, Extension::INTERNAL); builder.AddExtension(*extensions[0]); std::vector<ManifestFetchData*> fetches = builder.GetFetches(); EXPECT_EQ(1u, fetches.size()); scoped_ptr<ManifestFetchData> fetch(fetches[0]); fetches.clear(); // Make sure that extensions that update from the gallery ignore any // update URL data. const std::string& update_url = fetch->full_url().spec(); std::string::size_type x = update_url.find("x="); EXPECT_NE(std::string::npos, x); std::string::size_type ap = update_url.find("ap%3D", x); EXPECT_EQ(std::string::npos, ap); } static void TestDetermineUpdates() { MessageLoop message_loop; BrowserThread ui_thread(BrowserThread::UI, &message_loop); BrowserThread file_thread(BrowserThread::FILE, &message_loop); // Create a set of test extensions ServiceForManifestTests service; ExtensionList tmp; service.CreateTestExtensions(1, 3, &tmp, NULL, Extension::INTERNAL); service.set_extensions(tmp); ExtensionUpdater updater( &service, service.extension_prefs(), service.pref_service(), service.profile(), kUpdateFrequencySecs); updater.Start(); // Check passing an empty list of parse results to DetermineUpdates ManifestFetchData fetch_data(GURL("http://localhost/foo")); UpdateManifest::Results updates; std::vector<int> updateable = updater.DetermineUpdates(fetch_data, updates); EXPECT_TRUE(updateable.empty()); // Create two updates - expect that DetermineUpdates will return the first // one (v1.0 installed, v1.1 available) but not the second one (both // installed and available at v2.0). scoped_ptr<Version> one(Version::GetVersionFromString("1.0")); EXPECT_TRUE(tmp[0]->version()->Equals(*one)); fetch_data.AddExtension(tmp[0]->id(), tmp[0]->VersionString(), kNeverPingedData, kEmptyUpdateUrlData); AddParseResult(tmp[0]->id(), "1.1", "http://localhost/e1_1.1.crx", &updates); fetch_data.AddExtension(tmp[1]->id(), tmp[1]->VersionString(), kNeverPingedData, kEmptyUpdateUrlData); AddParseResult(tmp[1]->id(), tmp[1]->VersionString(), "http://localhost/e2_2.0.crx", &updates); updateable = updater.DetermineUpdates(fetch_data, updates); EXPECT_EQ(1u, updateable.size()); EXPECT_EQ(0, updateable[0]); } static void TestDetermineUpdatesPending() { // Create a set of test extensions ServiceForManifestTests service; PendingExtensionManager* pending_extension_manager = service.pending_extension_manager(); SetupPendingExtensionManagerForTest(3, GURL(), pending_extension_manager); MessageLoop message_loop; BrowserThread ui_thread(BrowserThread::UI, &message_loop); ExtensionUpdater updater( &service, service.extension_prefs(), service.pref_service(), service.profile(), kUpdateFrequencySecs); updater.Start(); ManifestFetchData fetch_data(GURL("http://localhost/foo")); UpdateManifest::Results updates; PendingExtensionManager::const_iterator it; for (it = pending_extension_manager->begin(); it != pending_extension_manager->end(); ++it) { fetch_data.AddExtension(it->first, "1.0.0.0", kNeverPingedData, kEmptyUpdateUrlData); AddParseResult(it->first, "1.1", "http://localhost/e1_1.1.crx", &updates); } std::vector<int> updateable = updater.DetermineUpdates(fetch_data, updates); // All the apps should be updateable. EXPECT_EQ(3u, updateable.size()); for (std::vector<int>::size_type i = 0; i < updateable.size(); ++i) { EXPECT_EQ(static_cast<int>(i), updateable[i]); } } static void TestMultipleManifestDownloading() { MessageLoop ui_loop; BrowserThread ui_thread(BrowserThread::UI, &ui_loop); BrowserThread file_thread(BrowserThread::FILE); file_thread.Start(); BrowserThread io_thread(BrowserThread::IO); io_thread.Start(); TestURLFetcherFactory factory; TestURLFetcher* fetcher = NULL; URLFetcher::set_factory(&factory); scoped_ptr<ServiceForDownloadTests> service(new ServiceForDownloadTests); ExtensionUpdater updater(service.get(), service->extension_prefs(), service->pref_service(), service->profile(), kUpdateFrequencySecs); updater.Start(); GURL url1("http://localhost/manifest1"); GURL url2("http://localhost/manifest2"); // Request 2 update checks - the first should begin immediately and the // second one should be queued up. ManifestFetchData* fetch1 = new ManifestFetchData(url1); ManifestFetchData* fetch2 = new ManifestFetchData(url2); ManifestFetchData::PingData zeroDays(0, 0); fetch1->AddExtension("1111", "1.0", zeroDays, kEmptyUpdateUrlData); fetch2->AddExtension("12345", "2.0", kNeverPingedData, kEmptyUpdateUrlData); updater.StartUpdateCheck(fetch1); updater.StartUpdateCheck(fetch2); std::string invalid_xml = "invalid xml"; fetcher = factory.GetFetcherByID(ExtensionUpdater::kManifestFetcherId); EXPECT_TRUE(fetcher != NULL && fetcher->delegate() != NULL); EXPECT_TRUE(fetcher->load_flags() == expected_load_flags); fetcher->delegate()->OnURLFetchComplete( fetcher, url1, net::URLRequestStatus(), 200, ResponseCookies(), invalid_xml); // Now that the first request is complete, make sure the second one has // been started. const std::string kValidXml = "<?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' />" " </app>" "</gupdate>"; fetcher = factory.GetFetcherByID(ExtensionUpdater::kManifestFetcherId); EXPECT_TRUE(fetcher != NULL && fetcher->delegate() != NULL); EXPECT_TRUE(fetcher->load_flags() == expected_load_flags); fetcher->delegate()->OnURLFetchComplete( fetcher, url2, net::URLRequestStatus(), 200, ResponseCookies(), kValidXml); // This should run the manifest parsing, then we want to make sure that our // service was called with GetExtensionById with the matching id from // kValidXml. file_thread.Stop(); io_thread.Stop(); ui_loop.RunAllPending(); EXPECT_EQ("12345", service->last_inquired_extension_id()); xmlCleanupGlobals(); // The FILE thread is needed for |service|'s cleanup, // because of ImportantFileWriter. file_thread.Start(); service.reset(); } static void TestSingleExtensionDownloading(bool pending) { MessageLoop ui_loop; BrowserThread ui_thread(BrowserThread::UI, &ui_loop); BrowserThread file_thread(BrowserThread::FILE); file_thread.Start(); BrowserThread io_thread(BrowserThread::IO); io_thread.Start(); TestURLFetcherFactory factory; TestURLFetcher* fetcher = NULL; URLFetcher::set_factory(&factory); scoped_ptr<ServiceForDownloadTests> service(new ServiceForDownloadTests); ExtensionUpdater updater(service.get(), service->extension_prefs(), service->pref_service(), service->profile(), kUpdateFrequencySecs); updater.Start(); GURL test_url("http://localhost/extension.crx"); std::string id = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; std::string hash = ""; scoped_ptr<Version> version(Version::GetVersionFromString("0.0.1")); ASSERT_TRUE(version.get()); updater.FetchUpdatedExtension(id, test_url, hash, version->GetString()); if (pending) { const bool kIsFromSync = true; const bool kInstallSilently = true; const Extension::State kInitialState = Extension::ENABLED; const bool kInitialIncognitoEnabled = false; PendingExtensionManager* pending_extension_manager = service->pending_extension_manager(); pending_extension_manager->AddForTesting( id, PendingExtensionInfo(test_url, &ShouldAlwaysInstall, kIsFromSync, kInstallSilently, kInitialState, kInitialIncognitoEnabled, Extension::INTERNAL)); } // Call back the ExtensionUpdater with a 200 response and some test data std::string extension_data("whatever"); fetcher = factory.GetFetcherByID(ExtensionUpdater::kExtensionFetcherId); EXPECT_TRUE(fetcher != NULL && fetcher->delegate() != NULL); EXPECT_TRUE(fetcher->load_flags() == expected_load_flags); fetcher->delegate()->OnURLFetchComplete( fetcher, test_url, net::URLRequestStatus(), 200, ResponseCookies(), extension_data); file_thread.Stop(); ui_loop.RunAllPending(); // Expect that ExtensionUpdater asked the mock extensions service to install // a file with the test data for the right id. EXPECT_EQ(id, service->extension_id()); FilePath tmpfile_path = service->install_path(); EXPECT_FALSE(tmpfile_path.empty()); EXPECT_EQ(test_url, service->download_url()); std::string file_contents; EXPECT_TRUE(file_util::ReadFileToString(tmpfile_path, &file_contents)); EXPECT_TRUE(extension_data == file_contents); // The FILE thread is needed for |service|'s cleanup, // because of ImportantFileWriter. file_thread.Start(); service.reset(); file_util::Delete(tmpfile_path, false); URLFetcher::set_factory(NULL); } static void TestBlacklistDownloading() { MessageLoop message_loop; BrowserThread ui_thread(BrowserThread::UI, &message_loop); BrowserThread file_thread(BrowserThread::FILE, &message_loop); BrowserThread io_thread(BrowserThread::IO); io_thread.Start(); TestURLFetcherFactory factory; TestURLFetcher* fetcher = NULL; URLFetcher::set_factory(&factory); ServiceForBlacklistTests service; ExtensionUpdater updater( &service, service.extension_prefs(), service.pref_service(), service.profile(), kUpdateFrequencySecs); updater.Start(); 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); EXPECT_TRUE(fetcher->load_flags() == expected_load_flags); fetcher->delegate()->OnURLFetchComplete( fetcher, test_url, net::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, service.pref_service()-> GetString(prefs::kExtensionBlacklistUpdateVersion)); URLFetcher::set_factory(NULL); } static void TestMultipleExtensionDownloading() { MessageLoopForUI message_loop; BrowserThread ui_thread(BrowserThread::UI, &message_loop); BrowserThread file_thread(BrowserThread::FILE, &message_loop); BrowserThread io_thread(BrowserThread::IO); io_thread.Start(); TestURLFetcherFactory factory; TestURLFetcher* fetcher = NULL; URLFetcher::set_factory(&factory); ServiceForDownloadTests service; ExtensionUpdater updater( &service, service.extension_prefs(), service.pref_service(), service.profile(), kUpdateFrequencySecs); updater.Start(); GURL url1("http://localhost/extension1.crx"); GURL url2("http://localhost/extension2.crx"); 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, hash1, version1); updater.FetchUpdatedExtension(id2, url2, hash2, version2); // Make the first fetch complete. std::string extension_data1("whatever"); fetcher = factory.GetFetcherByID(ExtensionUpdater::kExtensionFetcherId); EXPECT_TRUE(fetcher != NULL && fetcher->delegate() != NULL); EXPECT_TRUE(fetcher->load_flags() == expected_load_flags); fetcher->delegate()->OnURLFetchComplete( fetcher, url1, net::URLRequestStatus(), 200, ResponseCookies(), extension_data1); message_loop.RunAllPending(); // Expect that the service was asked to do an install with the right data. FilePath tmpfile_path = service.install_path(); EXPECT_FALSE(tmpfile_path.empty()); EXPECT_EQ(id1, service.extension_id()); EXPECT_EQ(url1, service.download_url()); message_loop.RunAllPending(); file_util::Delete(tmpfile_path, false); // Make sure the second fetch finished and asked the service to do an // update. std::string extension_data2("whatever2"); fetcher = factory.GetFetcherByID(ExtensionUpdater::kExtensionFetcherId); EXPECT_TRUE(fetcher != NULL && fetcher->delegate() != NULL); EXPECT_TRUE(fetcher->load_flags() == expected_load_flags); fetcher->delegate()->OnURLFetchComplete( fetcher, url2, net::URLRequestStatus(), 200, ResponseCookies(), extension_data2); message_loop.RunAllPending(); EXPECT_EQ(id2, service.extension_id()); EXPECT_EQ(url2, service.download_url()); EXPECT_FALSE(service.install_path().empty()); // Make sure the correct crx contents were passed for the update call. std::string file_contents; EXPECT_TRUE(file_util::ReadFileToString(service.install_path(), &file_contents)); EXPECT_TRUE(extension_data2 == file_contents); file_util::Delete(service.install_path(), false); } // Test requests to both a Google server and a non-google server. This allows // us to test various combinations of installed (ie roll call) and active // (ie app launch) ping scenarios. The invariant is that each type of ping // value should be present at most once per day, and can be calculated based // on the delta between now and the last ping time (or in the case of active // pings, that delta plus whether the app has been active). static void TestGalleryRequests(int rollcall_ping_days, int active_ping_days, bool active_bit) { MessageLoop message_loop; BrowserThread ui_thread(BrowserThread::UI, &message_loop); BrowserThread file_thread(BrowserThread::FILE, &message_loop); TestURLFetcherFactory factory; URLFetcher::set_factory(&factory); // Set up 2 mock extensions, one with a google.com update url and one // without. ServiceForManifestTests service; ExtensionList tmp; GURL url1("http://clients2.google.com/service/update2/crx"); GURL url2("http://www.somewebsite.com"); service.CreateTestExtensions(1, 1, &tmp, &url1.possibly_invalid_spec(), Extension::INTERNAL); service.CreateTestExtensions(2, 1, &tmp, &url2.possibly_invalid_spec(), Extension::INTERNAL); EXPECT_EQ(2u, tmp.size()); service.set_extensions(tmp); ExtensionPrefs* prefs = service.extension_prefs(); const std::string& id = tmp[0]->id(); Time now = Time::Now(); if (rollcall_ping_days == 0) { prefs->SetLastPingDay(id, now - TimeDelta::FromSeconds(15)); } else if (rollcall_ping_days > 0) { Time last_ping_day = now - TimeDelta::FromDays(rollcall_ping_days) - TimeDelta::FromSeconds(15); prefs->SetLastPingDay(id, last_ping_day); } // Store a value for the last day we sent an active ping. if (active_ping_days == 0) { prefs->SetLastActivePingDay(id, now - TimeDelta::FromSeconds(15)); } else if (active_ping_days > 0) { Time last_active_ping_day = now - TimeDelta::FromDays(active_ping_days) - TimeDelta::FromSeconds(15); prefs->SetLastActivePingDay(id, last_active_ping_day); } if (active_bit) prefs->SetActiveBit(id, true); ExtensionUpdater updater( &service, service.extension_prefs(), service.pref_service(), service.profile(), kUpdateFrequencySecs); updater.Start(); updater.set_blacklist_checks_enabled(false); // Make the updater do manifest fetching, and note the urls it tries to // fetch. std::vector<GURL> fetched_urls; updater.CheckNow(); TestURLFetcher* fetcher = factory.GetFetcherByID(ExtensionUpdater::kManifestFetcherId); EXPECT_TRUE(fetcher != NULL && fetcher->delegate() != NULL); fetched_urls.push_back(fetcher->original_url()); fetcher->delegate()->OnURLFetchComplete( fetcher, fetched_urls[0], net::URLRequestStatus(), 500, ResponseCookies(), ""); fetcher = factory.GetFetcherByID(ExtensionUpdater::kManifestFetcherId); fetched_urls.push_back(fetcher->original_url()); // The urls could have been fetched in either order, so use the host to // tell them apart and note the query each used. std::string url1_query; std::string url2_query; if (fetched_urls[0].host() == url1.host()) { url1_query = fetched_urls[0].query(); url2_query = fetched_urls[1].query(); } else if (fetched_urls[0].host() == url2.host()) { url1_query = fetched_urls[1].query(); url2_query = fetched_urls[0].query(); } else { NOTREACHED(); } // First make sure the non-google query had no ping parameter. std::string search_string = "ping%3D"; EXPECT_TRUE(url2_query.find(search_string) == std::string::npos); // Now make sure the google query had the correct ping parameter. bool ping_expected = false; bool did_rollcall = false; if (rollcall_ping_days != 0) { search_string += "r%253D" + base::IntToString(rollcall_ping_days); did_rollcall = true; ping_expected = true; } if (active_bit && active_ping_days != 0) { if (did_rollcall) search_string += "%2526"; search_string += "a%253D" + base::IntToString(active_ping_days); ping_expected = true; } bool ping_found = url1_query.find(search_string) != std::string::npos; EXPECT_EQ(ping_expected, ping_found) << "query was: " << url1_query << " was looking for " << search_string; } // This makes sure that the extension updater properly stores the results // of a <daystart> tag from a manifest fetch in one of two cases: 1) This is // the first time we fetched the extension, or 2) We sent a ping value of // >= 1 day for the extension. static void TestHandleManifestResults() { ServiceForManifestTests service; MessageLoop message_loop; BrowserThread ui_thread(BrowserThread::UI, &message_loop); ExtensionUpdater updater( &service, service.extension_prefs(), service.pref_service(), service.profile(), kUpdateFrequencySecs); updater.Start(); GURL update_url("http://www.google.com/manifest"); ExtensionList tmp; service.CreateTestExtensions(1, 1, &tmp, &update_url.spec(), Extension::INTERNAL); service.set_extensions(tmp); ManifestFetchData fetch_data(update_url); const Extension* extension = tmp[0]; fetch_data.AddExtension(extension->id(), extension->VersionString(), kNeverPingedData, kEmptyUpdateUrlData); UpdateManifest::Results results; results.daystart_elapsed_seconds = 750; updater.HandleManifestResults(fetch_data, &results); Time last_ping_day = service.extension_prefs()->LastPingDay(extension->id()); EXPECT_FALSE(last_ping_day.is_null()); int64 seconds_diff = (Time::Now() - last_ping_day).InSeconds(); EXPECT_LT(seconds_diff - results.daystart_elapsed_seconds, 5); } }; // Because we test some private methods of ExtensionUpdater, it's easer for the // actual test code to live in ExtenionUpdaterTest methods instead of TEST_F // subclasses where friendship with ExtenionUpdater is not inherited. TEST(ExtensionUpdaterTest, TestExtensionUpdateCheckRequests) { ExtensionUpdaterTest::TestExtensionUpdateCheckRequests(false); } TEST(ExtensionUpdaterTest, TestExtensionUpdateCheckRequestsPending) { ExtensionUpdaterTest::TestExtensionUpdateCheckRequests(true); } // This test is disabled on Mac, see http://crbug.com/26035. TEST(ExtensionUpdaterTest, TestBlacklistUpdateCheckRequests) { ExtensionUpdaterTest::TestBlacklistUpdateCheckRequests(); } TEST(ExtensionUpdaterTest, TestUpdateUrlData) { MessageLoop message_loop; BrowserThread file_thread(BrowserThread::FILE, &message_loop); ExtensionUpdaterTest::TestUpdateUrlDataEmpty(); ExtensionUpdaterTest::TestUpdateUrlDataSimple(); ExtensionUpdaterTest::TestUpdateUrlDataCompound(); ExtensionUpdaterTest::TestUpdateUrlDataFromGallery( Extension::GalleryUpdateUrl(false).spec()); ExtensionUpdaterTest::TestUpdateUrlDataFromGallery( Extension::GalleryUpdateUrl(true).spec()); } TEST(ExtensionUpdaterTest, TestDetermineUpdates) { ExtensionUpdaterTest::TestDetermineUpdates(); } TEST(ExtensionUpdaterTest, TestDetermineUpdatesPending) { ExtensionUpdaterTest::TestDetermineUpdatesPending(); } TEST(ExtensionUpdaterTest, TestMultipleManifestDownloading) { ExtensionUpdaterTest::TestMultipleManifestDownloading(); } TEST(ExtensionUpdaterTest, TestSingleExtensionDownloading) { ExtensionUpdaterTest::TestSingleExtensionDownloading(false); } TEST(ExtensionUpdaterTest, TestSingleExtensionDownloadingPending) { ExtensionUpdaterTest::TestSingleExtensionDownloading(true); } // This test is disabled on Mac, see http://crbug.com/26035. TEST(ExtensionUpdaterTest, TestBlacklistDownloading) { ExtensionUpdaterTest::TestBlacklistDownloading(); } TEST(ExtensionUpdaterTest, TestMultipleExtensionDownloading) { ExtensionUpdaterTest::TestMultipleExtensionDownloading(); } TEST(ExtensionUpdaterTest, TestGalleryRequests) { // We want to test a variety of combinations of expected ping conditions for // rollcall and active pings. int ping_cases[] = { ManifestFetchData::kNeverPinged, 0, 1, 5 }; for (size_t i = 0; i < arraysize(ping_cases); i++) { for (size_t j = 0; j < arraysize(ping_cases); j++) { for (size_t k = 0; k < 2; k++) { int rollcall_ping_days = ping_cases[i]; int active_ping_days = ping_cases[j]; // Skip cases where rollcall_ping_days == -1, but active_ping_days > 0, // because rollcall_ping_days == -1 means the app was just installed and // this is the first update check after installation. if (rollcall_ping_days == ManifestFetchData::kNeverPinged && active_ping_days > 0) continue; bool active_bit = k > 0; ExtensionUpdaterTest::TestGalleryRequests( rollcall_ping_days, active_ping_days, active_bit); ASSERT_FALSE(HasFailure()) << " rollcall_ping_days=" << ping_cases[i] << " active_ping_days=" << ping_cases[j] << " active_bit=" << active_bit; } } } } TEST(ExtensionUpdaterTest, TestHandleManifestResults) { ExtensionUpdaterTest::TestHandleManifestResults(); } TEST(ExtensionUpdaterTest, TestManifestFetchesBuilderAddExtension) { MessageLoop message_loop; BrowserThread file_thread(BrowserThread::FILE, &message_loop); MockService service; ManifestFetchesBuilder builder(&service, service.extension_prefs()); // Non-internal non-external extensions should be rejected. { ExtensionList extensions; service.CreateTestExtensions(1, 1, &extensions, NULL, Extension::INVALID); ASSERT_FALSE(extensions.empty()); builder.AddExtension(*extensions[0]); EXPECT_TRUE(builder.GetFetches().empty()); } // Extensions with invalid update URLs should be rejected. builder.AddPendingExtension( GenerateId("foo"), PendingExtensionInfo(GURL("http:google.com:foo"), &ShouldInstallExtensionsOnly, false, false, true, false, Extension::INTERNAL)); EXPECT_TRUE(builder.GetFetches().empty()); // Extensions with empty IDs should be rejected. builder.AddPendingExtension( "", PendingExtensionInfo(GURL(), &ShouldInstallExtensionsOnly, false, false, true, false, Extension::INTERNAL)); EXPECT_TRUE(builder.GetFetches().empty()); // TODO(akalin): Test that extensions with empty update URLs // converted from user scripts are rejected. // Extensions with empty update URLs should have a default one // filled in. builder.AddPendingExtension( GenerateId("foo"), PendingExtensionInfo(GURL(), &ShouldInstallExtensionsOnly, false, false, true, false, Extension::INTERNAL)); std::vector<ManifestFetchData*> fetches = builder.GetFetches(); ASSERT_EQ(1u, fetches.size()); scoped_ptr<ManifestFetchData> fetch(fetches[0]); fetches.clear(); EXPECT_FALSE(fetch->base_url().is_empty()); EXPECT_FALSE(fetch->full_url().is_empty()); } TEST(ExtensionUpdaterTest, TestStartUpdateCheckMemory) { MessageLoop message_loop; BrowserThread ui_thread(BrowserThread::UI, &message_loop); BrowserThread file_thread(BrowserThread::FILE, &message_loop); ServiceForManifestTests service; TestURLFetcherFactory factory; URLFetcher::set_factory(&factory); ExtensionUpdater updater( &service, service.extension_prefs(), service.pref_service(), service.profile(), kUpdateFrequencySecs); updater.Start(); updater.StartUpdateCheck(new ManifestFetchData(GURL())); // This should delete the newly-created ManifestFetchData. updater.StartUpdateCheck(new ManifestFetchData(GURL())); // This should add into |manifests_pending_|. updater.StartUpdateCheck(new ManifestFetchData( GURL("http://www.google.com"))); // This should clear out |manifests_pending_|. updater.Stop(); } TEST(ExtensionUpdaterTest, TestCheckSoon) { MessageLoop message_loop; BrowserThread ui_thread(BrowserThread::UI, &message_loop); BrowserThread file_thread(BrowserThread::FILE, &message_loop); ServiceForManifestTests service; TestURLFetcherFactory factory; URLFetcher::set_factory(&factory); ExtensionUpdater updater( &service, service.extension_prefs(), service.pref_service(), service.profile(), kUpdateFrequencySecs); EXPECT_FALSE(updater.WillCheckSoon()); updater.Start(); EXPECT_FALSE(updater.WillCheckSoon()); updater.CheckSoon(); EXPECT_TRUE(updater.WillCheckSoon()); updater.CheckSoon(); EXPECT_TRUE(updater.WillCheckSoon()); ExtensionUpdaterTest::SimulateCheckSoon(updater, &message_loop); EXPECT_FALSE(updater.WillCheckSoon()); updater.CheckSoon(); EXPECT_TRUE(updater.WillCheckSoon()); updater.Stop(); EXPECT_FALSE(updater.WillCheckSoon()); } // TODO(asargent) - (http://crbug.com/12780) add tests for: // -prodversionmin (shouldn't update if browser version too old) // -manifests & updates arriving out of order / interleaved // -malformed update url (empty, file://, has query, has a # fragment, etc.) // -An extension gets uninstalled while updates are in progress (so it doesn't // "come back from the dead") // -An extension gets manually updated to v3 while we're downloading v2 (ie // you don't get downgraded accidentally) // -An update manifest mentions multiple updates