blob: 8b3b42e0d2906591aeb6f6c7c89859f0a45018be [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "extensions/browser/updater/extension_downloader.h"
#include <stddef.h>
#include <utility>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/metrics/histogram.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/task/post_task.h"
#include "base/time/time.h"
#include "base/version.h"
#include "components/crx_file/crx_verifier.h"
#include "components/signin/public/identity_manager/access_token_info.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/signin/public/identity_manager/primary_account_access_token_fetcher.h"
#include "components/signin/public/identity_manager/scope_set.h"
#include "components/update_client/update_query_params.h"
#include "content/public/browser/file_url_loader.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/shared_cors_origin_access_list.h"
#include "extensions/browser/extension_file_task_runner.h"
#include "extensions/browser/extensions_browser_client.h"
#include "extensions/browser/notification_types.h"
#include "extensions/browser/updater/extension_cache.h"
#include "extensions/browser/updater/extension_downloader_test_delegate.h"
#include "extensions/browser/updater/request_queue_impl.h"
#include "extensions/common/extension_updater_uma.h"
#include "extensions/common/extension_urls.h"
#include "extensions/common/verifier_formats.h"
#include "net/base/backoff_entry.h"
#include "net/base/load_flags.h"
#include "net/base/net_errors.h"
#include "net/http/http_status_code.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/simple_url_loader.h"
using base::Time;
using base::TimeDelta;
using update_client::UpdateQueryParams;
namespace extensions {
namespace {
const net::BackoffEntry::Policy kDefaultBackoffPolicy = {
// Number of initial errors (in sequence) to ignore before applying
// exponential back-off rules.
0,
// Initial delay for exponential back-off in ms.
2000,
// Factor by which the waiting time will be multiplied.
2,
// Fuzzing percentage. ex: 10% will spread requests randomly
// between 90%-100% of the calculated time.
0.1,
// Maximum amount of time we are willing to delay our request in ms.
600000, // Ten minutes.
// 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";
const int kMaxAuthUserValue = 10;
const int kMaxOAuth2Attempts = 3;
const char kNotFromWebstoreInstallSource[] = "notfromwebstore";
const char kDefaultInstallSource[] = "";
const char kReinstallInstallSource[] = "reinstall";
const char kGoogleDotCom[] = "google.com";
const char kTokenServiceConsumerId[] = "extension_downloader";
const char kWebstoreOAuth2Scope[] =
"https://www.googleapis.com/auth/chromewebstore.readonly";
ExtensionDownloaderTestDelegate* g_test_delegate = nullptr;
#define RETRY_HISTOGRAM(name, retry_count, url) \
if ((url).DomainIs(kGoogleDotCom)) { \
UMA_HISTOGRAM_CUSTOM_COUNTS("Extensions." name "RetryCountGoogleUrl", \
retry_count, \
1, \
kMaxRetries, \
kMaxRetries + 1); \
} else { \
UMA_HISTOGRAM_CUSTOM_COUNTS("Extensions." name "RetryCountOtherUrl", \
retry_count, \
1, \
kMaxRetries, \
kMaxRetries + 1); \
}
bool ShouldRetryRequest(const network::SimpleURLLoader* loader) {
DCHECK(loader);
// Since HTTP errors are now presented as ERR_HTTP_RESPONSE_CODE_FAILURE
// by default, this will let both network and HTTP errors through.
if (loader->NetError() == net::OK)
return false;
// If it failed without receiving response headers, retry.
if (!loader->ResponseInfo() || !loader->ResponseInfo()->headers)
return true;
// If a response code was received, only retry on 5xx codes (server errors).
int response_code = loader->ResponseInfo()->headers->response_code();
return response_code >= 500 && response_code < 600;
}
bool ShouldRetryRequestForExtensionNotFoundInCache(const int net_error_code) {
return net_error_code == net::ERR_INTERNET_DISCONNECTED;
}
// This parses and updates a URL query such that the value of the |authuser|
// query parameter is incremented by 1. If parameter was not present in the URL,
// it will be added with a value of 1. All other query keys and values are
// preserved as-is. Returns |false| if the user index exceeds a hard-coded
// maximum.
bool IncrementAuthUserIndex(GURL* url) {
int user_index = 0;
std::string old_query = url->query();
std::vector<std::string> new_query_parts;
url::Component query(0, old_query.length());
url::Component key, value;
while (url::ExtractQueryKeyValue(old_query.c_str(), &query, &key, &value)) {
std::string key_string = old_query.substr(key.begin, key.len);
std::string value_string = old_query.substr(value.begin, value.len);
if (key_string == kAuthUserQueryKey) {
base::StringToInt(value_string, &user_index);
} else {
new_query_parts.push_back(base::StringPrintf(
"%s=%s", key_string.c_str(), value_string.c_str()));
}
}
if (user_index >= kMaxAuthUserValue)
return false;
new_query_parts.push_back(
base::StringPrintf("%s=%d", kAuthUserQueryKey, user_index + 1));
std::string new_query_string = base::JoinString(new_query_parts, "&");
url::Component new_query(0, new_query_string.size());
url::Replacements<char> replacements;
replacements.SetQuery(new_query_string.c_str(), new_query);
*url = url->ReplaceComponents(replacements);
return true;
}
} // namespace
const char ExtensionDownloader::kUpdateInteractivityHeader[] =
"X-Goog-Update-Interactivity";
const char ExtensionDownloader::kUpdateAppIdHeader[] = "X-Goog-Update-AppId";
const char ExtensionDownloader::kUpdateUpdaterHeader[] =
"X-Goog-Update-Updater";
const char ExtensionDownloader::kUpdateInteractivityForeground[] = "fg";
const char ExtensionDownloader::kUpdateInteractivityBackground[] = "bg";
UpdateDetails::UpdateDetails(const std::string& id,
const base::Version& version)
: id(id), version(version) {}
UpdateDetails::~UpdateDetails() = default;
ExtensionDownloader::ExtensionFetch::ExtensionFetch()
: credentials(CREDENTIALS_NONE) {}
ExtensionDownloader::ExtensionFetch::ExtensionFetch(
const std::string& id,
const GURL& url,
const std::string& package_hash,
const std::string& version,
const std::set<int>& request_ids,
const ManifestFetchData::FetchPriority fetch_priority)
: id(id),
url(url),
package_hash(package_hash),
version(version),
request_ids(request_ids),
fetch_priority(fetch_priority),
credentials(CREDENTIALS_NONE),
oauth2_attempt_count(0) {}
ExtensionDownloader::ExtensionFetch::~ExtensionFetch() = default;
ExtensionDownloader::FetchDataGroupKey::FetchDataGroupKey() = default;
ExtensionDownloader::FetchDataGroupKey::FetchDataGroupKey(
const FetchDataGroupKey& other) = default;
ExtensionDownloader::FetchDataGroupKey::FetchDataGroupKey(
const int request_id,
const GURL& update_url,
const bool is_force_installed)
: request_id(request_id),
update_url(update_url),
is_force_installed(is_force_installed) {}
ExtensionDownloader::FetchDataGroupKey::~FetchDataGroupKey() = default;
bool ExtensionDownloader::FetchDataGroupKey::operator<(
const FetchDataGroupKey& other) const {
return std::tie(request_id, update_url, is_force_installed) <
std::tie(other.request_id, other.update_url, other.is_force_installed);
}
ExtensionDownloader::ExtraParams::ExtraParams() : is_corrupt_reinstall(false) {}
ExtensionDownloader::ExtensionDownloader(
ExtensionDownloaderDelegate* delegate,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
const crx_file::VerifierFormat crx_format_requirement,
const base::FilePath& profile_path)
: delegate_(delegate),
url_loader_factory_(std::move(url_loader_factory)),
profile_path_for_url_loader_factory_(profile_path),
manifests_queue_(
&kDefaultBackoffPolicy,
base::BindRepeating(&ExtensionDownloader::CreateManifestLoader,
base::Unretained(this))),
extensions_queue_(
&kDefaultBackoffPolicy,
base::BindRepeating(&ExtensionDownloader::CreateExtensionLoader,
base::Unretained(this))),
extension_cache_(nullptr),
identity_manager_(nullptr),
crx_format_requirement_(crx_format_requirement) {
DCHECK(delegate_);
DCHECK(url_loader_factory_);
}
ExtensionDownloader::~ExtensionDownloader() = default;
bool ExtensionDownloader::AddPendingExtension(
const std::string& id,
const GURL& update_url,
mojom::ManifestLocation install_location,
bool is_corrupt_reinstall,
int request_id,
ManifestFetchData::FetchPriority fetch_priority) {
// Use a zero version to ensure that a pending extension will always
// be updated, and thus installed (assuming all extensions have
// non-zero versions).
return AddPendingExtensionWithVersion(
id, update_url, install_location, is_corrupt_reinstall, request_id,
fetch_priority, base::Version("0.0.0.0"), Manifest::TYPE_UNKNOWN,
std::string());
}
bool ExtensionDownloader::AddPendingExtensionWithVersion(
const std::string& id,
const GURL& update_url,
mojom::ManifestLocation install_location,
bool is_corrupt_reinstall,
int request_id,
ManifestFetchData::FetchPriority fetch_priority,
base::Version version,
Manifest::Type type,
const std::string& update_url_data) {
DCHECK(version.IsValid());
ExtraParams extra;
if (is_corrupt_reinstall)
extra.is_corrupt_reinstall = true;
if (!update_url_data.empty())
extra.update_url_data = update_url_data;
delegate_->OnExtensionDownloadStageChanged(
id, ExtensionDownloaderDelegate::Stage::PENDING);
return AddExtensionData(id, version, type, install_location, update_url,
extra, request_id, fetch_priority);
}
void ExtensionDownloader::StartAllPending(ExtensionCache* cache) {
if (cache) {
extension_cache_ = cache;
extension_cache_->Start(
base::BindOnce(&ExtensionDownloader::DoStartAllPending,
weak_ptr_factory_.GetWeakPtr()));
} else {
DoStartAllPending();
}
}
void ExtensionDownloader::DoStartAllPending() {
ReportStats();
url_stats_ = URLStats();
for (auto it = fetches_preparing_.begin(); it != fetches_preparing_.end();
++it) {
std::vector<std::unique_ptr<ManifestFetchData>>& list = it->second;
for (size_t i = 0; i < list.size(); ++i)
StartUpdateCheck(std::move(list[i]));
}
fetches_preparing_.clear();
}
void ExtensionDownloader::SetIdentityManager(
signin::IdentityManager* identity_manager) {
identity_manager_ = identity_manager;
}
// static
void ExtensionDownloader::set_test_delegate(
ExtensionDownloaderTestDelegate* delegate) {
g_test_delegate = delegate;
}
void ExtensionDownloader::SetBackoffPolicyForTesting(
const net::BackoffEntry::Policy* backoff_policy) {
manifests_queue_.set_backoff_policy(backoff_policy);
}
bool ExtensionDownloader::AddExtensionData(
const std::string& id,
const base::Version& version,
Manifest::Type extension_type,
mojom::ManifestLocation extension_location,
const GURL& extension_update_url,
const ExtraParams& extra,
int request_id,
ManifestFetchData::FetchPriority fetch_priority) {
GURL update_url(extension_update_url);
// Skip extensions with non-empty invalid update URLs.
if (!update_url.is_empty() && !update_url.is_valid()) {
DLOG(WARNING) << "Extension " << id << " has invalid update url "
<< update_url;
delegate_->OnExtensionDownloadStageChanged(
id, ExtensionDownloaderDelegate::Stage::FINISHED);
return false;
}
// Make sure we use SSL for store-hosted extensions.
if (extension_urls::IsWebstoreUpdateUrl(update_url) &&
!update_url.SchemeIsCryptographic())
update_url = extension_urls::GetWebstoreUpdateUrl();
// Skip extensions with empty IDs.
if (id.empty()) {
DLOG(WARNING) << "Found extension with empty ID";
delegate_->OnExtensionDownloadStageChanged(
id, ExtensionDownloaderDelegate::Stage::FINISHED);
return false;
}
if (update_url.DomainIs(kGoogleDotCom)) {
url_stats_.google_url_count++;
} else if (update_url.is_empty()) {
url_stats_.no_url_count++;
// Fill in default update URL.
update_url = extension_urls::GetWebstoreUpdateUrl();
} else {
url_stats_.other_url_count++;
}
switch (extension_type) {
case Manifest::TYPE_THEME:
++url_stats_.theme_count;
break;
case Manifest::TYPE_EXTENSION:
case Manifest::TYPE_USER_SCRIPT:
++url_stats_.extension_count;
break;
case Manifest::TYPE_HOSTED_APP:
case Manifest::TYPE_LEGACY_PACKAGED_APP:
++url_stats_.app_count;
break;
case Manifest::TYPE_PLATFORM_APP:
++url_stats_.platform_app_count;
break;
case Manifest::TYPE_UNKNOWN:
default:
++url_stats_.pending_count;
break;
}
DCHECK(!update_url.is_empty());
DCHECK(update_url.is_valid());
std::string install_source = extension_urls::IsWebstoreUpdateUrl(update_url)
? kDefaultInstallSource
: kNotFromWebstoreInstallSource;
if (extra.is_corrupt_reinstall)
install_source = kReinstallInstallSource;
ManifestFetchData::PingData ping_data;
ManifestFetchData::PingData* optional_ping_data = NULL;
if (delegate_->GetPingDataForExtension(id, &ping_data))
optional_ping_data = &ping_data;
// Find or create a ManifestFetchData to add this extension to.
bool added = false;
bool is_new_extension_force_installed =
extension_location == mojom::ManifestLocation::kExternalPolicyDownload;
FetchDataGroupKey key(request_id, update_url,
is_new_extension_force_installed);
auto existing_iter = fetches_preparing_.find(key);
if (existing_iter != fetches_preparing_.end() &&
!existing_iter->second.empty()) {
// Try to add to the ManifestFetchData at the end of the list.
ManifestFetchData* existing_fetch = existing_iter->second.back().get();
if (existing_fetch->AddExtension(
id, version.GetString(), optional_ping_data, extra.update_url_data,
install_source, extension_location, fetch_priority)) {
added = true;
}
}
if (!added) {
// Otherwise add a new element to the list, if the list doesn't exist or
// if its last element is already full.
std::unique_ptr<ManifestFetchData> fetch(
CreateManifestFetchData(update_url, request_id, fetch_priority));
ManifestFetchData* fetch_ptr = fetch.get();
if (is_new_extension_force_installed)
fetch_ptr->set_is_all_external_policy_download();
fetches_preparing_[key].push_back(std::move(fetch));
added = fetch_ptr->AddExtension(id, version.GetString(), optional_ping_data,
extra.update_url_data, install_source,
extension_location, fetch_priority);
DCHECK(added);
}
return true;
}
void ExtensionDownloader::ReportStats() const {
UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckExtension",
url_stats_.extension_count);
UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckTheme",
url_stats_.theme_count);
UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckApp", url_stats_.app_count);
UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckPackagedApp",
url_stats_.platform_app_count);
UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckPending",
url_stats_.pending_count);
UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckGoogleUrl",
url_stats_.google_url_count);
UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckOtherUrl",
url_stats_.other_url_count);
UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckNoUrl",
url_stats_.no_url_count);
}
void ExtensionDownloader::StartUpdateCheck(
std::unique_ptr<ManifestFetchData> fetch_data) {
if (g_test_delegate) {
g_test_delegate->StartUpdateCheck(this, delegate_, std::move(fetch_data));
return;
}
const ExtensionIdSet extension_ids = fetch_data->GetExtensionIds();
if (!ExtensionsBrowserClient::Get()->IsBackgroundUpdateAllowed()) {
NotifyExtensionsDownloadStageChanged(
extension_ids, ExtensionDownloaderDelegate::Stage::FINISHED);
NotifyExtensionsDownloadFailed(
extension_ids, fetch_data->request_ids(),
ExtensionDownloaderDelegate::Error::DISABLED);
return;
}
RequestQueue<ManifestFetchData>::iterator i;
for (i = manifests_queue_.begin(); i != manifests_queue_.end(); ++i) {
if (fetch_data->full_url() == i->full_url()) {
// This url is already scheduled to be fetched.
NotifyExtensionsDownloadStageChanged(
extension_ids,
ExtensionDownloaderDelegate::Stage::QUEUED_FOR_MANIFEST);
i->Merge(*fetch_data);
return;
}
}
if (manifests_queue_.active_request() &&
manifests_queue_.active_request()->full_url() == fetch_data->full_url()) {
NotifyExtensionsDownloadStageChanged(
extension_ids,
ExtensionDownloaderDelegate::Stage::DOWNLOADING_MANIFEST);
manifests_queue_.active_request()->Merge(*fetch_data);
} else {
UMA_HISTOGRAM_COUNTS_1M(
"Extensions.UpdateCheckUrlLength",
fetch_data->full_url().possibly_invalid_spec().length());
NotifyExtensionsDownloadStageChanged(
extension_ids, ExtensionDownloaderDelegate::Stage::QUEUED_FOR_MANIFEST);
manifests_queue_.ScheduleRequest(std::move(fetch_data));
}
}
network::mojom::URLLoaderFactory* ExtensionDownloader::GetURLLoaderFactoryToUse(
const GURL& url) {
if (!url.SchemeIsFile()) {
DCHECK(url_loader_factory_);
return url_loader_factory_.get();
}
// For file:// URL support, since we only issue "no-cors" requests with this
// factory, we can pass nullptr for the second argument.
file_url_loader_factory_.Bind(content::CreateFileURLLoaderFactory(
profile_path_for_url_loader_factory_,
nullptr /* shared_cors_origin_access_list */));
return file_url_loader_factory_.get();
}
void ExtensionDownloader::CreateManifestLoader() {
const ManifestFetchData* active_request = manifests_queue_.active_request();
const ExtensionIdSet extension_ids = active_request->GetExtensionIds();
NotifyExtensionsDownloadStageChanged(
extension_ids, ExtensionDownloaderDelegate::Stage::DOWNLOADING_MANIFEST);
std::vector<base::StringPiece> id_vector(extension_ids.begin(),
extension_ids.end());
std::string id_list = base::JoinString(id_vector, ",");
VLOG(2) << "Fetching " << active_request->full_url() << " for " << id_list;
VLOG(2) << "Update interactivity: "
<< (active_request->foreground_check()
? kUpdateInteractivityForeground
: kUpdateInteractivityBackground);
net::NetworkTrafficAnnotationTag traffic_annotation =
net::DefineNetworkTrafficAnnotation("extension_manifest_fetcher", R"(
semantics {
sender: "Extension Downloader"
description:
"Fetches information about an extension manifest (using its "
"update_url, which is usually Chrome Web Store) in order to update "
"the extension."
trigger:
"An update timer indicates that it's time to update extensions, or "
"a user triggers an extension update flow."
data:
"The extension id, version and install source (the cause of the "
"update flow). The client's OS, architecture, language, Chromium "
"version, channel and a flag stating whether the request "
"originated in the foreground or the background. Authentication is "
"used only for non-Chrome-Web-Store update_urls."
destination: WEBSITE
}
policy {
cookies_allowed: YES
cookies_store: "user"
setting:
"This feature cannot be disabled. It is only enabled when the user "
"has installed extensions."
chrome_policy {
ExtensionInstallBlocklist {
policy_options {mode: MANDATORY}
ExtensionInstallBlocklist: {
entries: '*'
}
}
}
})");
auto resource_request = std::make_unique<network::ResourceRequest>();
resource_request->url = active_request->full_url(),
resource_request->load_flags = net::LOAD_DISABLE_CACHE;
if (active_request->fetch_priority() ==
ManifestFetchData::FetchPriority::FOREGROUND) {
resource_request->priority = net::MEDIUM;
}
// Send traffic-management headers to the webstore, and omit credentials.
// https://bugs.chromium.org/p/chromium/issues/detail?id=647516
if (extension_urls::IsWebstoreUpdateUrl(active_request->full_url())) {
resource_request->headers.SetHeader(kUpdateInteractivityHeader,
active_request->foreground_check()
? kUpdateInteractivityForeground
: kUpdateInteractivityBackground);
resource_request->headers.SetHeader(kUpdateAppIdHeader, id_list);
resource_request->headers.SetHeader(
kUpdateUpdaterHeader,
base::StringPrintf(
"%s-%s", UpdateQueryParams::GetProdIdString(UpdateQueryParams::CRX),
UpdateQueryParams::GetProdVersion().c_str()));
resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
} else {
// Non-webstore sources may require HTTP auth.
resource_request->credentials_mode =
network::mojom::CredentialsMode::kInclude;
resource_request->site_for_cookies =
net::SiteForCookies::FromUrl(active_request->full_url());
}
manifest_loader_ = network::SimpleURLLoader::Create(
std::move(resource_request), traffic_annotation);
// Update checks can be interrupted if a network change is detected; this is
// common for the retail mode AppPack on ChromeOS. Retrying once should be
// enough to recover in those cases; let the fetcher retry up to 3 times
// just in case. http://crosbug.com/130602
const int kMaxRetries = 3;
manifest_loader_->SetRetryOptions(
kMaxRetries,
network::SimpleURLLoader::RetryMode::RETRY_ON_NETWORK_CHANGE);
network::mojom::URLLoaderFactory* url_loader_factory_to_use =
GetURLLoaderFactoryToUse(active_request->full_url());
manifest_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
url_loader_factory_to_use,
base::BindOnce(&ExtensionDownloader::OnManifestLoadComplete,
base::Unretained(this)));
}
void ExtensionDownloader::RetryManifestFetchRequest() {
constexpr base::TimeDelta backoff_delay;
NotifyExtensionsDownloadStageChanged(
manifests_queue_.active_request()->GetExtensionIds(),
ExtensionDownloaderDelegate::Stage::DOWNLOADING_MANIFEST_RETRY);
manifests_queue_.RetryRequest(backoff_delay);
}
void ExtensionDownloader::ReportManifestFetchFailure(
ManifestFetchData* fetch_data,
ExtensionDownloaderDelegate::Error error,
const ExtensionDownloaderDelegate::FailureData& data) {
const ExtensionIdSet extension_ids = fetch_data->GetExtensionIds();
NotifyExtensionsDownloadStageChanged(
extension_ids, ExtensionDownloaderDelegate::Stage::FINISHED);
NotifyExtensionsDownloadFailedWithFailureData(
extension_ids, fetch_data->request_ids(), error, data);
}
void ExtensionDownloader::TryFetchingExtensionsFromCache(
ManifestFetchData* fetch_data,
ExtensionDownloaderDelegate::Error error,
const int net_error,
const int response_code,
const base::Optional<ManifestInvalidFailureDataList>&
manifest_invalid_errors) {
const ExtensionIdSet extension_ids = fetch_data->GetExtensionIds();
ExtensionIdSet extensions_fetched_from_cache;
for (const auto& extension_id : extension_ids) {
// Extension is fetched here only in cases when we fail to fetch the update
// manifest or parsing of update manifest failed. In such cases, we don't
// have expected version and expected hash. Thus, passing empty hash and
// version would not be a problem as we only check for the expected hash and
// version if we have them.
auto extension_fetch_data(std::make_unique<ExtensionFetch>(
extension_id, fetch_data->base_url(), /*hash not fetched*/ "",
/*version not fetched*/ "", fetch_data->request_ids(),
fetch_data->fetch_priority()));
base::Optional<base::FilePath> cached_crx_path = GetCachedExtension(
*extension_fetch_data, /*manifest_fetch_failed*/ true);
if (cached_crx_path) {
delegate_->OnExtensionDownloadStageChanged(
extension_id, ExtensionDownloaderDelegate::Stage::FINISHED);
NotifyDelegateDownloadFinished(std::move(extension_fetch_data), true,
cached_crx_path.value(), false);
extensions_fetched_from_cache.insert(extension_id);
}
}
// All the extensions were found in the cache, no need to retry any request or
// report failure.
if (extensions_fetched_from_cache.size() == extension_ids.size())
return;
fetch_data->RemoveExtensions(extensions_fetched_from_cache,
manifest_query_params_);
if (ShouldRetryRequestForExtensionNotFoundInCache(net_error)) {
RetryManifestFetchRequest();
return;
}
if (error == ExtensionDownloaderDelegate::Error::MANIFEST_FETCH_FAILED) {
ExtensionDownloaderDelegate::FailureData failure_data(
-net_error,
(net_error == net::Error::ERR_HTTP_RESPONSE_CODE_FAILURE)
? base::Optional<int>(response_code)
: base::nullopt,
manifests_queue_.active_request_failure_count());
ReportManifestFetchFailure(fetch_data, error, failure_data);
return;
}
DCHECK(manifest_invalid_errors);
ManifestInvalidFailureDataList errors_for_remaining_extensions;
for (const auto& manifest_invalid_error : manifest_invalid_errors.value()) {
if (!extensions_fetched_from_cache.count(manifest_invalid_error.first))
errors_for_remaining_extensions.push_back(manifest_invalid_error);
}
NotifyExtensionsDownloadStageChanged(
fetch_data->GetExtensionIds(),
ExtensionDownloaderDelegate::Stage::FINISHED);
NotifyExtensionsManifestInvalidFailure(errors_for_remaining_extensions,
fetch_data->request_ids());
}
void ExtensionDownloader::RetryRequestOrHandleFailureOnManifestFetchFailure(
const network::SimpleURLLoader* loader,
const int response_code) {
bool all_force_installed_extensions =
manifests_queue_.active_request()->is_all_external_policy_download();
const int net_error = manifest_loader_->NetError();
const int request_failure_count =
manifests_queue_.active_request_failure_count();
// If the device is offline, do not retry for force installed extensions,
// try installing it from cache. Try fetching from cache only on first attempt
// in this case, because we will retry the request only if there was no entry
// in cache corresponding to this extension and there is no point in trying to
// fetch extension from cache again.
if (net_error == net::ERR_INTERNET_DISCONNECTED &&
all_force_installed_extensions && request_failure_count == 0) {
TryFetchingExtensionsFromCache(
manifests_queue_.active_request(),
ExtensionDownloaderDelegate::Error::MANIFEST_FETCH_FAILED, net_error,
response_code, base::nullopt /*manifest_invalid_errors*/);
return;
}
if (ShouldRetryRequest(loader) && request_failure_count < kMaxRetries) {
RetryManifestFetchRequest();
return;
}
const GURL url = loader->GetFinalURL();
RETRY_HISTOGRAM("ManifestFetchFailure", request_failure_count, url);
if (all_force_installed_extensions) {
TryFetchingExtensionsFromCache(
manifests_queue_.active_request(),
ExtensionDownloaderDelegate::Error::MANIFEST_FETCH_FAILED, net_error,
response_code, base::nullopt /*manifest_invalid_errors*/);
} else {
ExtensionDownloaderDelegate::FailureData failure_data(
-net_error,
(net_error == net::Error::ERR_HTTP_RESPONSE_CODE_FAILURE)
? base::Optional<int>(response_code)
: base::nullopt,
request_failure_count);
ReportManifestFetchFailure(
manifests_queue_.active_request(),
ExtensionDownloaderDelegate::Error::MANIFEST_FETCH_FAILED,
failure_data);
}
}
void ExtensionDownloader::OnManifestLoadComplete(
std::unique_ptr<std::string> response_body) {
const GURL url = manifest_loader_->GetFinalURL();
DCHECK(manifests_queue_.active_request());
int response_code = -1;
if (manifest_loader_->ResponseInfo() &&
manifest_loader_->ResponseInfo()->headers)
response_code = manifest_loader_->ResponseInfo()->headers->response_code();
VLOG(2) << response_code << " " << url;
const int request_failure_count =
manifests_queue_.active_request_failure_count();
// We want to try parsing the manifest, and if it indicates updates are
// available, we want to fire off requests to fetch those updates.
if (response_body && !response_body->empty()) {
RETRY_HISTOGRAM("ManifestFetchSuccess", request_failure_count, url);
VLOG(2) << "beginning manifest parse for " << url;
NotifyExtensionsDownloadStageChanged(
manifests_queue_.active_request()->GetExtensionIds(),
ExtensionDownloaderDelegate::Stage::PARSING_MANIFEST);
auto callback = base::BindOnce(&ExtensionDownloader::HandleManifestResults,
weak_ptr_factory_.GetWeakPtr(),
manifests_queue_.reset_active_request());
ParseUpdateManifest(*response_body, std::move(callback));
} else {
VLOG(1) << "Failed to fetch manifest '" << url.possibly_invalid_spec()
<< "' response code:" << response_code;
RetryRequestOrHandleFailureOnManifestFetchFailure(manifest_loader_.get(),
response_code);
}
manifest_loader_.reset();
file_url_loader_factory_.reset();
manifests_queue_.reset_active_request();
// If we have any pending manifest requests, fire off the next one.
manifests_queue_.StartNextRequest();
}
void ExtensionDownloader::HandleManifestResults(
std::unique_ptr<ManifestFetchData> fetch_data,
std::unique_ptr<UpdateManifestResults> results,
const base::Optional<ManifestParseFailure>& error) {
if (!results) {
VLOG(2) << "parsing manifest failed (" << fetch_data->full_url() << ")";
DCHECK(error.has_value());
ManifestInvalidFailureDataList manifest_invalid_errors;
const ExtensionIdSet extension_ids = fetch_data->GetExtensionIds();
manifest_invalid_errors.reserve(extension_ids.size());
// If the manifest parsing failed for all the extensions with a common
// error, add all extensions in the list with that error.
for (const auto& extension_id : extension_ids) {
manifest_invalid_errors.push_back(std::make_pair(
extension_id,
ExtensionDownloaderDelegate::FailureData(error.value().error)));
}
TryFetchingExtensionsFromCache(
fetch_data.get(), ExtensionDownloaderDelegate::Error::MANIFEST_INVALID,
0 /*net_error_code*/, 0 /*response_code*/, manifest_invalid_errors);
return;
} else {
VLOG(2) << "parsing manifest succeeded (" << fetch_data->full_url() << ")";
}
const ExtensionIdSet extension_ids = fetch_data->GetExtensionIds();
NotifyExtensionsDownloadStageChanged(
extension_ids, ExtensionDownloaderDelegate::Stage::MANIFEST_LOADED);
std::vector<UpdateManifestResult*> to_update;
std::set<std::string> no_updates;
ManifestInvalidFailureDataList errors;
// Examine the parsed manifest and kick off fetches of any new crx files.
DetermineUpdates(*fetch_data, *results, &to_update, &no_updates, &errors);
for (const UpdateManifestResult* update : to_update) {
const std::string& extension_id = update->extension_id;
GURL crx_url = update->crx_url;
NotifyUpdateFound(extension_id, update->version);
if (fetch_data->is_all_external_policy_download() && crx_url.is_empty()) {
DCHECK_EQ(fetch_data->fetch_priority(),
ManifestFetchData::FetchPriority::FOREGROUND);
}
FetchUpdatedExtension(
std::make_unique<ExtensionFetch>(
extension_id, crx_url, update->package_hash, update->version,
fetch_data->request_ids(), fetch_data->fetch_priority()),
update->info);
}
// If the manifest response included a <daystart> element, we want to save
// that value for any extensions which had sent a ping in the request.
if (fetch_data->base_url().DomainIs(kGoogleDotCom) &&
results->daystart_elapsed_seconds >= 0) {
Time day_start =
Time::Now() - TimeDelta::FromSeconds(results->daystart_elapsed_seconds);
for (const ExtensionId& id : extension_ids) {
ExtensionDownloaderDelegate::PingResult& result = ping_results_[id];
result.did_ping = fetch_data->DidPing(id, ManifestFetchData::ROLLCALL);
result.day_start = day_start;
}
}
NotifyExtensionsDownloadStageChanged(
no_updates, ExtensionDownloaderDelegate::Stage::FINISHED);
NotifyExtensionsDownloadFailed(
no_updates, fetch_data->request_ids(),
ExtensionDownloaderDelegate::Error::NO_UPDATE_AVAILABLE);
ExtensionIdSet extension_ids_with_errors;
for (const auto& error : errors)
extension_ids_with_errors.insert(error.first);
NotifyExtensionsDownloadStageChanged(
extension_ids_with_errors, ExtensionDownloaderDelegate::Stage::FINISHED);
NotifyExtensionsManifestInvalidFailure(errors, fetch_data->request_ids());
}
ExtensionDownloader::UpdateAvailability
ExtensionDownloader::GetUpdateAvailability(
const std::string& extension_id,
const std::vector<const UpdateManifestResult*>& possible_candidates,
UpdateManifestResult** update_result_out) const {
const bool is_extension_pending = delegate_->IsExtensionPending(extension_id);
std::string extension_version;
if (!is_extension_pending) {
// If we're not installing pending extension, we can only update
// extensions that have already existed in the system.
if (!delegate_->GetExtensionExistingVersion(extension_id,
&extension_version)) {
VLOG(2) << extension_id << " is not installed";
return UpdateAvailability::kBadUpdateSpecification;
}
VLOG(2) << extension_id << " is at '" << extension_version << "'";
}
bool has_noupdate = false;
for (const UpdateManifestResult* update : possible_candidates) {
const std::string& update_version_str = update->version;
if (VLOG_IS_ON(2)) {
if (update_version_str.empty())
VLOG(2) << "Manifest indicates " << extension_id
<< " has no update (info: " << update->info.value_or("no info")
<< ")";
else
VLOG(2) << "Manifest indicates " << extension_id
<< " latest version is '" << update_version_str << "'";
}
if (!is_extension_pending) {
// If we're not installing pending extension, and the update
// version is the same or older than what's already installed,
// we don't want it.
if (update_version_str.empty()) {
// If update manifest doesn't have version number => no update.
VLOG(2) << extension_id << " has empty version";
has_noupdate = true;
continue;
}
const base::Version update_version(update_version_str);
if (!update_version.IsValid()) {
VLOG(2) << extension_id << " has invalid version '"
<< update_version_str << "'";
continue;
}
const base::Version existing_version(extension_version);
if (update_version.CompareTo(existing_version) <= 0) {
VLOG(2) << extension_id << " version is not older than '"
<< update_version_str << "'";
has_noupdate = true;
continue;
}
}
// If the update specifies a browser minimum version, do we qualify?
if (update->browser_min_version.length() > 0 &&
!ExtensionsBrowserClient::Get()->IsMinBrowserVersionSupported(
update->browser_min_version)) {
// TODO(asargent) - We may want this to show up in the extensions UI
// eventually. (http://crbug.com/12547).
DLOG(WARNING) << "Updated version of extension " << extension_id
<< " available, but requires chrome version "
<< update->browser_min_version;
has_noupdate = true;
continue;
}
// Stop checking as soon as an update for |extension_id| is found.
VLOG(2) << "Will try to update " << extension_id;
*update_result_out = const_cast<UpdateManifestResult*>(update);
return UpdateAvailability::kAvailable;
}
return has_noupdate ? UpdateAvailability::kNoUpdate
: UpdateAvailability::kBadUpdateSpecification;
}
void ExtensionDownloader::DetermineUpdates(
const ManifestFetchData& fetch_data,
const UpdateManifestResults& possible_updates,
std::vector<UpdateManifestResult*>* to_update,
std::set<std::string>* no_updates,
ManifestInvalidFailureDataList* errors) {
DCHECK_NE(nullptr, to_update);
DCHECK_NE(nullptr, no_updates);
DCHECK_NE(nullptr, errors);
// Group successful possible updates by extension IDs.
const std::map<std::string, std::vector<const UpdateManifestResult*>>
update_groups = possible_updates.GroupSuccessfulByID();
// Contains IDs of extensions which neither have successful update entry nor
// are already inserted into |errors|.
ExtensionIdSet extension_errors;
const ExtensionIdSet extension_ids = fetch_data.GetExtensionIds();
// For each extensions in the current batch, greedily find an update from
// |possible_updates|.
for (const auto& extension_id : extension_ids) {
const auto it = update_groups.find(extension_id);
if (it == update_groups.end()) {
VLOG(2) << "Manifest doesn't have an update entry for " << extension_id;
extension_errors.insert(extension_id);
continue;
}
const std::vector<const UpdateManifestResult*>& possible_candidates =
it->second;
DCHECK(!possible_candidates.empty());
VLOG(2) << "Manifest has " << possible_candidates.size()
<< " update entries for " << extension_id;
UpdateManifestResult* update_result = nullptr;
UpdateAvailability update_availability = GetUpdateAvailability(
extension_id, possible_candidates, &update_result);
switch (update_availability) {
case UpdateAvailability::kAvailable:
DCHECK_NE(nullptr, update_result);
to_update->push_back(update_result);
break;
case UpdateAvailability::kNoUpdate:
no_updates->insert(extension_id);
break;
case UpdateAvailability::kBadUpdateSpecification:
errors->emplace_back(extension_id,
ManifestInvalidError::BAD_UPDATE_SPECIFICATION);
break;
}
}
for (const auto& possible_update : possible_updates.update_list) {
const ExtensionId& id = possible_update.extension_id;
if (!extension_errors.count(id))
continue;
DCHECK(possible_update.parse_error);
ManifestInvalidError error_type = possible_update.parse_error.value().error;
// Report any error corresponding to an extension.
errors->emplace_back(
id, error_type == ManifestInvalidError::BAD_APP_STATUS
? ExtensionDownloaderDelegate::FailureData(
error_type, possible_update.app_status)
: ExtensionDownloaderDelegate::FailureData(error_type));
extension_errors.erase(id);
}
// For the remaining extensions, we have missing ids.
for (const auto& id : extension_errors) {
errors->emplace_back(id, ExtensionDownloaderDelegate::FailureData(
ManifestInvalidError::MISSING_APP_ID));
}
}
base::Optional<base::FilePath> ExtensionDownloader::GetCachedExtension(
const ExtensionFetch& fetch_data,
bool manifest_fetch_failed) {
if (!extension_cache_) {
delegate_->OnExtensionDownloadCacheStatusRetrieved(
fetch_data.id,
ExtensionDownloaderDelegate::CacheStatus::CACHE_DISABLED);
return base::nullopt;
}
std::string version;
if (!extension_cache_->GetExtension(fetch_data.id, fetch_data.package_hash,
nullptr, &version)) {
delegate_->OnExtensionDownloadCacheStatusRetrieved(
fetch_data.id, ExtensionDownloaderDelegate::CacheStatus::CACHE_MISS);
return base::nullopt;
}
// If manifest fetch is failed, we need not verify the version of the cache as
// we will try to install the version present in the cache.
if (!manifest_fetch_failed && fetch_data.version != base::Version(version)) {
delegate_->OnExtensionDownloadCacheStatusRetrieved(
fetch_data.id,
ExtensionDownloaderDelegate::CacheStatus::CACHE_OUTDATED);
return base::nullopt;
}
delegate_->OnExtensionDownloadCacheStatusRetrieved(
fetch_data.id, manifest_fetch_failed
? ExtensionDownloaderDelegate::CacheStatus::
CACHE_HIT_ON_MANIFEST_FETCH_FAILURE
: ExtensionDownloaderDelegate::CacheStatus::CACHE_HIT);
base::FilePath crx_path;
// Now get .crx file path.
// TODO(https://crbug.com/1018271#c2) This has a side-effect in extension
// cache implementation: extension in the cache will be marked as recently
// used.
extension_cache_->GetExtension(fetch_data.id, fetch_data.package_hash,
&crx_path, &version);
return std::move(crx_path);
}
// Begins (or queues up) download of an updated extension.
void ExtensionDownloader::FetchUpdatedExtension(
std::unique_ptr<ExtensionFetch> fetch_data,
base::Optional<std::string> info) {
if (!fetch_data->url.is_valid()) {
// TODO(asargent): This can sometimes be invalid. See crbug.com/130881.
DLOG(WARNING) << "Invalid URL: '" << fetch_data->url.possibly_invalid_spec()
<< "' for extension " << fetch_data->id;
delegate_->OnExtensionDownloadStageChanged(
fetch_data->id, ExtensionDownloaderDelegate::Stage::FINISHED);
if (fetch_data->url.is_empty()) {
// We expect to receive initialised |info| from the manifest parser in
// case of no updates status in the update manifest.
ExtensionDownloaderDelegate::FailureData data(info.value_or(""));
NotifyExtensionsDownloadFailedWithFailureData(
{fetch_data->id}, fetch_data->request_ids,
ExtensionDownloaderDelegate::Error::CRX_FETCH_URL_EMPTY, data);
} else {
NotifyExtensionsDownloadFailed(
{fetch_data->id}, fetch_data->request_ids,
ExtensionDownloaderDelegate::Error::CRX_FETCH_URL_INVALID);
}
return;
}
for (RequestQueue<ExtensionFetch>::iterator iter = extensions_queue_.begin();
iter != extensions_queue_.end();
++iter) {
if (iter->id == fetch_data->id || iter->url == fetch_data->url) {
delegate_->OnExtensionDownloadStageChanged(
fetch_data->id, ExtensionDownloaderDelegate::Stage::QUEUED_FOR_CRX);
iter->request_ids.insert(fetch_data->request_ids.begin(),
fetch_data->request_ids.end());
return; // already scheduled
}
}
if (extensions_queue_.active_request() &&
extensions_queue_.active_request()->url == fetch_data->url) {
delegate_->OnExtensionDownloadStageChanged(
fetch_data->id, ExtensionDownloaderDelegate::Stage::DOWNLOADING_CRX);
extensions_queue_.active_request()->request_ids.insert(
fetch_data->request_ids.begin(), fetch_data->request_ids.end());
return;
}
base::Optional<base::FilePath> cached_crx_path =
GetCachedExtension(*fetch_data, /*manifest_fetch_failed*/ false);
if (cached_crx_path) {
delegate_->OnExtensionDownloadStageChanged(
fetch_data->id, ExtensionDownloaderDelegate::Stage::FINISHED);
NotifyDelegateDownloadFinished(std::move(fetch_data), true,
cached_crx_path.value(), false);
} else {
delegate_->OnExtensionDownloadStageChanged(
fetch_data->id, ExtensionDownloaderDelegate::Stage::QUEUED_FOR_CRX);
extensions_queue_.ScheduleRequest(std::move(fetch_data));
}
}
void ExtensionDownloader::NotifyDelegateDownloadFinished(
std::unique_ptr<ExtensionFetch> fetch_data,
bool from_cache,
const base::FilePath& crx_path,
bool file_ownership_passed) {
// Dereference required params before passing a scoped_ptr.
const ExtensionId& id = fetch_data->id;
const std::string& package_hash = fetch_data->package_hash;
const GURL& url = fetch_data->url;
const base::Version& version = fetch_data->version;
const std::set<int>& request_ids = fetch_data->request_ids;
const crx_file::VerifierFormat required_format =
extension_urls::IsWebstoreUpdateUrl(fetch_data->url)
? GetWebstoreVerifierFormat(false)
: crx_format_requirement_;
CRXFileInfo crx_info(crx_path, required_format);
crx_info.expected_hash = package_hash;
crx_info.extension_id = id;
crx_info.expected_version = version;
delegate_->OnExtensionDownloadFinished(
crx_info, file_ownership_passed, url, ping_results_[id], request_ids,
from_cache ? base::BindOnce(&ExtensionDownloader::CacheInstallDone,
weak_ptr_factory_.GetWeakPtr(),
std::move(fetch_data))
: ExtensionDownloaderDelegate::InstallCallback());
if (!from_cache)
ping_results_.erase(id);
}
void ExtensionDownloader::CacheInstallDone(
std::unique_ptr<ExtensionFetch> fetch_data,
bool should_download) {
ping_results_.erase(fetch_data->id);
if (should_download) {
// Resume download from cached manifest data.
extensions_queue_.ScheduleRequest(std::move(fetch_data));
}
}
void ExtensionDownloader::CreateExtensionLoader() {
const ExtensionFetch* fetch = extensions_queue_.active_request();
delegate_->OnExtensionDownloadStageChanged(
fetch->id, ExtensionDownloaderDelegate::Stage::DOWNLOADING_CRX);
extension_loader_resource_request_ =
std::make_unique<network::ResourceRequest>();
extension_loader_resource_request_->url = fetch->url;
int load_flags = net::LOAD_DISABLE_CACHE;
bool is_secure = fetch->url.SchemeIsCryptographic();
extension_loader_resource_request_->load_flags = load_flags;
if (fetch->credentials != ExtensionFetch::CREDENTIALS_COOKIES || !is_secure) {
extension_loader_resource_request_->credentials_mode =
network::mojom::CredentialsMode::kOmit;
} else {
extension_loader_resource_request_->site_for_cookies =
net::SiteForCookies::FromUrl(fetch->url);
}
if (fetch->credentials == ExtensionFetch::CREDENTIALS_OAUTH2_TOKEN &&
is_secure) {
if (access_token_.empty()) {
// We should try OAuth2, but we have no token cached. This
// ExtensionLoader will be started once the token fetch is complete,
// in either OnTokenFetchSuccess or OnTokenFetchFailure.
DCHECK(identity_manager_);
signin::ScopeSet webstore_scopes;
webstore_scopes.insert(kWebstoreOAuth2Scope);
// It is safe to use Unretained(this) here given that the callback
// will not be invoked if this object is deleted.
access_token_fetcher_ =
std::make_unique<signin::PrimaryAccountAccessTokenFetcher>(
kTokenServiceConsumerId, identity_manager_, webstore_scopes,
base::BindOnce(&ExtensionDownloader::OnAccessTokenFetchComplete,
base::Unretained(this)),
signin::PrimaryAccountAccessTokenFetcher::Mode::kImmediate);
return;
}
extension_loader_resource_request_->headers.SetHeader(
net::HttpRequestHeaders::kAuthorization,
base::StringPrintf("Bearer %s", access_token_.c_str()));
}
VLOG(2) << "Starting load of " << fetch->url << " for " << fetch->id;
StartExtensionLoader();
}
void ExtensionDownloader::StartExtensionLoader() {
net::NetworkTrafficAnnotationTag traffic_annotation =
net::DefineNetworkTrafficAnnotation("extension_crx_fetcher", R"(
semantics {
sender: "Extension Downloader"
description:
"Downloads an extension's crx file in order to update the "
"extension, using update_url from the extension's manifest which "
"is usually Chrome WebStore."
trigger:
"An update check indicates an extension update is available."
data:
"URL and required data to specify the extension to download. "
"OAuth2 token is also sent if connection is secure and to Google."
destination: WEBSITE
}
policy {
cookies_allowed: YES
cookies_store: "user"
setting:
"This feature cannot be disabled. It is only enabled when the user "
"has installed extensions and it needs updating."
chrome_policy {
ExtensionInstallBlacklist {
policy_options {mode: MANDATORY}
ExtensionInstallBlacklist: {
entries: '*'
}
}
}
})");
last_extension_loader_resource_request_headers_for_testing_ =
extension_loader_resource_request_->headers;
last_extension_loader_load_flags_for_testing_ =
extension_loader_resource_request_->load_flags;
const ExtensionFetch* active_request = extensions_queue_.active_request();
if (active_request->fetch_priority ==
ManifestFetchData::FetchPriority::FOREGROUND) {
extension_loader_resource_request_->priority = net::MEDIUM;
}
network::mojom::URLLoaderFactory* url_loader_factory_to_use =
GetURLLoaderFactoryToUse(extension_loader_resource_request_->url);
extension_loader_ = network::SimpleURLLoader::Create(
std::move(extension_loader_resource_request_), traffic_annotation);
const int kMaxRetries = 3;
extension_loader_->SetRetryOptions(
kMaxRetries,
network::SimpleURLLoader::RetryMode::RETRY_ON_NETWORK_CHANGE);
extension_loader_->DownloadToTempFile(
url_loader_factory_to_use,
base::BindOnce(&ExtensionDownloader::OnExtensionLoadComplete,
base::Unretained(this)));
}
void ExtensionDownloader::OnExtensionLoadComplete(base::FilePath crx_path) {
GURL url = extension_loader_->GetFinalURL();
int net_error = extension_loader_->NetError();
int response_code = -1;
if (extension_loader_->ResponseInfo() &&
extension_loader_->ResponseInfo()->headers) {
response_code = extension_loader_->ResponseInfo()->headers->response_code();
}
const base::TimeDelta& backoff_delay = base::TimeDelta::FromMilliseconds(0);
ExtensionFetch& active_request = *extensions_queue_.active_request();
const ExtensionId& id = active_request.id;
if (!crx_path.empty()) {
RETRY_HISTOGRAM("CrxFetchSuccess",
extensions_queue_.active_request_failure_count(),
url);
std::unique_ptr<ExtensionFetch> fetch_data =
extensions_queue_.reset_active_request();
delegate_->OnExtensionDownloadStageChanged(
id, ExtensionDownloaderDelegate::Stage::FINISHED);
NotifyDelegateDownloadFinished(std::move(fetch_data), false, crx_path,
true);
} else if (IterateFetchCredentialsAfterFailure(&active_request,
response_code)) {
delegate_->OnExtensionDownloadStageChanged(
id, ExtensionDownloaderDelegate::Stage::DOWNLOADING_CRX_RETRY);
extensions_queue_.RetryRequest(backoff_delay);
delegate_->OnExtensionDownloadRetryForTests();
} else {
const std::set<int>& request_ids = active_request.request_ids;
const ExtensionDownloaderDelegate::PingResult& ping = ping_results_[id];
VLOG(1) << "Failed to fetch extension '" << url.possibly_invalid_spec()
<< "' response code:" << response_code;
if (ShouldRetryRequest(extension_loader_.get()) &&
extensions_queue_.active_request_failure_count() < kMaxRetries) {
delegate_->OnExtensionDownloadStageChanged(
id, ExtensionDownloaderDelegate::Stage::DOWNLOADING_CRX_RETRY);
extensions_queue_.RetryRequest(backoff_delay);
delegate_->OnExtensionDownloadRetryForTests();
} else {
RETRY_HISTOGRAM("CrxFetchFailure",
extensions_queue_.active_request_failure_count(),
url);
delegate_->OnExtensionDownloadStageChanged(
id, ExtensionDownloaderDelegate::Stage::FINISHED);
ExtensionDownloaderDelegate::FailureData failure_data(
-net_error,
(net_error == net::Error::ERR_HTTP_RESPONSE_CODE_FAILURE)
? base::Optional<int>(response_code)
: base::nullopt,
extensions_queue_.active_request_failure_count());
delegate_->OnExtensionDownloadFailed(
id, ExtensionDownloaderDelegate::Error::CRX_FETCH_FAILED, ping,
request_ids, failure_data);
}
ping_results_.erase(id);
extensions_queue_.reset_active_request();
}
extension_loader_.reset();
file_url_loader_factory_.reset();
// If there are any pending downloads left, start the next one.
extensions_queue_.StartNextRequest();
}
void ExtensionDownloader::NotifyExtensionsManifestInvalidFailure(
const ManifestInvalidFailureDataList& errors,
const std::set<int>& request_ids) {
for (const auto& error_data : errors) {
const ExtensionId& extension_id = error_data.first;
ExtensionDownloaderDelegate::FailureData data = error_data.second;
auto ping_iter = ping_results_.find(extension_id);
delegate_->OnExtensionDownloadFailed(
extension_id, ExtensionDownloaderDelegate::Error::MANIFEST_INVALID,
ping_iter == ping_results_.end()
? ExtensionDownloaderDelegate::PingResult()
: ping_iter->second,
request_ids, data);
ping_results_.erase(extension_id);
}
}
void ExtensionDownloader::NotifyExtensionsDownloadStageChanged(
ExtensionIdSet extension_ids,
ExtensionDownloaderDelegate::Stage stage) {
for (const auto& it : extension_ids) {
delegate_->OnExtensionDownloadStageChanged(it, stage);
}
}
void ExtensionDownloader::NotifyExtensionsDownloadFailed(
ExtensionIdSet extension_ids,
std::set<int> request_ids,
ExtensionDownloaderDelegate::Error error) {
NotifyExtensionsDownloadFailedWithFailureData(
std::move(extension_ids), std::move(request_ids), error,
ExtensionDownloaderDelegate::FailureData());
}
void ExtensionDownloader::NotifyExtensionsDownloadFailedWithFailureData(
ExtensionIdSet extension_ids,
std::set<int> request_ids,
ExtensionDownloaderDelegate::Error error,
const ExtensionDownloaderDelegate::FailureData& data) {
for (const auto& it : extension_ids) {
auto ping_iter = ping_results_.find(it);
delegate_->OnExtensionDownloadFailed(
it, error,
ping_iter == ping_results_.end()
? ExtensionDownloaderDelegate::PingResult()
: ping_iter->second,
request_ids, data);
ping_results_.erase(it);
}
}
void ExtensionDownloader::NotifyUpdateFound(const std::string& id,
const std::string& version) {
UpdateDetails updateInfo(id, base::Version(version));
content::NotificationService::current()->Notify(
extensions::NOTIFICATION_EXTENSION_UPDATE_FOUND,
content::NotificationService::AllBrowserContextsAndSources(),
content::Details<UpdateDetails>(&updateInfo));
}
bool ExtensionDownloader::IterateFetchCredentialsAfterFailure(
ExtensionFetch* fetch,
int response_code) {
bool auth_failure = response_code == net::HTTP_UNAUTHORIZED ||
response_code == net::HTTP_FORBIDDEN;
if (!auth_failure) {
return false;
}
// Here we decide what to do next if the server refused to authorize this
// fetch.
switch (fetch->credentials) {
case ExtensionFetch::CREDENTIALS_NONE:
if (fetch->url.DomainIs(kGoogleDotCom) && identity_manager_) {
fetch->credentials = ExtensionFetch::CREDENTIALS_OAUTH2_TOKEN;
} else {
fetch->credentials = ExtensionFetch::CREDENTIALS_COOKIES;
}
return true;
case ExtensionFetch::CREDENTIALS_OAUTH2_TOKEN:
fetch->oauth2_attempt_count++;
// OAuth2 may fail due to an expired access token, in which case we
// should invalidate the token and try again.
if (response_code == net::HTTP_UNAUTHORIZED &&
fetch->oauth2_attempt_count <= kMaxOAuth2Attempts) {
DCHECK(identity_manager_);
signin::ScopeSet webstore_scopes;
webstore_scopes.insert(kWebstoreOAuth2Scope);
identity_manager_->RemoveAccessTokenFromCache(
identity_manager_->GetPrimaryAccountId(signin::ConsentLevel::kSync),
webstore_scopes, access_token_);
access_token_.clear();
return true;
}
// Either there is no Gaia identity available, the active identity
// doesn't have access to this resource, or the server keeps returning
// 401s and we've retried too many times. Fall back on cookies.
if (access_token_.empty() || response_code == net::HTTP_FORBIDDEN ||
fetch->oauth2_attempt_count > kMaxOAuth2Attempts) {
fetch->credentials = ExtensionFetch::CREDENTIALS_COOKIES;
return true;
}
// Something else is wrong. Time to give up.
return false;
case ExtensionFetch::CREDENTIALS_COOKIES:
if (response_code == net::HTTP_FORBIDDEN) {
// Try the next session identity, up to some maximum.
return IncrementAuthUserIndex(&fetch->url);
}
return false;
default:
NOTREACHED();
}
NOTREACHED();
return false;
}
void ExtensionDownloader::OnAccessTokenFetchComplete(
GoogleServiceAuthError error,
signin::AccessTokenInfo token_info) {
access_token_fetcher_.reset();
if (error.state() != GoogleServiceAuthError::NONE) {
// If we fail to get an access token, kick the pending fetch and let it fall
// back on cookies.
StartExtensionLoader();
return;
}
access_token_ = token_info.token;
extension_loader_resource_request_->headers.SetHeader(
net::HttpRequestHeaders::kAuthorization,
base::StringPrintf("Bearer %s", access_token_.c_str()));
StartExtensionLoader();
}
ManifestFetchData* ExtensionDownloader::CreateManifestFetchData(
const GURL& update_url,
int request_id,
ManifestFetchData::FetchPriority fetch_priority) {
ManifestFetchData::PingMode ping_mode = ManifestFetchData::NO_PING;
if (update_url.DomainIs(ping_enabled_domain_.c_str()))
ping_mode = ManifestFetchData::PING_WITH_ENABLED_STATE;
return new ManifestFetchData(update_url, request_id, brand_code_,
manifest_query_params_, ping_mode,
fetch_priority);
}
} // namespace extensions