blob: 5e8e9bccc40397e5929d8361cdd4d7b08a47e1cc [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/updater/extension_updater.h"
#include <stddef.h>
#include <stdint.h>
#include <list>
#include <map>
#include <memory>
#include <set>
#include <utility>
#include <vector>
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/ranges/algorithm.h"
#include "base/run_loop.h"
#include "base/strings/escape.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/threading/thread.h"
#include "base/time/time.h"
#include "base/version.h"
#include "build/branding_buildflags.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/extensions/crx_installer.h"
#include "chrome/browser/extensions/extension_sync_data.h"
#include "chrome/browser/extensions/fake_crx_installer.h"
#include "chrome/browser/extensions/mock_crx_installer.h"
#include "chrome/browser/extensions/test_extension_prefs.h"
#include "chrome/browser/extensions/test_extension_service.h"
#include "chrome/browser/extensions/test_extension_system.h"
#include "chrome/browser/extensions/updater/chrome_extension_downloader_factory.h"
#include "chrome/browser/google/google_brand.h"
#include "chrome/test/base/scoped_testing_local_state.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "components/crx_file/id_util.h"
#include "components/signin/public/identity_manager/identity_test_environment.h"
#include "components/sync_preferences/pref_service_syncable.h"
#include "components/update_client/update_query_params.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_utils.h"
#include "extensions/browser/blocklist_extension_prefs.h"
#include "extensions/browser/blocklist_state.h"
#include "extensions/browser/disable_reason.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/pref_names.h"
#include "extensions/browser/updater/extension_downloader.h"
#include "extensions/browser/updater/extension_downloader_delegate.h"
#include "extensions/browser/updater/extension_downloader_test_delegate.h"
#include "extensions/browser/updater/extension_downloader_test_helper.h"
#include "extensions/browser/updater/extension_downloader_types.h"
#include "extensions/browser/updater/extension_update_data.h"
#include "extensions/browser/updater/manifest_fetch_data.h"
#include "extensions/browser/updater/request_queue_impl.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/extension_urls.h"
#include "extensions/common/extensions_client.h"
#include "extensions/common/manifest_constants.h"
#include "extensions/common/manifest_url_handlers.h"
#include "extensions/common/mojom/manifest.mojom-shared.h"
#include "extensions/common/verifier_formats.h"
#include "net/base/backoff_entry.h"
#include "net/base/load_flags.h"
#include "net/http/http_request_headers.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "services/network/test/test_url_loader_factory.h"
#include "services/network/test/test_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/third_party/mozilla/url_parse.h"
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "base/files/scoped_temp_dir.h"
#include "chrome/browser/ash/login/users/chrome_user_manager_impl.h"
#include "chrome/browser/ash/settings/scoped_cros_settings_test_helper.h"
#include "chrome/browser/extensions/load_error_reporter.h"
#include "chrome/browser/extensions/updater/chromeos_extension_cache_delegate.h"
#include "chrome/browser/extensions/updater/extension_cache_impl.h"
#include "chrome/browser/extensions/updater/local_extension_cache.h"
#include "components/user_manager/scoped_user_manager.h"
#endif
using base::Time;
using content::BrowserThread;
using extensions::mojom::ManifestLocation;
using testing::_;
using testing::DoAll;
using testing::Invoke;
using testing::InvokeWithoutArgs;
using testing::Mock;
using testing::NiceMock;
using testing::Return;
using testing::SetArgPointee;
using update_client::UpdateQueryParams;
namespace extensions {
using Error = ExtensionDownloaderDelegate::Error;
using PingResult = ExtensionDownloaderDelegate::PingResult;
namespace {
const net::BackoffEntry::Policy kNoBackoffPolicy = {
// Number of initial errors (in sequence) to ignore before applying
// exponential back-off rules.
1000,
// Initial delay for exponential back-off in ms.
0,
// Factor by which the waiting time will be multiplied.
0,
// Fuzzing percentage. ex: 10% will spread requests randomly
// between 90%-100% of the calculated time.
0,
// Maximum amount of time we are willing to delay our request in ms.
0,
// Time to keep an entry from being discarded even when it
// has no significant state, -1 to never discard.
-1,
// Don't use initial delay unless the last request was an error.
false,
};
const char kAuthUserQueryKey[] = "authuser";
int kExpectedLoadFlags = net::LOAD_DISABLE_CACHE;
int kExpectedLoadFlagsForDownloadWithCookies = net::LOAD_DISABLE_CACHE;
// Fake authentication constants
const char kFakeOAuth2Token[] = "ce n'est pas un jeton";
// Extracts the integer value of the |authuser| query parameter. Returns 0 if
// the parameter is not set.
int GetAuthUserQueryValue(const GURL& url) {
std::string_view query_piece = url.query_piece();
url::Component query(0, query_piece.length());
url::Component key, value;
while (url::ExtractQueryKeyValue(query_piece, &query, &key, &value)) {
std::string_view key_string = query_piece.substr(key.begin, key.len);
if (key_string == kAuthUserQueryKey) {
int user_index = 0;
base::StringToInt(query_piece.substr(value.begin, value.len),
&user_index);
return user_index;
}
}
return 0;
}
class MockUpdateService : public UpdateService {
public:
MockUpdateService() : UpdateService(nullptr, nullptr) {}
MOCK_CONST_METHOD0(IsBusy, bool());
MOCK_METHOD3(SendUninstallPing,
void(const std::string& id,
const base::Version& version,
int reason));
MOCK_METHOD(void,
StartUpdateCheck,
(const ExtensionUpdateCheckParams& params,
UpdateFoundCallback update_found_callback,
base::OnceClosure callback),
(override));
};
} // namespace
// Base class for further specialized test classes.
class MockService : public TestExtensionService {
public:
explicit MockService(
TestExtensionPrefs* prefs,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
: prefs_(prefs),
pending_extension_manager_(prefs->profile()),
corrupted_extension_reinstaller_(prefs->profile()),
downloader_delegate_override_(nullptr),
test_shared_url_loader_factory_(url_loader_factory) {}
MockService(const MockService&) = delete;
MockService& operator=(const MockService&) = delete;
~MockService() override = default;
PendingExtensionManager* pending_extension_manager() override {
ADD_FAILURE() << "Subclass should override this if it will "
<< "be accessed by a test.";
return &pending_extension_manager_;
}
Profile* profile() { return prefs_->profile(); }
ExtensionPrefs* extension_prefs() { return prefs_->prefs(); }
PrefService* pref_service() { return prefs_->pref_service(); }
signin::IdentityTestEnvironment* identity_test_env() {
return identity_test_env_.get();
}
CorruptedExtensionReinstaller* corrupted_extension_reinstaller() override {
return &corrupted_extension_reinstaller_;
}
const CoreAccountId& account_id() { return account_info_.account_id; }
// 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,
ManifestLocation location) {
for (int i = 1; i <= count; i++) {
base::Value::Dict manifest;
manifest.Set(manifest_keys::kVersion, base::StringPrintf("%d.0.0.0", i));
manifest.Set(manifest_keys::kName,
base::StringPrintf("Extension %d.%d", id, i));
manifest.Set(manifest_keys::kManifestVersion, 2);
if (update_url)
manifest.Set(manifest_keys::kUpdateURL, *update_url);
scoped_refptr<Extension> e =
prefs_->AddExtensionWithManifest(manifest, location);
ASSERT_TRUE(e.get() != nullptr);
list->push_back(e);
}
}
ExtensionDownloader::Factory GetDownloaderFactory() {
return base::BindRepeating(&MockService::CreateExtensionDownloader,
base::Unretained(this));
}
ExtensionDownloader::Factory GetAuthenticatedDownloaderFactory() {
return base::BindRepeating(
&MockService::CreateExtensionDownloaderWithIdentity,
base::Unretained(this));
}
void OverrideDownloaderDelegate(ExtensionDownloaderDelegate* delegate) {
downloader_delegate_override_ = delegate;
}
protected:
const raw_ptr<TestExtensionPrefs, DanglingUntriaged> prefs_;
PendingExtensionManager pending_extension_manager_;
CorruptedExtensionReinstaller corrupted_extension_reinstaller_;
private:
std::unique_ptr<ExtensionDownloader> CreateExtensionDownloader(
ExtensionDownloaderDelegate* delegate) {
std::unique_ptr<ExtensionDownloader> downloader =
ChromeExtensionDownloaderFactory::CreateForURLLoaderFactory(
test_shared_url_loader_factory_,
downloader_delegate_override_ ? downloader_delegate_override_.get()
: delegate,
GetTestVerifierFormat());
return downloader;
}
std::unique_ptr<ExtensionDownloader> CreateExtensionDownloaderWithIdentity(
ExtensionDownloaderDelegate* delegate) {
identity_test_env_ = std::make_unique<signin::IdentityTestEnvironment>();
account_info_ = identity_test_env_->MakePrimaryAccountAvailable(
"bobloblaw@lawblog.example.com", signin::ConsentLevel::kSync);
std::unique_ptr<ExtensionDownloader> downloader(
CreateExtensionDownloader(delegate));
downloader->SetIdentityManager(identity_test_env_->identity_manager());
return downloader;
}
AccountInfo account_info_;
std::unique_ptr<signin::IdentityTestEnvironment> identity_test_env_;
raw_ptr<ExtensionDownloaderDelegate> downloader_delegate_override_;
scoped_refptr<network::SharedURLLoaderFactory>
test_shared_url_loader_factory_;
};
bool ShouldInstallExtensionsOnly(const Extension* extension,
content::BrowserContext* context) {
return extension->GetType() == Manifest::TYPE_EXTENSION;
}
bool ShouldInstallThemesOnly(const Extension* extension,
content::BrowserContext* context) {
return extension->is_theme();
}
bool ShouldAlwaysInstall(const Extension* extension,
content::BrowserContext* context) {
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 kMarkAcknowledged = false;
const bool kRemoteInstall = false;
std::string id =
crx_file::id_util::GenerateId(base::StringPrintf("extension%i", i));
pending_extension_manager->AddForTesting(PendingExtensionInfo(
id, std::string(), update_url, base::Version(), should_allow_install,
kIsFromSync, ManifestLocation::kInternal, Extension::NO_FLAGS,
kMarkAcknowledged, kRemoteInstall));
}
}
class ServiceForManifestTests : public MockService {
public:
explicit ServiceForManifestTests(
TestExtensionPrefs* prefs,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
: MockService(prefs, url_loader_factory),
registry_(ExtensionRegistry::Get(profile())) {}
~ServiceForManifestTests() override = default;
PendingExtensionManager* pending_extension_manager() override {
return &pending_extension_manager_;
}
const Extension* GetPendingExtensionUpdate(
const std::string& id) const override {
return nullptr;
}
bool IsExtensionEnabled(const std::string& id) const override {
return !registry_->disabled_extensions().Contains(id);
}
void set_extensions(ExtensionList extensions,
ExtensionList disabled_extensions,
ExtensionList blocklisted_extensions = ExtensionList()) {
registry_->ClearAll();
for (ExtensionList::const_iterator it = extensions.begin();
it != extensions.end(); ++it) {
registry_->AddEnabled(*it);
}
for (ExtensionList::const_iterator it = disabled_extensions.begin();
it != disabled_extensions.end(); ++it) {
registry_->AddDisabled(*it);
}
for (ExtensionList::const_iterator it = blocklisted_extensions.begin();
it != blocklisted_extensions.end(); ++it) {
registry_->AddBlocklisted(*it);
}
}
private:
raw_ptr<ExtensionRegistry> registry_;
};
class ServiceForDownloadTests : public MockService {
public:
explicit ServiceForDownloadTests(
TestExtensionPrefs* prefs,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
: MockService(prefs, url_loader_factory) {}
// Add a fake crx installer to be returned by a call to
// CreateUpdateInstaller() with a specific ID.
void AddFakeCrxInstaller(const std::string& id,
scoped_refptr<CrxInstaller> crx_installer) {
fake_crx_installers_[id] = crx_installer;
}
scoped_refptr<CrxInstaller> CreateUpdateInstaller(
const CRXFileInfo& file,
bool file_ownership_passed) override {
extension_id_ = file.extension_id;
install_path_ = file.path;
if (base::Contains(fake_crx_installers_, extension_id_)) {
return fake_crx_installers_[extension_id_];
}
return nullptr;
}
PendingExtensionManager* pending_extension_manager() override {
return &pending_extension_manager_;
}
CorruptedExtensionReinstaller* corrupted_extension_reinstaller() override {
return &corrupted_extension_reinstaller_;
}
const ExtensionId& extension_id() const { return extension_id_; }
const base::FilePath& install_path() const { return install_path_; }
private:
// Hold the set of ids that CreateUpdateInstaller() returns.
std::map<std::string, scoped_refptr<CrxInstaller>> fake_crx_installers_;
ExtensionId extension_id_;
base::FilePath install_path_;
GURL download_url_;
};
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) {
for (const std::string& pair : base::SplitString(
params, "&", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) {
std::vector<std::string> key_val = base::SplitString(
pair, "=", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
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] : std::string();
} else {
NOTREACHED();
}
}
}
// Helper function to extract the ping data param values for each extension in
// a manifest fetch url, returned in a map keyed by extension id.
// E.g. for "x=id%3Dabcdef%26ping%3Ddr%253D1%2526dr%253D1024" we'd return
// {"abcdef": {"dr": set("1", "1024")}}
typedef std::map<std::string, std::set<std::string>> ParamsMap;
static std::map<std::string, ParamsMap> GetPingDataFromURL(
const GURL& manifest_url) {
std::map<std::string, ParamsMap> result;
base::StringPairs toplevel_params;
base::SplitStringIntoKeyValuePairs(
manifest_url.query(), '=', '&', &toplevel_params);
for (const auto& param : toplevel_params) {
if (param.first != "x")
continue;
// We've found "x=<something>", now unescape <something> and look for
// the "id=<id>&ping=<ping_value>" parameters within.
std::string unescaped = base::UnescapeBinaryURLComponent(param.second);
base::StringPairs extension_params;
base::SplitStringIntoKeyValuePairs(unescaped, '=', '&', &extension_params);
std::multimap<std::string, std::string> param_map;
param_map.insert(extension_params.begin(), extension_params.end());
if (base::Contains(param_map, "id") && base::Contains(param_map, "ping")) {
std::string id = param_map.find("id")->second;
result[id] = ParamsMap();
// Pull the key=value pairs out of the ping parameter for this id and
// put into the result.
std::string ping =
base::UnescapeBinaryURLComponent(param_map.find("ping")->second);
base::StringPairs ping_params;
base::SplitStringIntoKeyValuePairs(ping, '=', '&', &ping_params);
for (const auto& ping_param : ping_params) {
if (!base::Contains(result[id], ping_param.first))
result[id][ping_param.first] = std::set<std::string>();
result[id][ping_param.first].insert(ping_param.second);
}
}
}
return result;
}
static void VerifyQueryAndExtractParameters(
const std::string& query,
std::map<std::string, std::string>* result) {
std::map<std::string, std::string> params;
ExtractParameters(query, &params);
std::string omaha_params = UpdateQueryParams::Get(UpdateQueryParams::CRX);
std::map<std::string, std::string> expected;
ExtractParameters(omaha_params, &expected);
for (auto it = expected.begin(); it != expected.end(); ++it) {
EXPECT_EQ(it->second, params[it->first]);
}
EXPECT_EQ(1U, params.count("x"));
std::string decoded = base::UnescapeBinaryURLComponent(params["x"]);
ExtractParameters(decoded, result);
}
// 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:
ExtensionUpdaterTest()
: task_environment_(content::BrowserTaskEnvironment::IO_MAINLOOP),
testing_local_state_(TestingBrowserProcess::GetGlobal()) {}
void SetUp() override {
prefs_ = std::make_unique<TestExtensionPrefs>(
base::SingleThreadTaskRunner::GetCurrentDefault());
}
void TearDown() override {
// Some tests create URLRequestContextGetters, whose destruction must run
// on the IO thread. Make sure the IO loop spins before shutdown so that
// those objects are released.
RunUntilIdle();
prefs_.reset();
}
void RunUntilIdle() {
prefs_->pref_service()->CommitPendingWrite();
base::RunLoop().RunUntilIdle();
}
void SimulateTimerFired(ExtensionUpdater* updater) { updater->NextCheck(); }
void OverrideUpdateService(ExtensionUpdater* updater,
UpdateService* service) {
updater->update_service_ = service;
}
bool CanUseUpdateService(ExtensionUpdater* updater,
const std::string& extension_id) {
return updater->CanUseUpdateService(extension_id);
}
// Adds a Result with the given data to results.
void AddParseResult(const std::string& id,
const std::string& version,
const std::string& url,
UpdateManifestResults* results) {
UpdateManifestResult result;
result.extension_id = id;
result.version = version;
result.crx_url = GURL(url);
results->update_list.push_back(result);
}
void StartUpdateCheck(ExtensionDownloader* downloader,
ManifestFetchData* fetch_data) {
downloader->StartUpdateCheck(
std::unique_ptr<ManifestFetchData>(fetch_data));
}
size_t ManifestFetchersCount(ExtensionDownloader* downloader) {
return downloader->manifests_queue_.size() +
(downloader->HasActiveManifestRequestForTesting() ? 1 : 0);
}
std::set<std::string> GetRunningInstallIds(const ExtensionUpdater& updater) {
std::set<std::string> ret;
for (const auto& pair : updater.running_crx_installs_)
ret.insert(pair.second.info.extension_id);
return ret;
}
const DownloadFailure* GetFailureWithId(
const std::vector<std::pair<ExtensionDownloaderTask, DownloadFailure>>&
failures,
const ExtensionId& id) {
auto it = base::ranges::find(
failures, id, [](const auto& failure) { return failure.first.id; });
return it == failures.end() ? nullptr : &it->second;
}
void TestExtensionUpdateCheckRequests(bool pending) {
// Create an extension with an update_url.
ExtensionDownloaderTestHelper helper;
ServiceForManifestTests service(prefs_.get(), helper.url_loader_factory());
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,
ManifestLocation::kInternal);
service.set_extensions(extensions, ExtensionList());
}
// Set up and start the updater.
ExtensionUpdater updater(&service, service.extension_prefs(),
service.pref_service(), service.profile(),
60 * 60 * 24, nullptr,
service.GetDownloaderFactory());
updater.Start();
// Tell the update that it's time to do update checks.
SimulateTimerFired(&updater);
// Get the url our loader was asked to fetch.
const ManifestFetchData& fetch =
*updater.downloader_->manifests_queue_.active_request();
const GURL& url = fetch.full_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::map<std::string, std::string> params;
VerifyQueryAndExtractParameters(url.query(), &params);
if (pending) {
EXPECT_TRUE(pending_extension_manager->IsIdPending(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"]);
}
void TestUpdateUrlDataEmpty() {
ExtensionDownloaderTestHelper helper;
const std::string id(32, 'a');
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.
GURL kUpdateURL("http://localhost/foo");
std::unique_ptr<ManifestFetchData> fetch_data(
CreateManifestFetchData(kUpdateURL));
AddExtensionToFetchDataForTesting(fetch_data.get(), id, version,
kUpdateURL);
std::map<std::string, std::string> params;
VerifyQueryAndExtractParameters(fetch_data->full_url().query(), &params);
EXPECT_EQ(id, params["id"]);
EXPECT_EQ(version, params["v"]);
EXPECT_EQ(0U, params.count("ap"));
}
void TestUpdateUrlDataSimple() {
const std::string id(32, 'a');
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.
std::unique_ptr<ManifestFetchData> fetch_data(
CreateManifestFetchData(GURL("http://localhost/foo")));
fetch_data->AddExtension(id, version,
&ExtensionDownloaderTestHelper::kNeverPingedData,
"bar", std::string(), ManifestLocation::kInternal,
DownloadFetchPriority::kBackground);
std::map<std::string, std::string> params;
VerifyQueryAndExtractParameters(fetch_data->full_url().query(), &params);
EXPECT_EQ(id, params["id"]);
EXPECT_EQ(version, params["v"]);
EXPECT_EQ("bar", params["ap"]);
}
void TestUpdateUrlDataCompound() {
const std::string id(32, 'a');
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.
std::unique_ptr<ManifestFetchData> fetch_data(
CreateManifestFetchData(GURL("http://localhost/foo")));
fetch_data->AddExtension(
id, version, &ExtensionDownloaderTestHelper::kNeverPingedData,
"a=1&b=2&c", std::string(), ManifestLocation::kInternal,
DownloadFetchPriority::kBackground);
std::map<std::string, std::string> params;
VerifyQueryAndExtractParameters(fetch_data->full_url().query(), &params);
EXPECT_EQ(id, params["id"]);
EXPECT_EQ(version, params["v"]);
EXPECT_EQ("a%3D1%26b%3D2%26c", params["ap"]);
}
void TestUpdateUrlDataFromUrl(
const std::string& update_url,
DownloadFetchPriority fetch_priority,
int num_extensions,
bool should_include_traffic_management_headers) {
ExtensionDownloaderTestHelper helper;
MockService service(prefs_.get(), helper.url_loader_factory());
ExtensionList extensions;
service.CreateTestExtensions(1, num_extensions, &extensions, &update_url,
ManifestLocation::kInternal);
for (int i = 0; i < num_extensions; ++i) {
const std::string& id = extensions[i]->id();
EXPECT_CALL(helper.delegate(), GetPingDataForExtension(id, _));
helper.downloader().AddPendingExtension(ExtensionDownloaderTask(
id, ManifestURL::GetUpdateURL(extensions[i].get()),
extensions[i]->location(), false, 0, fetch_priority,
extensions[i]->version(), extensions[i]->GetType(), std::string()));
}
// Get the headers our loader was asked to fetch.
base::RunLoop loop;
net::HttpRequestHeaders last_request_headers;
helper.test_url_loader_factory().SetInterceptor(base::BindLambdaForTesting(
[&](const network::ResourceRequest& request) {
last_request_headers = request.headers;
loop.Quit();
}));
helper.downloader().StartAllPending(nullptr);
EXPECT_TRUE(helper.downloader().HasActiveManifestRequestForTesting());
loop.Run();
// Make sure that extensions that update from the gallery ignore any
// update URL data.
const ManifestFetchData& fetch =
*helper.downloader().manifests_queue_.active_request();
const std::string& fetcher_url = fetch.full_url().spec();
std::string::size_type x = fetcher_url.find("x=");
EXPECT_NE(std::string::npos, x);
std::string::size_type ap = fetcher_url.find("ap%3D", x);
EXPECT_EQ(std::string::npos, ap);
net::HttpRequestHeaders fetch_headers;
std::swap(fetch_headers, last_request_headers);
EXPECT_EQ(should_include_traffic_management_headers,
fetch_headers.HasHeader(
ExtensionDownloader::kUpdateInteractivityHeader));
EXPECT_EQ(should_include_traffic_management_headers,
fetch_headers.HasHeader(ExtensionDownloader::kUpdateAppIdHeader));
EXPECT_EQ(
should_include_traffic_management_headers,
fetch_headers.HasHeader(ExtensionDownloader::kUpdateUpdaterHeader));
if (should_include_traffic_management_headers) {
std::string interactivity_value;
fetch_headers.GetHeader(ExtensionDownloader::kUpdateInteractivityHeader,
&interactivity_value);
std::string expected_interactivity_value =
fetch_priority == DownloadFetchPriority::kForeground ? "fg" : "bg";
EXPECT_EQ(expected_interactivity_value, interactivity_value);
std::string appid_value;
fetch_headers.GetHeader(ExtensionDownloader::kUpdateAppIdHeader,
&appid_value);
if (num_extensions > 1) {
for (int i = 0; i < num_extensions; ++i) {
EXPECT_TRUE(
testing::IsSubstring("", "", extensions[i]->id(), appid_value));
}
} else {
EXPECT_EQ(extensions[0]->id(), appid_value);
}
std::string updater_value;
fetch_headers.GetHeader(ExtensionDownloader::kUpdateUpdaterHeader,
&updater_value);
const std::string expected_updater_value = base::StringPrintf(
"%s-%s", UpdateQueryParams::GetProdIdString(UpdateQueryParams::CRX),
UpdateQueryParams::GetProdVersion().c_str());
EXPECT_EQ(expected_updater_value, updater_value);
}
}
void TestInstallSource() {
const std::string id(32, 'a');
const std::string version = "1.0";
const std::string install_source = "instally";
// Make sure that an installsource= appears in the x= parameter.
std::unique_ptr<ManifestFetchData> fetch_data(
CreateManifestFetchData(GURL("http://localhost/foo")));
fetch_data->AddExtension(
id, version, &ExtensionDownloaderTestHelper::kNeverPingedData,
ExtensionDownloaderTestHelper::kEmptyUpdateUrlData, install_source,
ManifestLocation::kInternal, DownloadFetchPriority::kBackground);
std::map<std::string, std::string> params;
VerifyQueryAndExtractParameters(fetch_data->full_url().query(), &params);
EXPECT_EQ(id, params["id"]);
EXPECT_EQ(version, params["v"]);
EXPECT_EQ(install_source, params["installsource"]);
}
void TestInstallLocation() {
const std::string id(32, 'a');
const std::string version = "1.0";
const std::string install_location = "external";
// Make sure that installedby= appears in the x= parameter.
std::unique_ptr<ManifestFetchData> fetch_data(
CreateManifestFetchData(GURL("http://localhost/foo")));
fetch_data->AddExtension(
id, version, &ExtensionDownloaderTestHelper::kNeverPingedData,
ExtensionDownloaderTestHelper::kEmptyUpdateUrlData, std::string(),
ManifestLocation::kExternalPrefDownload,
DownloadFetchPriority::kBackground);
std::map<std::string, std::string> params;
VerifyQueryAndExtractParameters(fetch_data->full_url().query(), &params);
EXPECT_EQ(id, params["id"]);
EXPECT_EQ(version, params["v"]);
EXPECT_EQ(install_location, params["installedby"]);
}
void TestDetermineUpdates() {
ExtensionDownloaderTestHelper helper;
GURL kUpdateURL("http://localhost/foo");
std::unique_ptr<ManifestFetchData> fetch_data(
CreateManifestFetchData(kUpdateURL));
// Check passing an empty list of parse results to DetermineUpdates
UpdateManifestResults updates;
std::vector<std::pair<ExtensionDownloaderTask, UpdateManifestResult*>>
updateable;
std::vector<std::pair<ExtensionDownloaderTask, DownloadFailure>> failures;
helper.downloader().DetermineUpdates(fetch_data->TakeAssociatedTasks(),
updates, &updateable, &failures);
EXPECT_TRUE(updateable.empty());
EXPECT_TRUE(failures.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).
const std::string id1 = crx_file::id_util::GenerateId("1");
const std::string id2 = crx_file::id_util::GenerateId("2");
AddExtensionToFetchDataForTesting(fetch_data.get(), id1, "1.0.0.0",
kUpdateURL);
AddParseResult(id1, "1.1", "http://localhost/e1_1.1.crx", &updates);
AddExtensionToFetchDataForTesting(fetch_data.get(), id2, "2.0.0.0",
kUpdateURL);
AddParseResult(id2, "2.0.0.0", "http://localhost/e2_2.0.crx", &updates);
EXPECT_CALL(helper.delegate(), IsExtensionPending(_))
.WillRepeatedly(Return(false));
EXPECT_CALL(helper.delegate(), GetExtensionExistingVersion(id1, _))
.WillOnce(DoAll(SetArgPointee<1>("1.0.0.0"), Return(true)));
EXPECT_CALL(helper.delegate(), GetExtensionExistingVersion(id2, _))
.WillOnce(DoAll(SetArgPointee<1>("2.0.0.0"), Return(true)));
updateable.clear();
failures.clear();
helper.downloader().DetermineUpdates(fetch_data->TakeAssociatedTasks(),
updates, &updateable, &failures);
ASSERT_EQ(1u, failures.size());
EXPECT_EQ(id2, failures[0].first.id);
EXPECT_EQ(ExtensionDownloaderDelegate::Error::NO_UPDATE_AVAILABLE,
failures[0].second.error);
ASSERT_EQ(1u, updateable.size());
EXPECT_EQ("1.1", updateable[0].second->version);
}
void TestDetermineUpdatesError() {
ExtensionDownloaderTestHelper helper;
MockExtensionDownloaderDelegate& delegate = helper.delegate();
GURL kUpdateURL("http://localhost/foo");
std::unique_ptr<ManifestFetchData> fetch_data(
CreateManifestFetchData(kUpdateURL));
UpdateManifestResults updates;
// id1 => updatable (current version (1.1) is older than update version).
// id2 => non_updateable (current version (2.0.0.0) is the same as update
// version).
// id3 => non_updateable (manifest update version is empty).
// id4 => errors (|updates| doesn't contain id4).
// id5 => errors (the extension is not currently installed).
// id6 => errors (manifest update version is invalid).
const std::string id1 = crx_file::id_util::GenerateId("1");
const std::string id2 = crx_file::id_util::GenerateId("2");
const std::string id3 = crx_file::id_util::GenerateId("3");
const std::string id4 = crx_file::id_util::GenerateId("4");
const std::string id5 = crx_file::id_util::GenerateId("5");
const std::string id6 = crx_file::id_util::GenerateId("6");
AddExtensionToFetchDataForTesting(fetch_data.get(), id1, "1.0.0.0",
kUpdateURL);
AddParseResult(id1, "1.1", "http://localhost/e1_1.1.crx", &updates);
AddExtensionToFetchDataForTesting(fetch_data.get(), id2, "2.0.0.0",
kUpdateURL);
AddParseResult(id2, "2.0.0.0", "http://localhost/e2_2.0.crx", &updates);
// Empty update version in manifest.
AddExtensionToFetchDataForTesting(fetch_data.get(), id3, "0.0.0.0",
kUpdateURL);
AddParseResult(id3, "", "http://localhost/e3_3.0.crx", &updates);
AddExtensionToFetchDataForTesting(fetch_data.get(), id4, "0.0.0.0",
kUpdateURL);
AddExtensionToFetchDataForTesting(fetch_data.get(), id5, "0.0.0.0",
kUpdateURL);
AddParseResult(id5, "5.0.0.0", "http://localhost/e5_5.0.crx", &updates);
// Invalid update version in manifest.
AddExtensionToFetchDataForTesting(fetch_data.get(), id6, "0.0.0.0",
kUpdateURL);
AddParseResult(id6, "invalid_version", "http://localhost/e6_6.0.crx",
&updates);
EXPECT_CALL(delegate, IsExtensionPending(_)).WillRepeatedly(Return(false));
EXPECT_CALL(delegate, GetExtensionExistingVersion(id1, _))
.WillOnce(DoAll(SetArgPointee<1>("1.0.0.0"), Return(true)));
EXPECT_CALL(delegate, GetExtensionExistingVersion(id2, _))
.WillOnce(DoAll(SetArgPointee<1>("2.0.0.0"), Return(true)));
EXPECT_CALL(delegate, GetExtensionExistingVersion(id3, _))
.WillOnce(DoAll(SetArgPointee<1>("0.0.0.0"), Return(true)));
EXPECT_CALL(delegate, GetExtensionExistingVersion(id5, _))
.WillOnce(DoAll(SetArgPointee<1>("0.0.0.0"), Return(false)));
EXPECT_CALL(delegate, GetExtensionExistingVersion(id6, _))
.WillOnce(DoAll(SetArgPointee<1>("0.0.0.0"), Return(true)));
std::vector<std::pair<ExtensionDownloaderTask, UpdateManifestResult*>>
updateable;
std::vector<std::pair<ExtensionDownloaderTask, DownloadFailure>> failures;
helper.downloader().DetermineUpdates(fetch_data->TakeAssociatedTasks(),
updates, &updateable, &failures);
std::vector<ExtensionId> ids_not_updateable({id2, id3});
for (const auto& id : ids_not_updateable) {
const auto* failure = GetFailureWithId(failures, id);
ASSERT_TRUE(failure);
EXPECT_EQ(ExtensionDownloaderDelegate::Error::NO_UPDATE_AVAILABLE,
failure->error);
}
std::vector<ExtensionId> ids_with_error({id4, id5, id6});
for (const auto& id : ids_with_error) {
const auto* failure = GetFailureWithId(failures, id);
ASSERT_TRUE(failure);
EXPECT_EQ(ExtensionDownloaderDelegate::Error::MANIFEST_INVALID,
failure->error);
}
EXPECT_EQ(5u, failures.size());
ASSERT_EQ(1u, updateable.size());
EXPECT_EQ("1.1", updateable[0].second->version);
}
void TestDetermineUpdatesPending() {
// Create a set of test extensions
ExtensionDownloaderTestHelper helper;
ServiceForManifestTests service(prefs_.get(), helper.url_loader_factory());
PendingExtensionManager* pending_extension_manager =
service.pending_extension_manager();
SetupPendingExtensionManagerForTest(3, GURL(), pending_extension_manager);
MockExtensionDownloaderDelegate& delegate = helper.delegate();
GURL kUpdateURL("http://localhost/foo");
std::unique_ptr<ManifestFetchData> fetch_data(
CreateManifestFetchData(kUpdateURL));
UpdateManifestResults updates;
std::list<std::string> ids_for_update_check =
pending_extension_manager->GetPendingIdsForUpdateCheck();
for (const std::string& id : ids_for_update_check) {
AddExtensionToFetchDataForTesting(fetch_data.get(), id, "1.0.0.0",
kUpdateURL);
AddParseResult(id, "1.1", "http://localhost/e1_1.1.crx", &updates);
}
// The delegate will tell the downloader that all the extensions are
// pending.
EXPECT_CALL(delegate, IsExtensionPending(_)).WillRepeatedly(Return(true));
std::vector<std::pair<ExtensionDownloaderTask, UpdateManifestResult*>>
updateable;
std::vector<std::pair<ExtensionDownloaderTask, DownloadFailure>> failures;
helper.downloader().DetermineUpdates(fetch_data->TakeAssociatedTasks(),
updates, &updateable, &failures);
// All the apps should be updateable.
EXPECT_EQ(3u, updateable.size());
EXPECT_TRUE(failures.empty());
}
void TestDetermineUpdatesDuplicates() {
base::HistogramTester histogram_tester;
ExtensionDownloaderTestHelper helper;
MockExtensionDownloaderDelegate& delegate = helper.delegate();
const std::string id1 = crx_file::id_util::GenerateId("1");
const std::string id2 = crx_file::id_util::GenerateId("2");
const std::string id3 = crx_file::id_util::GenerateId("3");
const std::string id4 = crx_file::id_util::GenerateId("4");
const std::string id5 = crx_file::id_util::GenerateId("5");
const std::string id6 = crx_file::id_util::GenerateId("6");
const std::string id7 = crx_file::id_util::GenerateId("7");
GURL kUpdateURL("http://localhost/foo");
std::unique_ptr<ManifestFetchData> fetch_data(
CreateManifestFetchData(kUpdateURL));
AddExtensionToFetchDataForTesting(fetch_data.get(), id1, "1.1.0.0",
kUpdateURL);
AddExtensionToFetchDataForTesting(fetch_data.get(), id2, "1.2.0.0",
kUpdateURL);
AddExtensionToFetchDataForTesting(fetch_data.get(), id3, "1.3.0.0",
kUpdateURL);
AddExtensionToFetchDataForTesting(fetch_data.get(), id4, "1.4.0.0",
kUpdateURL);
AddExtensionToFetchDataForTesting(fetch_data.get(), id5, "1.5.0.0",
kUpdateURL);
AddExtensionToFetchDataForTesting(fetch_data.get(), id6, "1.6.0.0",
kUpdateURL);
AddExtensionToFetchDataForTesting(fetch_data.get(), id7, "1.7.0.0",
kUpdateURL);
UpdateManifestResults updates;
AddParseResult(id1, "1.1.0.0", "http://localhost/e1_1.1.crx", &updates);
AddParseResult(id2, "1.2.0.a", "http://localhost/e2_1.1.crx", &updates);
AddParseResult(id3, "1.3.1.0", "http://localhost/e3_1.1.crx", &updates);
AddParseResult(id4, "1.4.0.0", "http://localhost/e4_1.1.crx", &updates);
AddParseResult(id4, "1.4.0.a", "http://localhost/e4_1.1.crx", &updates);
AddParseResult(id5, "1.5.0.a", "http://localhost/e5_1.1.crx", &updates);
AddParseResult(id5, "1.5.0.b", "http://localhost/e5_1.1.crx", &updates);
AddParseResult(id6, "1.6.0.a", "http://localhost/e6_1.1.crx", &updates);
AddParseResult(id6, "1.6.0.0", "http://localhost/e6_1.1.crx", &updates);
AddParseResult(id6, "1.6.1.0", "http://localhost/e6_1.1.crx", &updates);
AddParseResult(id6, "1.6.2.0", "http://localhost/e6_1.1.crx", &updates);
EXPECT_CALL(delegate, IsExtensionPending(_)).WillRepeatedly(Return(false));
EXPECT_CALL(delegate, GetExtensionExistingVersion(id1, _))
.WillOnce(DoAll(SetArgPointee<1>("1.1.0.0"), Return(true)));
EXPECT_CALL(delegate, GetExtensionExistingVersion(id2, _))
.WillOnce(DoAll(SetArgPointee<1>("1.2.0.0"), Return(true)));
EXPECT_CALL(delegate, GetExtensionExistingVersion(id3, _))
.WillOnce(DoAll(SetArgPointee<1>("1.3.0.0"), Return(true)));
EXPECT_CALL(delegate, GetExtensionExistingVersion(id4, _))
.WillOnce(DoAll(SetArgPointee<1>("1.4.0.0"), Return(true)));
EXPECT_CALL(delegate, GetExtensionExistingVersion(id5, _))
.WillOnce(DoAll(SetArgPointee<1>("1.5.0.0"), Return(true)));
EXPECT_CALL(delegate, GetExtensionExistingVersion(id6, _))
.WillOnce(DoAll(SetArgPointee<1>("1.6.0.0"), Return(true)));
std::vector<std::pair<ExtensionDownloaderTask, UpdateManifestResult*>>
updateable;
std::vector<std::pair<ExtensionDownloaderTask, DownloadFailure>> failures;
helper.downloader().DetermineUpdates(fetch_data->TakeAssociatedTasks(),
updates, &updateable, &failures);
std::vector<ExtensionId> ids_not_updateable({id1, id4});
for (const auto& id : ids_not_updateable) {
const auto* failure = GetFailureWithId(failures, id);
ASSERT_TRUE(failure);
EXPECT_EQ(ExtensionDownloaderDelegate::Error::NO_UPDATE_AVAILABLE,
failure->error);
}
std::vector<ExtensionId> ids_with_error({id2, id5, id7});
for (const auto& id : ids_with_error) {
const auto* failure = GetFailureWithId(failures, id);
ASSERT_TRUE(failure);
EXPECT_EQ(ExtensionDownloaderDelegate::Error::MANIFEST_INVALID,
failure->error);
}
EXPECT_EQ(5u, failures.size());
ASSERT_EQ(2u, updateable.size());
EXPECT_EQ("1.3.1.0", updateable[0].second->version);
EXPECT_EQ("1.6.1.0", updateable[1].second->version);
}
void TestMultipleManifestDownloading() {
ExtensionDownloaderTestHelper helper;
MockExtensionDownloaderDelegate& delegate = helper.delegate();
helper.downloader().manifests_queue_.set_backoff_policy(kNoBackoffPolicy);
GURL kUpdateUrl("http://localhost/manifest1");
std::unique_ptr<ManifestFetchData> fetch1(
CreateManifestFetchData(kUpdateUrl));
std::unique_ptr<ManifestFetchData> fetch2(
CreateManifestFetchData(kUpdateUrl));
std::unique_ptr<ManifestFetchData> fetch3(
CreateManifestFetchData(kUpdateUrl));
std::unique_ptr<ManifestFetchData> fetch4(
CreateManifestFetchData(kUpdateUrl));
DownloadPingData zeroDays(0, 0, true, 0);
AddExtensionToFetchDataForTesting(fetch1.get(), "1111", "1.0", kUpdateUrl,
zeroDays);
AddExtensionToFetchDataForTesting(fetch2.get(), "2222", "2.0", kUpdateUrl,
zeroDays);
AddExtensionToFetchDataForTesting(fetch3.get(), "3333", "3.0", kUpdateUrl,
zeroDays);
AddExtensionToFetchDataForTesting(fetch4.get(), "4444", "4.0", kUpdateUrl,
zeroDays);
// This will start the first fetcher and queue the others. The next in queue
// is started as each fetcher receives its response. Note that the fetchers
// don't necessarily run in the order that they are started from here.
GURL fetch1_url = fetch1->full_url();
GURL fetch2_url = fetch2->full_url();
GURL fetch3_url = fetch3->full_url();
GURL fetch4_url = fetch4->full_url();
// fetch1_url
{
helper.StartUpdateCheck(std::move(fetch1));
RunUntilIdle();
helper.test_url_loader_factory().AddResponse(fetch1_url.spec(), "",
net::HTTP_BAD_REQUEST);
EXPECT_CALL(
delegate,
OnExtensionDownloadFailed(
"1111", ExtensionDownloaderDelegate::Error::MANIFEST_FETCH_FAILED,
_, _, _))
.WillOnce(InvokeWithoutArgs(&delegate,
&MockExtensionDownloaderDelegate::Quit));
delegate.Wait();
Mock::VerifyAndClearExpectations(&delegate);
fetch1_url = GURL();
RunUntilIdle();
}
// fetch2_url
{
helper.StartUpdateCheck(std::move(fetch2));
RunUntilIdle();
const std::string kInvalidXml = "invalid xml";
helper.test_url_loader_factory().AddResponse(fetch2_url.spec(),
kInvalidXml, net::HTTP_OK);
EXPECT_CALL(
delegate,
OnExtensionDownloadFailed(
"2222", ExtensionDownloaderDelegate::Error::MANIFEST_INVALID, _,
_, _))
.WillOnce(InvokeWithoutArgs(&delegate,
&MockExtensionDownloaderDelegate::Quit));
delegate.Wait();
Mock::VerifyAndClearExpectations(&delegate);
fetch2_url = GURL();
RunUntilIdle();
}
// fetch3_url
{
helper.StartUpdateCheck(std::move(fetch3));
RunUntilIdle();
const std::string kNoUpdate = CreateUpdateManifest(
{UpdateManifestItem("3333")
.version("3.0.0.0")
.prodversionmin("3.0.0.0")
.codebase("http://example.com/extension_3.0.0.0.crx")});
helper.test_url_loader_factory().AddResponse(fetch3_url.spec(), kNoUpdate,
net::HTTP_OK);
// The third fetcher doesn't have an update available.
EXPECT_CALL(delegate, IsExtensionPending("3333")).WillOnce(Return(false));
EXPECT_CALL(delegate, GetExtensionExistingVersion("3333", _))
.WillOnce(DoAll(SetArgPointee<1>("3.0.0.0"), Return(true)));
EXPECT_CALL(
delegate,
OnExtensionDownloadFailed(
"3333", ExtensionDownloaderDelegate::Error::NO_UPDATE_AVAILABLE,
_, _, _))
.WillOnce(InvokeWithoutArgs(&delegate,
&MockExtensionDownloaderDelegate::Quit));
delegate.Wait();
Mock::VerifyAndClearExpectations(&delegate);
fetch3_url = GURL();
RunUntilIdle();
}
// fetch4_url
{
helper.StartUpdateCheck(std::move(fetch4));
RunUntilIdle();
// The last fetcher has an update.
const std::string kUpdateAvailable = CreateUpdateManifest(
{UpdateManifestItem("4444")
.version("4.0.42.0")
.prodversionmin("4.0.42.0")
.codebase("http://example.com/extension_1.2.3.4.crx")});
helper.test_url_loader_factory().AddResponse(
fetch4_url.spec(), kUpdateAvailable, net::HTTP_OK);
EXPECT_CALL(delegate, IsExtensionPending("4444")).WillOnce(Return(false));
EXPECT_CALL(delegate, GetExtensionExistingVersion("4444", _))
.WillOnce(DoAll(SetArgPointee<1>("4.0.0.0"), Return(true)));
// Verify that the downloader decided to update this extension.
EXPECT_CALL(delegate,
OnExtensionUpdateFound("4444", _, base::Version("4.0.42.0")))
.WillOnce([&delegate]() { delegate.Quit(); });
delegate.Wait();
Mock::VerifyAndClearExpectations(&delegate);
fetch4_url = GURL();
}
if (helper.downloader().HasActiveManifestRequestForTesting())
ADD_FAILURE() << "Unexpected load";
}
void TestManifestRetryDownloading() {
ExtensionDownloaderTestHelper helper;
MockExtensionDownloaderDelegate& delegate = helper.delegate();
helper.downloader().manifests_queue_.set_backoff_policy(kNoBackoffPolicy);
GURL kUpdateUrl("http://localhost/manifest1");
std::unique_ptr<ManifestFetchData> fetch(
CreateManifestFetchData(kUpdateUrl));
DownloadPingData zeroDays(0, 0, true, 0);
fetch->AddExtension("1111", "1.0", &zeroDays,
ExtensionDownloaderTestHelper::kEmptyUpdateUrlData,
std::string(), ManifestLocation::kInternal,
DownloadFetchPriority::kBackground);
// This will start the first fetcher.
helper.StartUpdateCheck(std::move(fetch));
RunUntilIdle();
// ExtensionDownloader should retry kMaxRetries times and then fail.
EXPECT_CALL(
delegate,
OnExtensionDownloadFailed(
"1111", ExtensionDownloaderDelegate::Error::MANIFEST_FETCH_FAILED,
_, _, _));
helper.test_url_loader_factory().SetInterceptor(base::BindLambdaForTesting(
[&](const network::ResourceRequest& request) {
EXPECT_TRUE(request.load_flags == kExpectedLoadFlags);
EXPECT_EQ(network::mojom::CredentialsMode::kInclude,
request.credentials_mode);
}));
for (int i = 0; i <= ExtensionDownloader::kMaxRetries; ++i) {
// All fetches will fail.
auto* request = helper.GetPendingRequest(0);
// Code 5xx causes ExtensionDownloader to retry.
helper.test_url_loader_factory().SimulateResponseForPendingRequest(
request->request.url, network::URLLoaderCompletionStatus(net::OK),
network::CreateURLResponseHead(net::HTTP_INTERNAL_SERVER_ERROR), "");
RunUntilIdle();
}
Mock::VerifyAndClearExpectations(&delegate);
// For response codes that are not in the 5xx range ExtensionDownloader
// should not retry.
fetch.reset(CreateManifestFetchData(kUpdateUrl));
fetch->AddExtension("1111", "1.0", &zeroDays,
ExtensionDownloaderTestHelper::kEmptyUpdateUrlData,
std::string(), ManifestLocation::kInternal,
DownloadFetchPriority::kBackground);
// This will start the first fetcher.
helper.StartUpdateCheck(std::move(fetch));
RunUntilIdle();
EXPECT_CALL(
delegate,
OnExtensionDownloadFailed(
"1111", ExtensionDownloaderDelegate::Error::MANIFEST_FETCH_FAILED,
_, _, _));
// The first fetch will fail, and require retrying.
{
auto* request = helper.GetPendingRequest(0);
helper.test_url_loader_factory().SimulateResponseForPendingRequest(
request->request.url, network::URLLoaderCompletionStatus(net::OK),
network::CreateURLResponseHead(net::HTTP_INTERNAL_SERVER_ERROR), "");
}
RunUntilIdle();
// The second fetch will fail with response 400 and should not cause
// ExtensionDownloader to retry.
{
auto* request = helper.GetPendingRequest(0);
helper.test_url_loader_factory().SimulateResponseForPendingRequest(
request->request.url, network::URLLoaderCompletionStatus(net::OK),
network::CreateURLResponseHead(net::HTTP_BAD_REQUEST), "");
}
RunUntilIdle();
Mock::VerifyAndClearExpectations(&delegate);
}
void TestManifestCredentialsNonWebstore() {
ExtensionDownloaderTestHelper helper;
helper.downloader().manifests_queue_.set_backoff_policy(kNoBackoffPolicy);
GURL kUpdateUrl("http://localhost/manifest1");
std::unique_ptr<ManifestFetchData> fetch(
CreateManifestFetchData(kUpdateUrl));
DownloadPingData zeroDays(0, 0, true, 0);
fetch->AddExtension("1111", "1.0", &zeroDays,
ExtensionDownloaderTestHelper::kEmptyUpdateUrlData,
std::string(), ManifestLocation::kInternal,
DownloadFetchPriority::kBackground);
helper.StartUpdateCheck(std::move(fetch));
RunUntilIdle();
helper.test_url_loader_factory().SetInterceptor(base::BindLambdaForTesting(
[&](const network::ResourceRequest& request) {
EXPECT_EQ(network::mojom::CredentialsMode::kInclude,
request.credentials_mode);
}));
auto* request = helper.GetPendingRequest(0);
helper.test_url_loader_factory().SimulateResponseForPendingRequest(
request->request.url, network::URLLoaderCompletionStatus(net::OK),
network::CreateURLResponseHead(net::HTTP_INTERNAL_SERVER_ERROR), "");
RunUntilIdle();
}
void TestManifestCredentialsWebstore() {
ExtensionDownloaderTestHelper helper;
helper.downloader().manifests_queue_.set_backoff_policy(kNoBackoffPolicy);
GURL kUpdateUrl(extension_urls::kChromeWebstoreUpdateURL);
std::unique_ptr<ManifestFetchData> fetch(
CreateManifestFetchData(kUpdateUrl));
DownloadPingData zeroDays(0, 0, true, 0);
fetch->AddExtension("1111", "1.0", &zeroDays,
ExtensionDownloaderTestHelper::kEmptyUpdateUrlData,
std::string(), ManifestLocation::kInternal,
DownloadFetchPriority::kBackground);
helper.StartUpdateCheck(std::move(fetch));
RunUntilIdle();
helper.test_url_loader_factory().SetInterceptor(base::BindLambdaForTesting(
[&](const network::ResourceRequest& request) {
EXPECT_EQ(network::mojom::CredentialsMode::kOmit,
request.credentials_mode);
}));
auto* request = helper.GetPendingRequest(0);
helper.test_url_loader_factory().SimulateResponseForPendingRequest(
request->request.url, network::URLLoaderCompletionStatus(net::OK),
network::CreateURLResponseHead(net::HTTP_INTERNAL_SERVER_ERROR), "");
RunUntilIdle();
}
// Checks that the manifest is fetched with a priority of |net::MEDIUM| (which
// maps to |TaskPriority::USER_BLOCKING| for the task runner) when the active
// request's |fetch_priority| is in the FOREGROUND.
void TestManifestFetchPriority(DownloadFetchPriority fetch_priority) {
ExtensionDownloaderTestHelper helper;
helper.downloader().manifests_queue_.set_backoff_policy(kNoBackoffPolicy);
GURL test_url("http://localhost/manifest1");
std::unique_ptr<ManifestFetchData> fetch(
CreateManifestFetchData(test_url));
DownloadPingData zero_days(0, 0, true, 0);
fetch->AddExtension("1111", "1.0", &zero_days,
ExtensionDownloaderTestHelper::kEmptyUpdateUrlData,
std::string(), ManifestLocation::kInternal,
fetch_priority);
helper.StartUpdateCheck(std::move(fetch));
RunUntilIdle();
auto* request = helper.GetPendingRequest(0);
helper.test_url_loader_factory().SimulateResponseForPendingRequest(
request->request.url, network::URLLoaderCompletionStatus(net::OK),
network::CreateURLResponseHead(net::HTTP_INTERNAL_SERVER_ERROR), "");
RunUntilIdle();
if (fetch_priority == DownloadFetchPriority::kForeground) {
EXPECT_EQ(net::MEDIUM, request->request.priority);
} else {
EXPECT_EQ(net::IDLE, request->request.priority);
}
}
// Checks that the crx of a given extension is downloaded with a priority of
// |net::MEDIUM| (maps to |TaskPriority::USER_BLOCKING| for the task runner)
// when the active request's |fetch_priority| is in the FOREGROUND.
void TestSingleExtensionDownloadingPriority(
DownloadFetchPriority fetch_priority) {
ExtensionUpdater::ScopedSkipScheduledCheckForTest skip_scheduled_checks;
ExtensionDownloaderTestHelper helper;
std::unique_ptr<ServiceForDownloadTests> service =
std::make_unique<ServiceForDownloadTests>(prefs_.get(),
helper.url_loader_factory());
ExtensionUpdater updater(service.get(), service->extension_prefs(),
service->pref_service(), service->profile(),
kUpdateFrequencySecs, nullptr,
service->GetDownloaderFactory());
MockExtensionDownloaderDelegate delegate;
delegate.DelegateTo(&updater);
service->OverrideDownloaderDelegate(&delegate);
updater.Start();
updater.EnsureDownloaderCreated();
updater.downloader_->extensions_queue_.set_backoff_policy(kNoBackoffPolicy);
GURL test_url("http://localhost/extension.crx");
const std::string id(32, 'a');
std::string hash;
CRXFileInfo crx_file_info;
base::Version version("0.0.1");
ExtensionDownloaderTask task = CreateDownloaderTask(id);
task.fetch_priority = fetch_priority;
std::unique_ptr<ExtensionDownloader::ExtensionFetch> fetch =
std::make_unique<ExtensionDownloader::ExtensionFetch>(
std::move(task), test_url, hash, version.GetString(),
fetch_priority);
updater.downloader_->FetchUpdatedExtension(std::move(fetch), std::nullopt);
auto* request = helper.GetPendingRequest(0);
if (fetch_priority == DownloadFetchPriority::kForeground) {
EXPECT_EQ(net::MEDIUM, request->request.priority);
} else {
EXPECT_EQ(net::IDLE, request->request.priority);
}
}
void TestSingleExtensionDownloading(bool pending, bool retry, bool fail) {
ExtensionUpdater::ScopedSkipScheduledCheckForTest skip_scheduled_checks;
ExtensionDownloaderTestHelper helper;
std::unique_ptr<ServiceForDownloadTests> service =
std::make_unique<ServiceForDownloadTests>(prefs_.get(),
helper.url_loader_factory());
ExtensionUpdater updater(service.get(), service->extension_prefs(),
service->pref_service(), service->profile(),
kUpdateFrequencySecs, nullptr,
service->GetDownloaderFactory());
MockExtensionDownloaderDelegate delegate;
delegate.DelegateTo(&updater);
service->OverrideDownloaderDelegate(&delegate);
updater.Start();
updater.EnsureDownloaderCreated();
updater.downloader_->extensions_queue_.set_backoff_policy(kNoBackoffPolicy);
GURL test_url("http://localhost/extension.crx");
const std::string id(32, 'a');
std::string hash;
CRXFileInfo crx_file_info;
base::Version version("0.0.1");
std::set<int> requests;
requests.insert(0);
std::unique_ptr<ExtensionDownloader::ExtensionFetch> fetch =
std::make_unique<ExtensionDownloader::ExtensionFetch>(
CreateDownloaderTask(id), test_url, hash, version.GetString(),
DownloadFetchPriority::kBackground);
updater.downloader_->FetchUpdatedExtension(std::move(fetch), std::nullopt);
if (pending) {
const bool kIsFromSync = true;
const bool kMarkAcknowledged = false;
const bool kRemoteInstall = false;
PendingExtensionManager* pending_extension_manager =
service->pending_extension_manager();
pending_extension_manager->AddForTesting(PendingExtensionInfo(
id, std::string(), test_url, version, &ShouldAlwaysInstall,
kIsFromSync, ManifestLocation::kInternal, Extension::NO_FLAGS,
kMarkAcknowledged, kRemoteInstall));
}
if (retry) {
EXPECT_CALL(delegate, OnExtensionDownloadRetryForTests())
.WillOnce(DoAll(
InvokeWithoutArgs(&delegate,
&MockExtensionDownloaderDelegate::Quit),
InvokeWithoutArgs(&helper, &ExtensionDownloaderTestHelper::
ClearURLLoaderFactoryResponses)));
helper.test_url_loader_factory().AddResponse(
test_url.spec(), "", net::HTTP_INTERNAL_SERVER_ERROR);
delegate.Wait();
EXPECT_TRUE(updater.downloader_->extension_loader_);
}
if (fail) {
EXPECT_CALL(delegate, OnExtensionDownloadFailed(id, _, _, requests, _))
.WillOnce(DoAll(
InvokeWithoutArgs(&delegate,
&MockExtensionDownloaderDelegate::Quit),
InvokeWithoutArgs(&helper, &ExtensionDownloaderTestHelper::
ClearURLLoaderFactoryResponses)));
helper.test_url_loader_factory().AddResponse(
test_url.spec(), "Any content. It is irrelevant.",
net::HTTP_NOT_FOUND);
delegate.Wait();
} else {
EXPECT_TRUE(updater.downloader_->extension_loader_);
EXPECT_CALL(delegate,
OnExtensionDownloadFinished_(_, _, _, _, requests, _))
.WillOnce(
DoAll(testing::SaveArg<0>(&crx_file_info),
InvokeWithoutArgs(&delegate,
&MockExtensionDownloaderDelegate::Quit)));
helper.test_url_loader_factory().AddResponse(
test_url.spec(), "Any content. It is irrelevant.");
delegate.Wait();
EXPECT_EQ(version, crx_file_info.expected_version);
}
if (fail) {
// Don't expect any extension to have been installed.
EXPECT_TRUE(service->extension_id().empty());
} else {
// Expect that ExtensionUpdater asked the mock extensions service to
// install a file with the test data for the right id.
EXPECT_EQ(id, crx_file_info.extension_id);
base::FilePath tmpfile_path = crx_file_info.path;
EXPECT_FALSE(tmpfile_path.empty());
}
}
// Helper to create a new test file of zeros.
void CreateFile(const base::FilePath& file,
size_t size,
const base::Time& timestamp) {
const std::string data(size, 0);
EXPECT_TRUE(base::WriteFile(file, data));
EXPECT_TRUE(base::TouchFile(file, timestamp, timestamp));
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
// This tests the condition when the entry for the crx file is already
// present in the cache but the crx file is itself corrupted. In this case,
// after detecting the corruption of the crx file, it's entry should be
// removed from the cache and re-downloaded from the link in the update
// manifest.
void TestCacheCorruption() {
const char kTestExtensionId[] = "test_app";
const std::string version = "1.1";
const std::string hash = "abcd";
ExtensionDownloaderTestHelper helper;
// Set update manifest fetch data and result.
GURL kUpdateURL("http://localhost/foo");
std::unique_ptr<ManifestFetchData> fetch(
CreateManifestFetchData(kUpdateURL));
AddExtensionToFetchDataForTesting(fetch.get(), kTestExtensionId, "1.0",
kUpdateURL);
const std::string manifest = CreateUpdateManifest(
{UpdateManifestItem(kTestExtensionId)
.version(version)
.hash(hash)
.codebase("http://example.com/extension_1.2.3.4.crx")
.prodversionmin("1.1")});
helper.test_url_loader_factory().AddResponse(fetch->full_url().spec(),
manifest, net::HTTP_OK);
// We need crx installer to install the crx file and also for mock extension
// service. CrxInstallers require a real ExtensionService. Create one on
// the testing profile. Any action the CrxInstallers take is on the testing
// profile's extension service, not on our mock |service|.
TestingProfile profile;
static_cast<TestExtensionSystem*>(ExtensionSystem::Get(&profile))
->CreateExtensionService(base::CommandLine::ForCurrentProcess(),
base::FilePath(), false);
ExtensionService* extension_service =
ExtensionSystem::Get(&profile)->extension_service();
scoped_refptr<MockCrxInstaller> mock_installer =
base::MakeRefCounted<MockCrxInstaller>(extension_service);
// Do nothing when called the first time, by ExtensionUpdater
// But do the real action when called later on by this test code.
EXPECT_CALL(*mock_installer, InstallCrxFile(_))
.WillOnce(Return())
.WillOnce([&mock_installer](const CRXFileInfo& info) {
return mock_installer->CrxInstaller::InstallCrxFile(info);
});
// Just let the real CrxInstaller implementation have the callback.
EXPECT_CALL(*mock_installer, AddInstallerCallback(_))
.WillOnce(Invoke([&](CrxInstaller::InstallerResultCallback callback) {
mock_installer->CrxInstaller::AddInstallerCallback(
std::move(callback));
}));
mock_installer->set_expected_id(kTestExtensionId);
mock_installer->set_expected_hash(hash);
// Create mock extension service for test. We need this mock service so that
// the extension updater process can be intercepted before the installer
// which is then called explicitly.
std::unique_ptr<ServiceForDownloadTests> service =
std::make_unique<ServiceForDownloadTests>(prefs_.get(),
helper.url_loader_factory());
service->AddFakeCrxInstaller(kTestExtensionId, mock_installer);
ExtensionUpdater updater(service.get(), service->extension_prefs(),
service->pref_service(), service->profile(),
kUpdateFrequencySecs, nullptr,
service->GetDownloaderFactory());
MockExtensionDownloaderDelegate& delegate = helper.delegate();
delegate.DelegateTo(&updater);
service->OverrideDownloaderDelegate(&delegate);
updater.Start();
// Create and initialize local cache.
const base::Time now = base::Time::Now();
base::ScopedTempDir cache_dir;
ASSERT_TRUE(cache_dir.CreateUniqueTempDir());
const base::FilePath cache_path = cache_dir.GetPath();
CreateFile(cache_path.Append(LocalExtensionCache::kCacheReadyFlagFileName),
0, now);
ExtensionCacheImpl test_extension_cache(
std::make_unique<ChromeOSExtensionCacheDelegate>(cache_path));
base::RunLoop cache_init_run_loop;
test_extension_cache.Start(cache_init_run_loop.QuitClosure());
cache_init_run_loop.Run();
// Create crx file in a temp directory.
base::ScopedTempDir tmp_dir;
ASSERT_TRUE(tmp_dir.CreateUniqueTempDir());
const base::FilePath tmp_path = tmp_dir.GetPath();
const base::FilePath filename =
tmp_path.Append(LocalExtensionCache::ExtensionFileName(
kTestExtensionId, version, "" /* hash */));
// Create a small file of zeroes, e.g. 100 bytes size.
CreateFile(filename, 100, now - base::Seconds(3));
// Add crx file entry in the cache.
base::RunLoop put_extension_run_loop;
test_extension_cache.AllowCaching("test_app");
test_extension_cache.PutExtension(
kTestExtensionId, "" /* expected hash*/, filename, version,
base::BindLambdaForTesting(
[&put_extension_run_loop](const base::FilePath& file_path,
bool file_ownership_passed) {
put_extension_run_loop.Quit();
}));
put_extension_run_loop.Run();
// Set cache in extension downloader.
helper.downloader().StartAllPending(&test_extension_cache);
EXPECT_CALL(delegate, IsExtensionPending(kTestExtensionId))
.WillOnce(Return(true));
// Download the update manifest for the extension, find the same extension
// version in the cache, start installing the cached crx file which fails
// due to unpacker error and is hence, removed from the cache and
// re-downlaoded for installation.
testing::Sequence sequence;
EXPECT_CALL(delegate,
OnExtensionDownloadStageChanged(
kTestExtensionId,
ExtensionDownloaderDelegate::Stage::QUEUED_FOR_MANIFEST))
.Times(testing::AnyNumber());
EXPECT_CALL(delegate,
OnExtensionDownloadStageChanged(
kTestExtensionId,
ExtensionDownloaderDelegate::Stage::DOWNLOADING_MANIFEST))
.InSequence(sequence);
EXPECT_CALL(delegate,
OnExtensionDownloadStageChanged(
kTestExtensionId,
ExtensionDownloaderDelegate::Stage::PARSING_MANIFEST))
.InSequence(sequence);
EXPECT_CALL(delegate,
OnExtensionDownloadStageChanged(
kTestExtensionId,
ExtensionDownloaderDelegate::Stage::MANIFEST_LOADED))
.InSequence(sequence);
EXPECT_CALL(delegate,
OnExtensionDownloadCacheStatusRetrieved(
kTestExtensionId,
ExtensionDownloaderDelegate::CacheStatus::CACHE_HIT))
.InSequence(sequence);
EXPECT_CALL(delegate, OnExtensionDownloadStageChanged(
kTestExtensionId,
ExtensionDownloaderDelegate::Stage::FINISHED))
.InSequence(sequence);
EXPECT_CALL(delegate,
OnExtensionDownloadStageChanged(
kTestExtensionId,
ExtensionDownloaderDelegate::Stage::DOWNLOADING_CRX))
.InSequence(sequence);
helper.StartUpdateCheck(std::move(fetch));
content::RunAllTasksUntilIdle();
LoadErrorReporter::Init(false);
updater.SetExtensionCacheForTesting(&test_extension_cache);
CRXFileInfo crx_info(filename, GetTestVerifierFormat());
crx_info.extension_id = kTestExtensionId;
crx_info.expected_hash = hash;
// This time call the real InstallCrxFile implementation (see expectation
// set above for mock_installer).
mock_installer->InstallCrxFile(crx_info);
content::RunAllTasksUntilIdle();
testing::Mock::VerifyAndClearExpectations(&delegate);
}
#endif
// Update a single extension in an environment where the download request
// initially responds with a 403 status. If |identity_provider| is not NULL,
// this will first expect a request which includes an Authorization header
// with an OAuth2 bearer token; otherwise, or if OAuth2 failure is simulated,
// this expects the downloader to fall back onto cookie-based credentials.
void TestProtectedDownload(
const std::string& url_prefix,
bool enable_oauth2,
bool succeed_with_oauth2,
int valid_authuser,
int max_authuser) {
ExtensionDownloaderTestHelper helper;
std::unique_ptr<ServiceForDownloadTests> service =
std::make_unique<ServiceForDownloadTests>(prefs_.get(),
helper.url_loader_factory());
const ExtensionDownloader::Factory& downloader_factory =
enable_oauth2 ? service->GetAuthenticatedDownloaderFactory()
: service->GetDownloaderFactory();
ExtensionUpdater updater(service.get(), service->extension_prefs(),
service->pref_service(), service->profile(),
kUpdateFrequencySecs, nullptr, downloader_factory);
MockExtensionDownloaderDelegate delegate;
delegate.DelegateTo(&updater);
service->OverrideDownloaderDelegate(&delegate);
updater.Start();
updater.EnsureDownloaderCreated();
updater.downloader_->extensions_queue_.set_backoff_policy(kNoBackoffPolicy);
GURL test_url(base::StringPrintf("%s/extension.crx", url_prefix.c_str()));
const std::string id(32, 'a');
std::string hash;
base::Version version("0.0.1");
std::unique_ptr<ExtensionDownloader::ExtensionFetch> extension_fetch =
std::make_unique<ExtensionDownloader::ExtensionFetch>(
CreateDownloaderTask(id), test_url, hash, version.GetString(),
DownloadFetchPriority::kBackground);
updater.downloader_->FetchUpdatedExtension(std::move(extension_fetch),
std::nullopt);
EXPECT_EQ(
kExpectedLoadFlags,
updater.downloader_->last_extension_loader_load_flags_for_testing_);
// Fake a 403 response.
EXPECT_CALL(delegate, OnExtensionDownloadRetryForTests())
.WillOnce(DoAll(
InvokeWithoutArgs(&delegate,
&MockExtensionDownloaderDelegate::Quit),
InvokeWithoutArgs(&helper, &ExtensionDownloaderTestHelper::
ClearURLLoaderFactoryResponses)));
helper.test_url_loader_factory().AddResponse(test_url.spec(), "",
net::HTTP_FORBIDDEN);
delegate.Wait();
// Only call out to WaitForAccessTokenRequest(...) method below if
// HTTPS is in use in a google domain and oauth is explicitly enabled.
// Otherwise, test will await an access token request that have not
// (and will not) happen.
//
// Note that in case the condition below isn't satisfied, the download
// proceeds normally, but the request does not carry an 'Authorization'
// HTTP header.
if (enable_oauth2 && test_url.DomainIs("google.com") &&
test_url.SchemeIsCryptographic()) {
service->identity_test_env()
->WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
service->account_id(), kFakeOAuth2Token, base::Time::Now());
}
bool using_oauth2 = false;
int expected_load_flags = kExpectedLoadFlags;
// Verify that the fetch has had its credentials properly incremented.
EXPECT_TRUE(updater.downloader_->extension_loader_);
net::HttpRequestHeaders fetch_headers =
updater.downloader_
->last_extension_loader_resource_request_headers_for_testing_;
// If the download URL is not https, no credentials should be provided.
if (!test_url.SchemeIsCryptographic()) {
// No cookies.
EXPECT_EQ(
kExpectedLoadFlags,
updater.downloader_->last_extension_loader_load_flags_for_testing_);
// No Authorization header.
EXPECT_FALSE(
fetch_headers.HasHeader(net::HttpRequestHeaders::kAuthorization));
expected_load_flags = kExpectedLoadFlags;
} else {
// HTTPS is in use, so credentials are allowed.
if (enable_oauth2 && test_url.DomainIs("google.com")) {
// If an IdentityProvider is present and the URL is a google.com
// URL, the fetcher should be in OAuth2 mode after the intitial
// challenge.
EXPECT_TRUE(fetch_headers.HasHeader(
net::HttpRequestHeaders::kAuthorization));
std::string expected_header_value = base::StringPrintf("Bearer %s",
kFakeOAuth2Token);
std::string actual_header_value;
fetch_headers.GetHeader(net::HttpRequestHeaders::kAuthorization,
&actual_header_value);
EXPECT_EQ(expected_header_value, actual_header_value);
using_oauth2 = true;
} else {
// No IdentityProvider (or no google.com), so expect cookies instead of
// an Authorization header.
EXPECT_FALSE(fetch_headers.HasHeader(
net::HttpRequestHeaders::kAuthorization));
EXPECT_EQ(
kExpectedLoadFlagsForDownloadWithCookies,
updater.downloader_->last_extension_loader_load_flags_for_testing_);
expected_load_flags = kExpectedLoadFlagsForDownloadWithCookies;
}
}
bool success = false;
if (using_oauth2) {
if (succeed_with_oauth2) {
success = true;
} else {
// Simulate OAuth2 failure and ensure that we fall back on cookies.
EXPECT_CALL(delegate, OnExtensionDownloadRetryForTests())
.WillOnce(
DoAll(InvokeWithoutArgs(&delegate,
&MockExtensionDownloaderDelegate::Quit),
InvokeWithoutArgs(&helper,
&ExtensionDownloaderTestHelper::
ClearURLLoaderFactoryResponses)));
helper.test_url_loader_factory().AddResponse(test_url.spec(), "",
net::HTTP_FORBIDDEN);
delegate.Wait();
const ExtensionDownloader::ExtensionFetch& fetch =
*updater.downloader_->extensions_queue_.active_request();
EXPECT_EQ(0, GetAuthUserQueryValue(fetch.url));
EXPECT_EQ(ExtensionDownloader::ExtensionFetch::CREDENTIALS_COOKIES,
fetch.credentials);
EXPECT_TRUE(updater.downloader_->extension_loader_);
fetch_headers =
updater.downloader_
->last_extension_loader_resource_request_headers_for_testing_;
EXPECT_FALSE(
fetch_headers.HasHeader(net::HttpRequestHeaders::kAuthorization));
EXPECT_EQ(
kExpectedLoadFlagsForDownloadWithCookies,
updater.downloader_->last_extension_loader_load_flags_for_testing_);
expected_load_flags = kExpectedLoadFlagsForDownloadWithCookies;
}
}
if (!success) {
// Not yet ready to simulate a successful fetch. At this point we begin
// simulating cookie-based authentication with increasing values of
// authuser (starting from 0.)
int user_index = 0;
for (; user_index <= max_authuser; ++user_index) {
const ExtensionDownloader::ExtensionFetch& fetch =
*updater.downloader_->extensions_queue_.active_request();
EXPECT_EQ(user_index, GetAuthUserQueryValue(fetch.url));
if (user_index == valid_authuser) {
success = true;
break;
}
// Simulate an authorization failure which should elicit an increment
// of the authuser value.
EXPECT_TRUE(updater.downloader_->extension_loader_);
EXPECT_EQ(
expected_load_flags,
updater.downloader_->last_extension_loader_load_flags_for_testing_);
EXPECT_CALL(delegate, OnExtensionDownloadRetryForTests())
.WillOnce(
DoAll(InvokeWithoutArgs(&delegate,
&MockExtensionDownloaderDelegate::Quit),
InvokeWithoutArgs(&helper,
&ExtensionDownloaderTestHelper::
ClearURLLoaderFactoryResponses)));
helper.test_url_loader_factory().AddResponse(
fetch.url.spec(), "whatever", net::HTTP_FORBIDDEN);
delegate.Wait();
}
// Simulate exhaustion of all available authusers.
if (!success && user_index > max_authuser) {
const ExtensionDownloader::ExtensionFetch& fetch =
*updater.downloader_->extensions_queue_.active_request();
EXPECT_TRUE(updater.downloader_->extension_loader_);
helper.test_url_loader_factory().AddResponse(
fetch.url.spec(), std::string(), net::HTTP_UNAUTHORIZED);
EXPECT_CALL(delegate, OnExtensionDownloadFailed(_, _, _, _, _))
.WillOnce(InvokeWithoutArgs(
&delegate, &MockExtensionDownloaderDelegate::Quit));
delegate.Wait();
}
}
// Simulate successful authorization with a 200 response.
if (success) {
EXPECT_TRUE(updater.downloader_->extension_loader_);
const ExtensionDownloader::ExtensionFetch& fetch =
*updater.downloader_->extensions_queue_.active_request();
CRXFileInfo crx_file_info;
EXPECT_CALL(delegate, OnExtensionDownloadFinished_(_, _, _, _, _, _))
.WillOnce(
DoAll(testing::SaveArg<0>(&crx_file_info),
InvokeWithoutArgs(&delegate,
&MockExtensionDownloaderDelegate::Quit)));
helper.test_url_loader_factory().AddResponse(fetch.url.spec(),
"whatever");
delegate.Wait();
// Verify installation would proceed as normal.
EXPECT_EQ(id, crx_file_info.extension_id);
base::FilePath tmpfile_path = crx_file_info.path;
EXPECT_FALSE(tmpfile_path.empty());
}
}
// Two extensions are updated. If |updates_start_running| is true, the
// mock extensions service has CreateUpdateInstaller(...) return the
// fake CrxInstallers created by the test. Otherwise, CreateUpdateInstaller()
// returns nullptr, signaling install failures.
void TestMultipleExtensionDownloading(bool updates_start_running) {
ExtensionDownloaderTestHelper helper;
ServiceForDownloadTests service(prefs_.get(), helper.url_loader_factory());
ExtensionUpdater updater(&service, service.extension_prefs(),
service.pref_service(), service.profile(),
kUpdateFrequencySecs, nullptr,
service.GetDownloaderFactory());
updater.Start();
updater.EnsureDownloaderCreated();
updater.downloader_->extensions_queue_.set_backoff_policy(kNoBackoffPolicy);
EXPECT_THAT(GetRunningInstallIds(updater), testing::IsEmpty());
GURL url1("http://localhost/extension1.crx");
GURL url2("http://localhost/extension2.crx");
const std::string id1(32, 'a');
const std::string id2(32, 'b');
std::string hash1;
std::string hash2;
std::string version1 = "0.1";
std::string version2 = "0.1";
// Start two fetches
std::unique_ptr<ExtensionDownloader::ExtensionFetch> fetch1 =
std::make_unique<ExtensionDownloader::ExtensionFetch>(
CreateDownloaderTask(id1), url1, hash1, version1,
DownloadFetchPriority::kBackground);
std::unique_ptr<ExtensionDownloader::ExtensionFetch> fetch2 =
std::make_unique<ExtensionDownloader::ExtensionFetch>(
CreateDownloaderTask(id2), url2, hash2, version2,
DownloadFetchPriority::kBackground);
updater.downloader_->FetchUpdatedExtension(std::move(fetch1),
std::optional<std::string>());
updater.downloader_->FetchUpdatedExtension(std::move(fetch2),
std::optional<std::string>());
// Make the first fetch complete.
EXPECT_TRUE(updater.downloader_->extension_loader_);
EXPECT_EQ(
kExpectedLoadFlags,
updater.downloader_->last_extension_loader_load_flags_for_testing_);
// We need some CrxInstallers, and CrxInstallers require a real
// ExtensionService. Create one on the testing profile. Any action
// the CrxInstallers take is on the testing profile's extension
// service, not on our mock |service|. This allows us to fake
// the CrxInstaller actions we want.
TestingProfile profile;
static_cast<TestExtensionSystem*>(ExtensionSystem::Get(&profile))
->CreateExtensionService(base::CommandLine::ForCurrentProcess(),
base::FilePath(), false);
ExtensionService* extension_service =
ExtensionSystem::Get(&profile)->extension_service();
scoped_refptr<FakeCrxInstaller> fake_crx1 =
base::MakeRefCounted<FakeCrxInstaller>(extension_service);
scoped_refptr<FakeCrxInstaller> fake_crx2 =
base::MakeRefCounted<FakeCrxInstaller>(extension_service);
if (updates_start_running) {
// Add mock CrxInstaller to be returned by
// service.CreateUpdateInstaller().
service.AddFakeCrxInstaller(id1, fake_crx1);
service.AddFakeCrxInstaller(id2, fake_crx2);
} else {
// If we don't add mock CRX installers, the mock service will just return
// nullptr, meaning a failure.
}
helper.test_url_loader_factory().AddResponse(
url1.spec(), "Any content. This is irrelevant.", net::HTTP_OK);
content::RunAllTasksUntilIdle();
// Expect that the service was asked to do an install with the right data.
base::FilePath tmpfile_path = service.install_path();
EXPECT_FALSE(tmpfile_path.empty());
EXPECT_EQ(id1, service.extension_id());
RunUntilIdle();
// Make sure the second fetch finished and asked the service to do an
// update.
EXPECT_TRUE(updater.downloader_->extension_loader_);
EXPECT_EQ(
kExpectedLoadFlags,
updater.downloader_->last_extension_loader_load_flags_for_testing_);
helper.test_url_loader_factory().AddResponse(
url2.spec(), "Any other content. This is irrelevant.", net::HTTP_OK);
content::RunAllTasksUntilIdle();
if (updates_start_running) {
// Both installations should have launched in parallel.
EXPECT_THAT(GetRunningInstallIds(updater),
testing::UnorderedElementsAre(id1, id2));
// Fake install notice. This should end the first installation.
fake_crx1->RunInstallerCallbacks(CrxInstallError(
CrxInstallErrorType::OTHER, CrxInstallErrorDetail::NONE));
content::RunAllTasksUntilIdle();
EXPECT_THAT(GetRunningInstallIds(updater),
testing::UnorderedElementsAre(id2));
fake_crx2->RunInstallerCallbacks(CrxInstallError(
CrxInstallErrorType::OTHER, CrxInstallErrorDetail::NONE));
content::RunAllTasksUntilIdle();
}
EXPECT_THAT(GetRunningInstallIds(updater), testing::IsEmpty());
}
void TestGalleryRequestsWithBrand(bool use_organic_brand_code) {
google_brand::BrandForTesting brand_for_testing(
use_organic_brand_code ? "GGLS" : "TEST");
// 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 < std::size(ping_cases); i++) {
for (size_t j = 0; j < std::size(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;
TestGalleryRequests(rollcall_ping_days, active_ping_days, active_bit,
!use_organic_brand_code);
ASSERT_FALSE(HasFailure()) <<
" rollcall_ping_days=" << ping_cases[i] <<
" active_ping_days=" << ping_cases[j] <<
" active_bit=" << active_bit;
}
}
}
}
// 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).
void TestGalleryRequests(int rollcall_ping_days,
int active_ping_days,
bool active_bit,
bool expect_brand_code) {
// Set up 2 mock extensions, one with a google.com update url and one
// without.
prefs_ = std::make_unique<TestExtensionPrefs>(
base::SingleThreadTaskRunner::GetCurrentDefault());
ExtensionDownloaderTestHelper helper;
ServiceForManifestTests service(prefs_.get(), helper.url_loader_factory());
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(),
ManifestLocation::kInternal);
service.CreateTestExtensions(2, 1, &tmp, &url2.possibly_invalid_spec(),
ManifestLocation::kInternal);
EXPECT_EQ(2u, tmp.size());
service.set_extensions(tmp, ExtensionList());
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 - base::Seconds(15));
} else if (rollcall_ping_days > 0) {
Time last_ping_day =
now - base::Days(rollcall_ping_days) - base::Seconds(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 - base::Seconds(15));
} else if (active_ping_days > 0) {
Time last_active_ping_day =
now - base::Days(active_ping_days) - base::Seconds(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, nullptr,
service.GetDownloaderFactory());
updater.Start();
updater.CheckNow(ExtensionUpdater::CheckParams());
// Make the updater do manifest fetching, and note the urls it tries to
// fetch.
std::vector<GURL> fetched_urls;
ASSERT_TRUE(updater.downloader_->HasActiveManifestRequestForTesting());
const ManifestFetchData& fetch =
*updater.downloader_->manifests_queue_.active_request();
fetched_urls.push_back(fetch.full_url());
helper.test_url_loader_factory().AddResponse(
fetched_urls[0].spec(), std::string(), net::HTTP_INTERNAL_SERVER_ERROR);
RunUntilIdle();
const ManifestFetchData& fetch2 =
*updater.downloader_->manifests_queue_.active_request();
fetched_urls.push_back(fetch2.full_url());
// The urls could have been fetched in either order, so use the host to
// tell them apart and note the query each used.
GURL url1_fetch_url;
GURL url2_fetch_url;
std::string url1_query;
std::string url2_query;
if (fetched_urls[0].host() == url1.host()) {
url1_fetch_url = fetched_urls[0];
url2_fetch_url = fetched_urls[1];
url1_query = fetched_urls[0].query();
url2_query = fetched_urls[1].query();
} else if (fetched_urls[0].host() == url2.host()) {
url1_fetch_url = fetched_urls[1];
url2_fetch_url = fetched_urls[0];
url1_query = fetched_urls[1].query();
url2_query = fetched_urls[0].query();
} else {
NOTREACHED();
}
std::map<std::string, ParamsMap> url1_ping_data =
GetPingDataFromURL(url1_fetch_url);
ParamsMap url1_params = ParamsMap();
if (!url1_ping_data.empty() && base::Contains(url1_ping_data, id))
url1_params = url1_ping_data[id];
// First make sure the non-google query had no ping parameter.
EXPECT_TRUE(GetPingDataFromURL(url2_fetch_url).empty());
// Now make sure the google query had the correct ping parameter.
bool did_rollcall = false;
if (rollcall_ping_days != 0) {
ASSERT_TRUE(base::Contains(url1_params, "r"));
ASSERT_EQ(1u, url1_params["r"].size());
EXPECT_EQ(base::NumberToString(rollcall_ping_days),
*url1_params["r"].begin());
did_rollcall = true;
}
if (active_bit && active_ping_days != 0 && did_rollcall) {
ASSERT_TRUE(base::Contains(url1_params, "a"));
ASSERT_EQ(1u, url1_params["a"].size());
EXPECT_EQ(base::NumberToString(active_ping_days),
*url1_params["a"].begin());
}
// Make sure the non-google query has no brand parameter.
const std::string brand_string = "brand%3D";
EXPECT_TRUE(url2_query.find(brand_string) == std::string::npos);
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
// Make sure the google query has a brand parameter, but only if the
// brand is non-organic.
if (expect_brand_code) {
EXPECT_TRUE(url1_query.find(brand_string) != std::string::npos);
} else {
EXPECT_TRUE(url1_query.find(brand_string) == std::string::npos);
}
#else
// Chromium builds never add the brand to the parameter, even for google
// queries.
EXPECT_TRUE(url1_query.find(brand_string) == std::string::npos);
#endif
RunUntilIdle();
}
// 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.
void TestHandleManifestResults() {
ExtensionDownloaderTestHelper helper;
ServiceForManifestTests service(prefs_.get(), helper.url_loader_factory());
GURL update_url("http://www.google.com/manifest");
ExtensionList tmp;
service.CreateTestExtensions(1, 1, &tmp, &update_url.spec(),
ManifestLocation::kInternal);
service.set_extensions(tmp, ExtensionList());
ExtensionUpdater updater(&service, service.extension_prefs(),
service.pref_service(), service.profile(),
kUpdateFrequencySecs, nullptr,
service.GetDownloaderFactory());
updater.Start();
updater.EnsureDownloaderCreated();
std::unique_ptr<ManifestFetchData> fetch_data(
CreateManifestFetchData(update_url));
const Extension* extension = tmp[0].get();
AddExtensionToFetchDataForTesting(fetch_data.get(), extension->id(),
extension->VersionString(), update_url);
auto results = std::make_unique<UpdateManifestResults>();
constexpr int kDaystartElapsedSeconds = 750;
results->daystart_elapsed_seconds = kDaystartElapsedSeconds;
updater.downloader_->HandleManifestResults(std::move(fetch_data),
std::move(results),
/*error=*/std::nullopt);
Time last_ping_day =
service.extension_prefs()->LastPingDay(extension->id());
EXPECT_FALSE(last_ping_day.is_null());
int64_t seconds_diff = (Time::Now() - last_ping_day).InSeconds();
EXPECT_LT(seconds_diff - kDaystartElapsedSeconds, 5);
}
void TestManifestAddExtension(DownloadFetchPriority data_priority,
DownloadFetchPriority extension_priority,
DownloadFetchPriority expected_priority) {
const std::string id(32, 'a');
const std::string version = "1.0";
std::unique_ptr<ManifestFetchData> fetch_data(
CreateManifestFetchData(GURL("http://localhost/foo"), data_priority));
ASSERT_TRUE(fetch_data->AddExtension(
id, version, &ExtensionDownloaderTestHelper::kNeverPingedData,
std::string(), std::string(), ManifestLocation::kInternal,
extension_priority));
ASSERT_EQ(expected_priority, fetch_data->fetch_priority());
}
void TestManifestMerge(DownloadFetchPriority data_priority,
DownloadFetchPriority other_priority,
DownloadFetchPriority expected_priority) {
std::unique_ptr<ManifestFetchData> fetch_data(
CreateManifestFetchData(GURL("http://localhost/foo"), data_priority));
std::unique_ptr<ManifestFetchData> fetch_other(
CreateManifestFetchData(GURL("http://localhost/foo"), other_priority));
fetch_data->Merge(std::move(fetch_other));
ASSERT_EQ(expected_priority, fetch_data->fetch_priority());
}
protected:
std::unique_ptr<TestExtensionPrefs> prefs_;
content::BrowserTaskEnvironment task_environment_;
ManifestFetchData* CreateManifestFetchData(
const GURL& update_url,
DownloadFetchPriority fetch_priority) {
return new ManifestFetchData(update_url, 0, "",
UpdateQueryParams::Get(UpdateQueryParams::CRX),
ManifestFetchData::PING, fetch_priority);
}
ManifestFetchData* CreateManifestFetchData(const GURL& update_url) {
return CreateManifestFetchData(update_url,
DownloadFetchPriority::kBackground);
}
private:
content::InProcessUtilityThreadHelper in_process_utility_thread_helper_;
ScopedTestingLocalState testing_local_state_;
#if BUILDFLAG(IS_CHROMEOS_ASH)
ash::ScopedCrosSettingsTestHelper cros_settings_test_helper_;
user_manager::ScopedUserManager test_user_manager_{
ash::ChromeUserManagerImpl::CreateChromeUserManager()};
#endif
};
// Because we test some private methods of ExtensionUpdater, it's easier for the
// actual test code to live in ExtensionUpdaterTest methods instead of TEST_F
// subclasses where friendship with ExtensionUpdater is not inherited.
TEST_F(ExtensionUpdaterTest, TestExtensionUpdateCheckRequests) {
TestExtensionUpdateCheckRequests(false);
}
TEST_F(ExtensionUpdaterTest, TestExtensionUpdateCheckRequestsPending) {
TestExtensionUpdateCheckRequests(true);
}
TEST_F(ExtensionUpdaterTest, TestUpdateUrlData) {
TestUpdateUrlDataEmpty();
TestUpdateUrlDataSimple();
TestUpdateUrlDataCompound();
std::string gallery_url_spec = extension_urls::GetWebstoreUpdateUrl().spec();
TestUpdateUrlDataFromUrl(gallery_url_spec, DownloadFetchPriority::kBackground,
1, true);
TestUpdateUrlDataFromUrl(gallery_url_spec, DownloadFetchPriority::kForeground,
1, true);
TestUpdateUrlDataFromUrl(gallery_url_spec, DownloadFetchPriority::kBackground,
2, true);
TestUpdateUrlDataFromUrl(gallery_url_spec, DownloadFetchPriority::kForeground,
4, true);
TestUpdateUrlDataFromUrl("http://example.com/update",
DownloadFetchPriority::kForeground, 4, false);
}
TEST_F(ExtensionUpdaterTest, TestInstallSource) {
TestInstallSource();
}
TEST_F(ExtensionUpdaterTest, TestInstallLocation) {
TestInstallLocation();
}
TEST_F(ExtensionUpdaterTest, TestDetermineUpdates) {
TestDetermineUpdates();
}
TEST_F(ExtensionUpdaterTest, TestDetermineUpdatesPending) {
TestDetermineUpdatesPending();
}
TEST_F(ExtensionUpdaterTest, TestDetermineUpdatesDuplicates) {
TestDetermineUpdatesDuplicates();
}
TEST_F(ExtensionUpdaterTest, TestDetermineUpdatesError) {
TestDetermineUpdatesError();
}
TEST_F(ExtensionUpdaterTest, TestMultipleManifestDownloading) {
TestMultipleManifestDownloading();
}
TEST_F(ExtensionUpdaterTest, TestSingleExtensionDownloading) {
TestSingleExtensionDownloading(false, false, false);
}
TEST_F(ExtensionUpdaterTest, TestSingleExtensionDownloadingPending) {
TestSingleExtensionDownloading(true, false, false);
}
TEST_F(ExtensionUpdaterTest, TestSingleExtensionDownloadingWithRetry) {
TestSingleExtensionDownloading(false, true, false);
}
TEST_F(ExtensionUpdaterTest, TestSingleExtensionDownloadingPendingWithRetry) {
TestSingleExtensionDownloading(true, true, false);
}
TEST_F(ExtensionUpdaterTest, TestSingleExtensionDownloadingFailure) {
TestSingleExtensionDownloading(false, false, true);
}
TEST_F(ExtensionUpdaterTest, TestSingleExtensionDownloadingFailureWithRetry) {
TestSingleExtensionDownloading(false, true, true);
}
TEST_F(ExtensionUpdaterTest, TestSingleExtensionDownloadingFailurePending) {
TestSingleExtensionDownloading(true, false, true);
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
TEST_F(ExtensionUpdaterTest, TestCacheCorruptionCrxDownload) {
TestCacheCorruption();
}
#endif
TEST_F(ExtensionUpdaterTest, ProtectedDownloadCookieAuth) {
TestProtectedDownload(
"https://chrome.google.com/webstore/download",
false, false, // No OAuth2 support
0, 0);
}
TEST_F(ExtensionUpdaterTest, ProtectedDownloadCookieFailure) {
TestProtectedDownload(
"https://chrome.google.com/webstore/download",
false, false, // No OAuth2 support
0, -1); // max_authuser=-1 simulates no valid authuser value.
}
TEST_F(ExtensionUpdaterTest, ProtectedDownloadWithNonDefaultAuthUser1) {
TestProtectedDownload("https://google.com", false, false, 1, 1);
}
TEST_F(ExtensionUpdaterTest, ProtectedDownloadWithNonDefaultAuthUser2) {
TestProtectedDownload("https://google.com", false, false, 2, 2);
}
TEST_F(ExtensionUpdaterTest, ProtectedDownloadAuthUserExhaustionFailure) {
TestProtectedDownload("https://google.com", false, false, 2, 5);
}
TEST_F(ExtensionUpdaterTest, ProtectedDownloadWithOAuth2Token) {
TestProtectedDownload(
"https://google.com",
true, true,
0, -1);
}
TEST_F(ExtensionUpdaterTest, ProtectedDownloadWithOAuth2Failure) {
TestProtectedDownload(
"https://google.com",
true, false,
0, -1);
}
TEST_F(ExtensionUpdaterTest, ProtectedDownloadNoOAuth2WithNonGoogleDomain) {
TestProtectedDownload(
"https://not-google.com",
true, true,
0, -1);
}
TEST_F(ExtensionUpdaterTest, ProtectedDownloadFailWithoutHTTPS) {
TestProtectedDownload(
"http://google.com",
true, true,
0, 0);
}
TEST_F(ExtensionUpdaterTest, TestMultipleExtensionDownloadingUpdatesFail) {
TestMultipleExtensionDownloading(false);
}
TEST_F(ExtensionUpdaterTest, TestMultipleExtensionDownloadingUpdatesSucceed) {
TestMultipleExtensionDownloading(true);
}
TEST_F(ExtensionUpdaterTest, TestManifestRetryDownloading) {
TestManifestRetryDownloading();
}
TEST_F(ExtensionUpdaterTest, DISABLED_TestGalleryRequestsWithOrganicBrand) {
TestGalleryRequestsWithBrand(true);
}
TEST_F(ExtensionUpdaterTest, DISABLED_TestGalleryRequestsWithNonOrganicBrand) {
TestGalleryRequestsWithBrand(false);
}
TEST_F(ExtensionUpdaterTest, TestHandleManifestResults) {
TestHandleManifestResults();
}
TEST_F(ExtensionUpdaterTest, TestNonAutoUpdateableLocations) {
ExtensionDownloaderTestHelper helper;
ServiceForManifestTests service(prefs_.get(), helper.url_loader_factory());
ExtensionUpdater updater(&service, service.extension_prefs(),
service.pref_service(), service.profile(),
kUpdateFrequencySecs, nullptr,
service.GetDownloaderFactory());
MockExtensionDownloaderDelegate delegate;
service.OverrideDownloaderDelegate(&delegate);
// Non-internal non-external extensions should be rejected.
ExtensionList extensions;
service.CreateTestExtensions(1, 1, &extensions, nullptr,
ManifestLocation::kInvalidLocation);
ASSERT_EQ(1u, extensions.size());
// The test will fail with unexpected calls if the delegate's methods are
// invoked for the extension.
service.set_extensions(extensions, ExtensionList());
updater.Start();
updater.CheckNow(ExtensionUpdater::CheckParams());
}
TEST_F(ExtensionUpdaterTest, TestUpdatingDisabledExtensions) {
ExtensionDownloaderTestHelper helper;
ServiceForManifestTests service(prefs_.get(), helper.url_loader_factory());
ExtensionUpdater updater(&service, service.extension_prefs(),
service.pref_service(), service.profile(),
kUpdateFrequencySecs, nullptr,
service.GetDownloaderFactory());
NiceMock<MockUpdateService> update_service;
OverrideUpdateService(&updater, &update_service);
// Non-internal non-external extensions should be rejected.
ExtensionList enabled_extensions;
ExtensionList disabled_extensions;
service.CreateTestExtensions(1, 1, &enabled_extensions, nullptr,
ManifestLocation::kInternal);
service.CreateTestExtensions(2, 1, &disabled_extensions, nullptr,
ManifestLocation::kInternal);
ASSERT_EQ(1u, enabled_extensions.size());
ASSERT_EQ(1u, disabled_extensions.size());
// We expect that both enabled and disabled extensions are auto-updated.
EXPECT_CALL(update_service,
StartUpdateCheck(
::testing::Field(&ExtensionUpdateCheckParams::update_info,
::testing::SizeIs(2)),
_, _));
service.set_extensions(enabled_extensions, disabled_extensions);
updater.Start();
updater.CheckNow(ExtensionUpdater::CheckParams());
}
// crbug.com/1098540: Tests that removely disabled extensions that are part of
// the blocklisted extensions are still receive updates.
TEST_F(ExtensionUpdaterTest, TestUpdatingRemotelyDisabledExtensions) {
ExtensionDownloaderTestHelper helper;
ServiceForManifestTests service(prefs_.get(), helper.url_loader_factory());
ExtensionUpdater updater(&service, service.extension_prefs(),
service.pref_service(), service.profile(),
kUpdateFrequencySecs, nullptr,
service.GetDownloaderFactory());
NiceMock<MockUpdateService> update_service;
OverrideUpdateService(&updater, &update_service);
ExtensionList enabled_extensions;
ExtensionList blocklisted_extensions;
service.CreateTestExtensions(1, 1, &enabled_extensions, nullptr,
ManifestLocation::kInternal);
service.CreateTestExtensions(2, 1, &blocklisted_extensions, nullptr,
ManifestLocation::kInternal);
service.CreateTestExtensions(3, 1, &blocklisted_extensions, nullptr,
ManifestLocation::kInternal);
ASSERT_EQ(1u, enabled_extensions.size());
ASSERT_EQ(2u, blocklisted_extensions.size());
const std::string& remotely_blocklisted_id = blocklisted_extensions[0]->id();
blocklist_prefs::SetSafeBrowsingExtensionBlocklistState(
remotely_blocklisted_id, BitMapBlocklistState::BLOCKLISTED_MALWARE,
service.extension_prefs());
blocklist_prefs::AddOmahaBlocklistState(
remotely_blocklisted_id, BitMapBlocklistState::BLOCKLISTED_MALWARE,
service.extension_prefs());
// We expect that both enabled and remotely blocklisted extensions are
// auto-updated.
EXPECT_CALL(update_service,
StartUpdateCheck(
::testing::Field(&ExtensionUpdateCheckParams::update_info,
::testing::SizeIs(2)),
_, _));
service.set_extensions(enabled_extensions, ExtensionList(),
blocklisted_extensions);
updater.Start();
updater.CheckNow(ExtensionUpdater::CheckParams());
}
TEST_F(ExtensionUpdaterTest, TestManifestFetchesBuilderAddExtension) {
auto helper = std::make_unique<ExtensionDownloaderTestHelper>();
EXPECT_EQ(0u, ManifestFetchersCount(&helper->downloader()));
// First, verify that adding valid extensions does invoke the callbacks on
// the delegate.
std::string id = crx_file::id_util::GenerateId("foo");
EXPECT_CALL(helper->delegate(), GetPingDataForExtension(id, _))
.WillOnce(Return(false));
EXPECT_TRUE(helper->downloader().AddPendingExtension(
CreateDownloaderTask(id, GURL("http://example.com/update"))));
helper->downloader().StartAllPending(nullptr);
Mock::VerifyAndClearExpectations(&helper->delegate());
EXPECT_EQ(1u, ManifestFetchersCount(&helper->downloader()));
// Extensions with invalid update URLs should be rejected.
id = crx_file::id_util::GenerateId("foo2");
EXPECT_FALSE(helper->downloader().AddPendingExtension(
CreateDownloaderTask(id, GURL("http:google.com:foo"))));
helper->downloader().StartAllPending(nullptr);
EXPECT_EQ(1u, ManifestFetchersCount(&helper->downloader()));
// Extensions with empty IDs should be rejected.
EXPECT_FALSE(helper->downloader().AddPendingExtension(
CreateDownloaderTask(std::string(), GURL())));
helper->downloader().StartAllPending(nullptr);
EXPECT_EQ(1u, ManifestFetchersCount(&helper->downloader()));
// TODO(akalin): Test that extensions with empty update URLs
// converted from user scripts are rejected.
// Reset the ExtensionDownloader so that it drops the current fetcher.
helper = std::make_unique<ExtensionDownloaderTestHelper>();
EXPECT_EQ(0u, ManifestFetchersCount(&helper->downloader()));
// Extensions with empty update URLs should have a default one
// filled in.
id = crx_file::id_util::GenerateId("foo3");
EXPECT_CALL(helper->delegate(), GetPingDataForExtension(id, _))
.WillOnce(Return(false));
EXPECT_TRUE(helper->downloader().AddPendingExtension(
CreateDownloaderTask(id, GURL())));
helper->downloader().StartAllPending(nullptr);
EXPECT_EQ(1u, ManifestFetchersCount(&helper->downloader()));
RunUntilIdle();
auto* request = &(*helper->test_url_loader_factory().pending_requests())[0];
ASSERT_TRUE(request);
EXPECT_FALSE(request->request.url.is_empty());
}
TEST_F(ExtensionUpdaterTest, TestAddPendingExtensionWithVersion) {
constexpr char kVersion[] = "1.2.3.4";
auto helper = std::make_unique<ExtensionDownloaderTestHelper>();
EXPECT_EQ(0u, ManifestFetchersCount(&helper->downloader()));
{
// First, verify that adding valid extensions does invoke the callbacks on
// the delegate.
ExtensionId id = crx_file::id_util::GenerateId("foo");
EXPECT_CALL(helper->delegate(), GetPingDataForExtension(id, _))
.WillOnce(Return(false));
ExtensionDownloaderTask task =
CreateDownloaderTask(id, GURL("http://example.com/update"));
task.version = base::Version(kVersion);
EXPECT_TRUE(helper->downloader().AddPendingExtension(std::move(task)));
helper->downloader().StartAllPending(nullptr);
Mock::VerifyAndClearExpectations(&helper->delegate());
EXPECT_EQ(1u, ManifestFetchersCount(&helper->downloader()));
}
{
// Extensions with invalid update URLs should be rejected.
ExtensionId id = crx_file::id_util::GenerateId("foo2");
ExtensionDownloaderTask task =
CreateDownloaderTask(id, GURL("http:google.com:foo"));
task.version = base::Version(kVersion);
EXPECT_FALSE(helper->downloader().AddPendingExtension(std::move(task)));
helper->downloader().StartAllPending(nullptr);
EXPECT_EQ(1u, ManifestFetchersCount(&helper->downloader()));
}
{
// Extensions with empty IDs should be rejected.
ExtensionDownloaderTask task = CreateDownloaderTask(std::string(), GURL());
task.version = base::Version(kVersion);
EXPECT_FALSE(helper->downloader().AddPendingExtension(std::move(task)));
helper->downloader().StartAllPending(nullptr);
EXPECT_EQ(1u, ManifestFetchersCount(&helper->downloader()));
}
// Reset the ExtensionDownloader so that it drops the current fetcher.
helper = std::make_unique<ExtensionDownloaderTestHelper>();
EXPECT_EQ(0u, ManifestFetchersCount(&helper->downloader()));
{
// Extensions with empty update URLs should have a default one
// filled in.
ExtensionId id = crx_file::id_util::GenerateId("foo3");
EXPECT_CALL(helper->delegate(), GetPingDataForExtension(id, _))
.WillOnce(Return(false));
ExtensionDownloaderTask task = CreateDownloaderTask(id, GURL());
task.version = base::Version(kVersion);
EXPECT_TRUE(helper->downloader().AddPendingExtension(std::move(task)));
helper->downloader().StartAllPending(nullptr);
EXPECT_EQ(1u, ManifestFetchersCount(&helper->downloader()));
}
RunUntilIdle();
auto* request = &(*helper->test_url_loader_factory().pending_requests())[0];
ASSERT_TRUE(request);
EXPECT_FALSE(request->request.url.is_empty());
EXPECT_THAT(request->request.url.possibly_invalid_spec(),
testing::HasSubstr(kVersion));
}
TEST_F(ExtensionUpdaterTest, TestStartUpdateCheckMemory) {
ExtensionDownloaderTestHelper helper;
StartUpdateCheck(&helper.downloader(),
CreateManifestFetchData(GURL("http://localhost/foo")));
// This should delete the newly-created ManifestFetchData.
StartUpdateCheck(&helper.downloader(),
CreateManifestFetchData(GURL("http://localhost/foo")));
// This should add into |manifests_pending_|.
StartUpdateCheck(&helper.downloader(),
CreateManifestFetchData(GURL("http://www.google.com")));
// The dtor of |downloader| should delete the pending fetchers.
}
TEST_F(ExtensionUpdaterTest, TestCheckSoon) {
ExtensionDownloaderTestHelper helper;
ServiceForManifestTests service(prefs_.get(), helper.url_loader_factory());
ExtensionUpdater updater(&service, service.extension_prefs(),
service.pref_service(), service.profile(),
kUpdateFrequencySecs, nullptr,
service.GetDownloaderFactory());
EXPECT_FALSE(updater.WillCheckSoon());
updater.Start();
EXPECT_TRUE(updater.WillCheckSoon());
RunUntilIdle();
EXPECT_FALSE(updater.WillCheckSoon());
updater.CheckSoon();
EXPECT_TRUE(updater.WillCheckSoon());
updater.CheckSoon();
EXPECT_TRUE(updater.WillCheckSoon());
RunUntilIdle();
EXPECT_FALSE(updater.WillCheckSoon());
updater.CheckSoon();
EXPECT_TRUE(updater.WillCheckSoon());
updater.Stop();
EXPECT_FALSE(updater.WillCheckSoon());
}
TEST_F(ExtensionUpdaterTest, TestUninstallWhileUpdateCheck) {
ExtensionDownloaderTestHelper helper;
ServiceForManifestTests service(prefs_.get(), helper.url_loader_factory());
ExtensionList tmp;
service.CreateTestExtensions(1, 1, &tmp, nullptr,
ManifestLocation::kInternal);
service.set_extensions(tmp, ExtensionList());
ASSERT_EQ(1u, tmp.size());
ExtensionId id = tmp.front()->id();
ExtensionRegistry* registry = ExtensionRegistry::Get(service.profile());
ASSERT_TRUE(registry->enabled_extensions().GetByID(id));
ExtensionUpdater updater(&service, service.extension_prefs(),
service.pref_service(), service.profile(),
kUpdateFrequencySecs, nullptr,
service.GetDownloaderFactory());
ExtensionUpdater::CheckParams params;
params.ids = {id};
updater.Start();
updater.CheckNow(std::move(params));
service.set_extensions(ExtensionList(), ExtensionList());
ASSERT_FALSE(registry->enabled_extensions().GetByID(id));
// RunUntilIdle is needed to make sure that the UpdateService instance that
// runs the extension update process has a chance to exit gracefully; without
// it, the test would crash.
RunUntilIdle();
}
TEST_F(ExtensionUpdaterTest, TestManifestFetchDataAddExtension) {
TestManifestAddExtension(DownloadFetchPriority::kBackground,
DownloadFetchPriority::kBackground,
DownloadFetchPriority::kBackground);
TestManifestAddExtension(DownloadFetchPriority::kBackground,
DownloadFetchPriority::kForeground,
DownloadFetchPriority::kForeground);
TestManifestAddExtension(DownloadFetchPriority::kForeground,
DownloadFetchPriority::kBackground,
DownloadFetchPriority::kForeground);
TestManifestAddExtension(DownloadFetchPriority::kForeground,
DownloadFetchPriority::kForeground,
DownloadFetchPriority::kForeground);
}
TEST_F(ExtensionUpdaterTest, TestManifestFetchDataMerge) {
TestManifestMerge(DownloadFetchPriority::kBackground,
DownloadFetchPriority::kBackground,
DownloadFetchPriority::kBackground);
TestManifestMerge(DownloadFetchPriority::kBackground,
DownloadFetchPriority::kForeground,
DownloadFetchPriority::kForeground);
TestManifestMerge(DownloadFetchPriority::kForeground,
DownloadFetchPriority::kBackground,
DownloadFetchPriority::kForeground);
TestManifestMerge(DownloadFetchPriority::kForeground,
DownloadFetchPriority::kForeground,
DownloadFetchPriority::kForeground);
}
TEST_F(ExtensionUpdaterTest, TestManifestFetchCredentials) {
TestManifestCredentialsWebstore();
TestManifestCredentialsNonWebstore();
}
TEST_F(ExtensionUpdaterTest, TestManifestFetchPriority) {
TestManifestFetchPriority(DownloadFetchPriority::kBackground);
TestManifestFetchPriority(DownloadFetchPriority::kForeground);
}
TEST_F(ExtensionUpdaterTest, TestExtensionPriority) {
TestSingleExtensionDownloadingPriority(DownloadFetchPriority::kBackground);
TestSingleExtensionDownloadingPriority(DownloadFetchPriority::kForeground);
}
class CanUseUpdateServiceTest : public ExtensionUpdaterTest {
public:
CanUseUpdateServiceTest() = default;
~CanUseUpdateServiceTest() override = default;
void SetUp() override {
ExtensionUpdaterTest::SetUp();
service_ = std::make_unique<ServiceForDownloadTests>(
prefs_.get(), downloader_test_helper_.url_loader_factory());
updater_ = std::make_unique<ExtensionUpdater>(
service_.get(), service_->extension_prefs(), service_->pref_service(),
service_->profile(), kUpdateFrequencySecs, nullptr,
service_->GetDownloaderFactory());
store_extension_ =
ExtensionBuilder("store_extension")
.SetManifestKey(
"update_url",
extension_urls::GetDefaultWebstoreUpdateUrl().spec())
.Build();
offstore_extension_ =
ExtensionBuilder("offstore_extension")
.SetManifestKey("update_url", "http://localhost/test/updates.xml")
.Build();
emptyurl_extension_ = ExtensionBuilder("emptyurl_extension").Build();
userscript_extension_ =
ExtensionBuilder("userscript_extension")
.SetManifestKey("converted_from_user_script", true)
.Build();
ASSERT_TRUE(store_extension_.get());
ASSERT_TRUE(ExtensionRegistry::Get(service_->profile())
->AddEnabled(store_extension_));
ASSERT_TRUE(offstore_extension_.get());
ASSERT_TRUE(ExtensionRegistry::Get(service_->profile())
->AddEnabled(offstore_extension_));
ASSERT_TRUE(emptyurl_extension_.get());
ASSERT_TRUE(ExtensionRegistry::Get(service_->profile())
->AddEnabled(emptyurl_extension_));
ASSERT_TRUE(userscript_extension_.get());
ASSERT_TRUE(ExtensionRegistry::Get(service_->profile())
->AddEnabled(userscript_extension_));
}
protected:
ExtensionUpdater* updater() { return updater_.get(); }
ExtensionUpdater::ScopedSkipScheduledCheckForTest skip_scheduled_checks_;
ExtensionDownloaderTestHelper downloader_test_helper_;
std::unique_ptr<ServiceForDownloadTests> service_;
std::unique_ptr<ExtensionUpdater> updater_;
scoped_refptr<const Extension> store_extension_;
scoped_refptr<const Extension> offstore_extension_;
scoped_refptr<const Extension> emptyurl_extension_;
scoped_refptr<const Extension> userscript_extension_;
};
class UpdateServiceCanUpdateFeatureEnabledNonDefaultUpdateUrl
: public CanUseUpdateServiceTest {
public:
void SetUp() override {
CanUseUpdateServiceTest::SetUp();
// Change the webstore update url.
auto* command_line = base::CommandLine::ForCurrentProcess();
// Note: |offstore_extension_|'s update url is the same.
command_line->AppendSwitchASCII("apps-gallery-update-url",
"http://localhost/test2/updates.xml");
ExtensionsClient::Get()->InitializeWebStoreUrls(
base::CommandLine::ForCurrentProcess());
}
};
TEST_F(CanUseUpdateServiceTest, TestDefaults) {
// Update service can only update webstore extensions when enabled.
EXPECT_TRUE(CanUseUpdateService(updater(), store_extension_->id()));
// ... and extensions with empty update URL.
EXPECT_TRUE(CanUseUpdateService(updater(), emptyurl_extension_->id()));
// It can't update off-store extensions.
EXPECT_FALSE(CanUseUpdateService(updater(), offstore_extension_->id()));
// ... or extensions with empty update URL converted from user script.
EXPECT_FALSE(CanUseUpdateService(updater(), userscript_extension_->id()));
// ... or extensions that don't exist.
EXPECT_FALSE(CanUseUpdateService(updater(), std::string(32, 'a')));
// ... or extensions with empty ID (is it possible?).
EXPECT_FALSE(CanUseUpdateService(updater(), ""));
}
TEST_F(UpdateServiceCanUpdateFeatureEnabledNonDefaultUpdateUrl,
CanUseUpdateServiceFeatureEnabledNonDefaultUpdateUrl) {
// Update service can update extensions when the default webstore update url
// is changed.
EXPECT_FALSE(CanUseUpdateService(updater(), store_extension_->id()));
EXPECT_TRUE(CanUseUpdateService(updater(), emptyurl_extension_->id()));
EXPECT_FALSE(CanUseUpdateService(updater(), offstore_extension_->id()));
EXPECT_FALSE(CanUseUpdateService(updater(), userscript_extension_->id()));
EXPECT_FALSE(CanUseUpdateService(updater(), std::string(32, 'a')));
EXPECT_FALSE(CanUseUpdateService(updater(), ""));
}
// 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
} // namespace extensions