summaryrefslogtreecommitdiff
path: root/chromium/extensions/browser/updater/extension_downloader.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/extensions/browser/updater/extension_downloader.cc')
-rw-r--r--chromium/extensions/browser/updater/extension_downloader.cc947
1 files changed, 947 insertions, 0 deletions
diff --git a/chromium/extensions/browser/updater/extension_downloader.cc b/chromium/extensions/browser/updater/extension_downloader.cc
new file mode 100644
index 00000000000..116ef7abb15
--- /dev/null
+++ b/chromium/extensions/browser/updater/extension_downloader.cc
@@ -0,0 +1,947 @@
+// 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/sparse_histogram.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/time/time.h"
+#include "base/version.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/notification_details.h"
+#include "content/public/browser/notification_service.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/request_queue_impl.h"
+#include "extensions/browser/updater/safe_manifest_parser.h"
+#include "extensions/common/extension_urls.h"
+#include "extensions/common/manifest_url_handlers.h"
+#include "google_apis/gaia/identity_provider.h"
+#include "net/base/backoff_entry.h"
+#include "net/base/load_flags.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_status_code.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "net/url_request/url_request_status.h"
+
+using base::Time;
+using base::TimeDelta;
+using content::BrowserThread;
+
+namespace extensions {
+
+const char ExtensionDownloader::kBlacklistAppID[] = "com.google.crx.blacklist";
+
+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.
+ -1,
+
+ // 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 kGoogleDotCom[] = "google.com";
+const char kTokenServiceConsumerId[] = "extension_downloader";
+const char kWebstoreOAuth2Scope[] =
+ "https://www.googleapis.com/auth/chromewebstore.readonly";
+
+#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 net::URLRequestStatus& status,
+ int response_code) {
+ // Retry if the response code is a server error, or the request failed because
+ // of network errors as opposed to file errors.
+ return ((response_code >= 500 && status.is_success()) ||
+ status.status() == net::URLRequestStatus::FAILED);
+}
+
+// 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
+
+UpdateDetails::UpdateDetails(const std::string& id, const Version& version)
+ : id(id), version(version) {
+}
+
+UpdateDetails::~UpdateDetails() {
+}
+
+ExtensionDownloader::ExtensionFetch::ExtensionFetch()
+ : url(), 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)
+ : id(id),
+ url(url),
+ package_hash(package_hash),
+ version(version),
+ request_ids(request_ids),
+ credentials(CREDENTIALS_NONE),
+ oauth2_attempt_count(0) {
+}
+
+ExtensionDownloader::ExtensionFetch::~ExtensionFetch() {
+}
+
+ExtensionDownloader::ExtensionDownloader(
+ ExtensionDownloaderDelegate* delegate,
+ net::URLRequestContextGetter* request_context)
+ : OAuth2TokenService::Consumer(kTokenServiceConsumerId),
+ delegate_(delegate),
+ request_context_(request_context),
+ manifests_queue_(&kDefaultBackoffPolicy,
+ base::Bind(&ExtensionDownloader::CreateManifestFetcher,
+ base::Unretained(this))),
+ extensions_queue_(&kDefaultBackoffPolicy,
+ base::Bind(&ExtensionDownloader::CreateExtensionFetcher,
+ base::Unretained(this))),
+ extension_cache_(NULL),
+ enable_extra_update_metrics_(false),
+ weak_ptr_factory_(this) {
+ DCHECK(delegate_);
+ DCHECK(request_context_.get());
+}
+
+ExtensionDownloader::~ExtensionDownloader() {
+}
+
+bool ExtensionDownloader::AddExtension(const Extension& extension,
+ int request_id) {
+ // Skip extensions with empty update URLs converted from user
+ // scripts.
+ if (extension.converted_from_user_script() &&
+ ManifestURL::GetUpdateURL(&extension).is_empty()) {
+ return false;
+ }
+
+ // If the extension updates itself from the gallery, ignore any update URL
+ // data. At the moment there is no extra data that an extension can
+ // communicate to the the gallery update servers.
+ std::string update_url_data;
+ if (!ManifestURL::UpdatesFromGallery(&extension))
+ update_url_data = delegate_->GetUpdateUrlData(extension.id());
+
+ return AddExtensionData(
+ extension.id(), *extension.version(), extension.GetType(),
+ ManifestURL::GetUpdateURL(&extension), update_url_data, request_id);
+}
+
+bool ExtensionDownloader::AddPendingExtension(const std::string& id,
+ const GURL& update_url,
+ int request_id) {
+ // Use a zero version to ensure that a pending extension will always
+ // be updated, and thus installed (assuming all extensions have
+ // non-zero versions).
+ Version version("0.0.0.0");
+ DCHECK(version.IsValid());
+
+ return AddExtensionData(id, version, Manifest::TYPE_UNKNOWN, update_url,
+ std::string(), request_id);
+}
+
+void ExtensionDownloader::StartAllPending(ExtensionCache* cache) {
+ if (cache) {
+ extension_cache_ = cache;
+ extension_cache_->Start(base::Bind(&ExtensionDownloader::DoStartAllPending,
+ weak_ptr_factory_.GetWeakPtr()));
+ } else {
+ DoStartAllPending();
+ }
+}
+
+void ExtensionDownloader::DoStartAllPending() {
+ ReportStats();
+ url_stats_ = URLStats();
+
+ for (FetchMap::iterator it = fetches_preparing_.begin();
+ it != fetches_preparing_.end();
+ ++it) {
+ std::vector<linked_ptr<ManifestFetchData>>& list = it->second;
+ for (size_t i = 0; i < list.size(); ++i) {
+ StartUpdateCheck(scoped_ptr<ManifestFetchData>(list[i].release()));
+ }
+ }
+ fetches_preparing_.clear();
+}
+
+void ExtensionDownloader::StartBlacklistUpdate(
+ const std::string& version,
+ const ManifestFetchData::PingData& ping_data,
+ int request_id) {
+ // Note: it is very important that we use the https version of the update
+ // url here to avoid DNS hijacking of the blacklist, which is not validated
+ // by a public key signature like .crx files are.
+ scoped_ptr<ManifestFetchData> blacklist_fetch(CreateManifestFetchData(
+ extension_urls::GetWebstoreUpdateUrl(), request_id));
+ DCHECK(blacklist_fetch->base_url().SchemeIsCryptographic());
+ blacklist_fetch->AddExtension(kBlacklistAppID, version, &ping_data,
+ std::string(), kDefaultInstallSource);
+ StartUpdateCheck(std::move(blacklist_fetch));
+}
+
+void ExtensionDownloader::SetWebstoreIdentityProvider(
+ scoped_ptr<IdentityProvider> identity_provider) {
+ identity_provider_.swap(identity_provider);
+}
+
+bool ExtensionDownloader::AddExtensionData(const std::string& id,
+ const Version& version,
+ Manifest::Type extension_type,
+ const GURL& extension_update_url,
+ const std::string& update_url_data,
+ int request_id) {
+ 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;
+ 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";
+ 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;
+ }
+
+ std::vector<GURL> update_urls;
+ update_urls.push_back(update_url);
+ // If metrics are enabled, also add to ManifestFetchData for the
+ // webstore update URL.
+ if (!extension_urls::IsWebstoreUpdateUrl(update_url) &&
+ enable_extra_update_metrics_) {
+ update_urls.push_back(extension_urls::GetWebstoreUpdateUrl());
+ }
+
+ for (size_t i = 0; i < update_urls.size(); ++i) {
+ DCHECK(!update_urls[i].is_empty());
+ DCHECK(update_urls[i].is_valid());
+
+ std::string install_source =
+ i == 0 ? kDefaultInstallSource : kNotFromWebstoreInstallSource;
+
+ 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;
+ FetchMap::iterator existing_iter =
+ fetches_preparing_.find(std::make_pair(request_id, update_urls[i]));
+ 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, update_url_data,
+ install_source)) {
+ 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.
+ linked_ptr<ManifestFetchData> fetch(
+ CreateManifestFetchData(update_urls[i], request_id));
+ fetches_preparing_[std::make_pair(request_id, update_urls[i])].push_back(
+ fetch);
+ added = fetch->AddExtension(id, version.GetString(), optional_ping_data,
+ update_url_data, install_source);
+ 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(
+ scoped_ptr<ManifestFetchData> fetch_data) {
+ const std::set<std::string>& id_set(fetch_data->extension_ids());
+
+ if (!ExtensionsBrowserClient::Get()->IsBackgroundUpdateAllowed()) {
+ NotifyExtensionsDownloadFailed(id_set,
+ fetch_data->request_ids(),
+ ExtensionDownloaderDelegate::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.
+ i->Merge(*fetch_data);
+ return;
+ }
+ }
+
+ if (manifests_queue_.active_request() &&
+ manifests_queue_.active_request()->full_url() == fetch_data->full_url()) {
+ manifests_queue_.active_request()->Merge(*fetch_data);
+ } else {
+ UMA_HISTOGRAM_COUNTS(
+ "Extensions.UpdateCheckUrlLength",
+ fetch_data->full_url().possibly_invalid_spec().length());
+
+ manifests_queue_.ScheduleRequest(std::move(fetch_data));
+ }
+}
+
+void ExtensionDownloader::CreateManifestFetcher() {
+ if (VLOG_IS_ON(2)) {
+ std::vector<std::string> id_vector(
+ manifests_queue_.active_request()->extension_ids().begin(),
+ manifests_queue_.active_request()->extension_ids().end());
+ std::string id_list = base::JoinString(id_vector, ",");
+ VLOG(2) << "Fetching " << manifests_queue_.active_request()->full_url()
+ << " for " << id_list;
+ }
+
+ manifest_fetcher_ = net::URLFetcher::Create(
+ kManifestFetcherId, manifests_queue_.active_request()->full_url(),
+ net::URLFetcher::GET, this);
+ manifest_fetcher_->SetRequestContext(request_context_.get());
+ manifest_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
+ net::LOAD_DO_NOT_SAVE_COOKIES |
+ net::LOAD_DISABLE_CACHE);
+ // 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
+ manifest_fetcher_->SetAutomaticallyRetryOnNetworkChanges(3);
+ manifest_fetcher_->Start();
+}
+
+void ExtensionDownloader::OnURLFetchComplete(const net::URLFetcher* source) {
+ VLOG(2) << source->GetResponseCode() << " " << source->GetURL();
+
+ if (source == manifest_fetcher_.get()) {
+ std::string data;
+ source->GetResponseAsString(&data);
+ OnManifestFetchComplete(source->GetURL(),
+ source->GetStatus(),
+ source->GetResponseCode(),
+ source->GetBackoffDelay(),
+ data);
+ } else if (source == extension_fetcher_.get()) {
+ OnCRXFetchComplete(source,
+ source->GetURL(),
+ source->GetStatus(),
+ source->GetResponseCode(),
+ source->GetBackoffDelay());
+ } else {
+ NOTREACHED();
+ }
+}
+
+void ExtensionDownloader::OnManifestFetchComplete(
+ const GURL& url,
+ const net::URLRequestStatus& status,
+ int response_code,
+ const base::TimeDelta& backoff_delay,
+ const std::string& data) {
+ // 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 (status.status() == net::URLRequestStatus::SUCCESS &&
+ (response_code == 200 || (url.SchemeIsFile() && data.length() > 0))) {
+ RETRY_HISTOGRAM("ManifestFetchSuccess",
+ manifests_queue_.active_request_failure_count(),
+ url);
+ VLOG(2) << "beginning manifest parse for " << url;
+ scoped_refptr<SafeManifestParser> safe_parser(new SafeManifestParser(
+ data,
+ base::Bind(
+ &ExtensionDownloader::HandleManifestResults,
+ weak_ptr_factory_.GetWeakPtr(),
+ base::Owned(manifests_queue_.reset_active_request().release()))));
+ safe_parser->Start();
+ } else {
+ VLOG(1) << "Failed to fetch manifest '" << url.possibly_invalid_spec()
+ << "' response code:" << response_code;
+ if (ShouldRetryRequest(status, response_code) &&
+ manifests_queue_.active_request_failure_count() < kMaxRetries) {
+ manifests_queue_.RetryRequest(backoff_delay);
+ } else {
+ RETRY_HISTOGRAM("ManifestFetchFailure",
+ manifests_queue_.active_request_failure_count(),
+ url);
+ NotifyExtensionsDownloadFailed(
+ manifests_queue_.active_request()->extension_ids(),
+ manifests_queue_.active_request()->request_ids(),
+ ExtensionDownloaderDelegate::MANIFEST_FETCH_FAILED);
+ }
+ }
+ manifest_fetcher_.reset();
+ manifests_queue_.reset_active_request();
+
+ // If we have any pending manifest requests, fire off the next one.
+ manifests_queue_.StartNextRequest();
+}
+
+void ExtensionDownloader::HandleManifestResults(
+ const ManifestFetchData* fetch_data,
+ const UpdateManifest::Results* results) {
+ // Keep a list of extensions that will not be updated, so that the |delegate_|
+ // can be notified once we're done here.
+ std::set<std::string> not_updated(fetch_data->extension_ids());
+
+ if (!results) {
+ VLOG(2) << "parsing manifest failed (" << fetch_data->full_url() << ")";
+ NotifyExtensionsDownloadFailed(
+ not_updated, fetch_data->request_ids(),
+ ExtensionDownloaderDelegate::MANIFEST_INVALID);
+ return;
+ } else {
+ VLOG(2) << "parsing manifest succeeded (" << fetch_data->full_url() << ")";
+ }
+
+ // Examine the parsed manifest and kick off fetches of any new crx files.
+ std::vector<int> updates;
+ DetermineUpdates(*fetch_data, *results, &updates);
+ for (size_t i = 0; i < updates.size(); i++) {
+ const UpdateManifest::Result* update = &(results->list.at(updates[i]));
+ const std::string& id = update->extension_id;
+ not_updated.erase(id);
+
+ GURL crx_url = update->crx_url;
+ if (id != kBlacklistAppID) {
+ NotifyUpdateFound(update->extension_id, update->version);
+ } else {
+ // The URL of the blacklist file is returned by the server and we need to
+ // be sure that we continue to be able to reliably detect whether a URL
+ // references a blacklist file.
+ DCHECK(extension_urls::IsBlacklistUpdateUrl(crx_url)) << crx_url;
+
+ // Force https (crbug.com/129587).
+ if (!crx_url.SchemeIsCryptographic()) {
+ url::Replacements<char> replacements;
+ std::string scheme("https");
+ replacements.SetScheme(scheme.c_str(),
+ url::Component(0, scheme.size()));
+ crx_url = crx_url.ReplaceComponents(replacements);
+ }
+ }
+ scoped_ptr<ExtensionFetch> fetch(
+ new ExtensionFetch(update->extension_id, crx_url, update->package_hash,
+ update->version, fetch_data->request_ids()));
+ FetchUpdatedExtension(std::move(fetch));
+ }
+
+ // 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);
+
+ const std::set<std::string>& extension_ids = fetch_data->extension_ids();
+ std::set<std::string>::const_iterator i;
+ for (i = extension_ids.begin(); i != extension_ids.end(); i++) {
+ const std::string& id = *i;
+ ExtensionDownloaderDelegate::PingResult& result = ping_results_[id];
+ result.did_ping = fetch_data->DidPing(id, ManifestFetchData::ROLLCALL);
+ result.day_start = day_start;
+ }
+ }
+
+ NotifyExtensionsDownloadFailed(
+ not_updated, fetch_data->request_ids(),
+ ExtensionDownloaderDelegate::NO_UPDATE_AVAILABLE);
+}
+
+void ExtensionDownloader::DetermineUpdates(
+ const ManifestFetchData& fetch_data,
+ const UpdateManifest::Results& possible_updates,
+ std::vector<int>* result) {
+ for (size_t i = 0; i < possible_updates.list.size(); i++) {
+ const UpdateManifest::Result* update = &possible_updates.list[i];
+ const std::string& id = update->extension_id;
+
+ if (!fetch_data.Includes(id)) {
+ VLOG(2) << "Ignoring " << id << " from this manifest";
+ continue;
+ }
+
+ if (VLOG_IS_ON(2)) {
+ if (update->version.empty())
+ VLOG(2) << "manifest indicates " << id << " has no update";
+ else
+ VLOG(2) << "manifest indicates " << id << " latest version is '"
+ << update->version << "'";
+ }
+
+ if (!delegate_->IsExtensionPending(id)) {
+ // 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.
+ std::string version;
+ if (!delegate_->GetExtensionExistingVersion(id, &version)) {
+ VLOG(2) << id << " is not installed";
+ continue;
+ }
+
+ VLOG(2) << id << " is at '" << version << "'";
+
+ Version existing_version(version);
+ Version update_version(update->version);
+ if (!update_version.IsValid() ||
+ update_version.CompareTo(existing_version) <= 0) {
+ 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 " << id
+ << " available, but requires chrome version "
+ << update->browser_min_version;
+ continue;
+ }
+ VLOG(2) << "will try to update " << id;
+ result->push_back(i);
+ }
+}
+
+// Begins (or queues up) download of an updated extension.
+void ExtensionDownloader::FetchUpdatedExtension(
+ scoped_ptr<ExtensionFetch> fetch_data) {
+ 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;
+ 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) {
+ 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) {
+ extensions_queue_.active_request()->request_ids.insert(
+ fetch_data->request_ids.begin(), fetch_data->request_ids.end());
+ } else {
+ std::string version;
+ if (extension_cache_ &&
+ extension_cache_->GetExtension(fetch_data->id, fetch_data->package_hash,
+ NULL, &version) &&
+ version == fetch_data->version) {
+ base::FilePath crx_path;
+ // Now get .crx file path and mark extension as used.
+ extension_cache_->GetExtension(fetch_data->id, fetch_data->package_hash,
+ &crx_path, &version);
+ NotifyDelegateDownloadFinished(std::move(fetch_data), true, crx_path,
+ false);
+ } else {
+ extensions_queue_.ScheduleRequest(std::move(fetch_data));
+ }
+ }
+}
+
+void ExtensionDownloader::NotifyDelegateDownloadFinished(
+ scoped_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 std::string& id = fetch_data->id;
+ const std::string& package_hash = fetch_data->package_hash;
+ const GURL& url = fetch_data->url;
+ const std::string& version = fetch_data->version;
+ const std::set<int>& request_ids = fetch_data->request_ids;
+ delegate_->OnExtensionDownloadFinished(
+ CRXFileInfo(id, crx_path, package_hash), file_ownership_passed, url,
+ version, ping_results_[id], request_ids,
+ from_cache ? base::Bind(&ExtensionDownloader::CacheInstallDone,
+ weak_ptr_factory_.GetWeakPtr(),
+ base::Passed(&fetch_data))
+ : ExtensionDownloaderDelegate::InstallCallback());
+ if (!from_cache)
+ ping_results_.erase(id);
+}
+
+void ExtensionDownloader::CacheInstallDone(
+ scoped_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::CreateExtensionFetcher() {
+ const ExtensionFetch* fetch = extensions_queue_.active_request();
+ extension_fetcher_ = net::URLFetcher::Create(kExtensionFetcherId, fetch->url,
+ net::URLFetcher::GET, this);
+ extension_fetcher_->SetRequestContext(request_context_.get());
+ extension_fetcher_->SetAutomaticallyRetryOnNetworkChanges(3);
+
+ int load_flags = net::LOAD_DISABLE_CACHE;
+ bool is_secure = fetch->url.SchemeIsCryptographic();
+ if (fetch->credentials != ExtensionFetch::CREDENTIALS_COOKIES || !is_secure) {
+ load_flags |= net::LOAD_DO_NOT_SEND_COOKIES | net::LOAD_DO_NOT_SAVE_COOKIES;
+ }
+ extension_fetcher_->SetLoadFlags(load_flags);
+
+ // Download CRX files to a temp file. The blacklist is small and will be
+ // processed in memory, so it is fetched into a string.
+ if (fetch->id != kBlacklistAppID) {
+ extension_fetcher_->SaveResponseToTemporaryFile(
+ BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE));
+ }
+
+ if (fetch->credentials == ExtensionFetch::CREDENTIALS_OAUTH2_TOKEN &&
+ is_secure) {
+ if (access_token_.empty()) {
+ // We should try OAuth2, but we have no token cached. This
+ // ExtensionFetcher will be started once the token fetch is complete,
+ // in either OnTokenFetchSuccess or OnTokenFetchFailure.
+ DCHECK(identity_provider_.get());
+ OAuth2TokenService::ScopeSet webstore_scopes;
+ webstore_scopes.insert(kWebstoreOAuth2Scope);
+ access_token_request_ =
+ identity_provider_->GetTokenService()->StartRequest(
+ identity_provider_->GetActiveAccountId(), webstore_scopes, this);
+ return;
+ }
+ extension_fetcher_->AddExtraRequestHeader(
+ base::StringPrintf("%s: Bearer %s",
+ net::HttpRequestHeaders::kAuthorization,
+ access_token_.c_str()));
+ }
+
+ VLOG(2) << "Starting fetch of " << fetch->url << " for " << fetch->id;
+ extension_fetcher_->Start();
+}
+
+void ExtensionDownloader::OnCRXFetchComplete(
+ const net::URLFetcher* source,
+ const GURL& url,
+ const net::URLRequestStatus& status,
+ int response_code,
+ const base::TimeDelta& backoff_delay) {
+ ExtensionFetch& active_request = *extensions_queue_.active_request();
+ const std::string& id = active_request.id;
+ if (status.status() == net::URLRequestStatus::SUCCESS &&
+ (response_code == 200 || url.SchemeIsFile())) {
+ RETRY_HISTOGRAM("CrxFetchSuccess",
+ extensions_queue_.active_request_failure_count(),
+ url);
+ base::FilePath crx_path;
+ // Take ownership of the file at |crx_path|.
+ CHECK(source->GetResponseAsFilePath(true, &crx_path));
+ scoped_ptr<ExtensionFetch> fetch_data =
+ extensions_queue_.reset_active_request();
+ if (extension_cache_) {
+ const std::string& version = fetch_data->version;
+ const std::string& expected_hash = fetch_data->package_hash;
+ extension_cache_->PutExtension(
+ id, expected_hash, crx_path, version,
+ base::Bind(&ExtensionDownloader::NotifyDelegateDownloadFinished,
+ weak_ptr_factory_.GetWeakPtr(), base::Passed(&fetch_data),
+ false));
+ } else {
+ NotifyDelegateDownloadFinished(std::move(fetch_data), false, crx_path,
+ true);
+ }
+ } else if (IterateFetchCredentialsAfterFailure(
+ &active_request, status, response_code)) {
+ extensions_queue_.RetryRequest(backoff_delay);
+ } 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(status, response_code) &&
+ extensions_queue_.active_request_failure_count() < kMaxRetries) {
+ extensions_queue_.RetryRequest(backoff_delay);
+ } else {
+ RETRY_HISTOGRAM("CrxFetchFailure",
+ extensions_queue_.active_request_failure_count(),
+ url);
+ // status.error() is 0 (net::OK) or negative. (See net/base/net_errors.h)
+ UMA_HISTOGRAM_SPARSE_SLOWLY("Extensions.CrxFetchError", -status.error());
+ delegate_->OnExtensionDownloadFailed(
+ id, ExtensionDownloaderDelegate::CRX_FETCH_FAILED, ping, request_ids);
+ }
+ ping_results_.erase(id);
+ extensions_queue_.reset_active_request();
+ }
+
+ extension_fetcher_.reset();
+
+ // If there are any pending downloads left, start the next one.
+ extensions_queue_.StartNextRequest();
+}
+
+void ExtensionDownloader::NotifyExtensionsDownloadFailed(
+ const std::set<std::string>& extension_ids,
+ const std::set<int>& request_ids,
+ ExtensionDownloaderDelegate::Error error) {
+ for (std::set<std::string>::const_iterator it = extension_ids.begin();
+ it != extension_ids.end();
+ ++it) {
+ const ExtensionDownloaderDelegate::PingResult& ping = ping_results_[*it];
+ delegate_->OnExtensionDownloadFailed(*it, error, ping, request_ids);
+ ping_results_.erase(*it);
+ }
+}
+
+void ExtensionDownloader::NotifyUpdateFound(const std::string& id,
+ const std::string& version) {
+ UpdateDetails updateInfo(id, Version(version));
+ content::NotificationService::current()->Notify(
+ extensions::NOTIFICATION_EXTENSION_UPDATE_FOUND,
+ content::NotificationService::AllBrowserContextsAndSources(),
+ content::Details<UpdateDetails>(&updateInfo));
+}
+
+bool ExtensionDownloader::IterateFetchCredentialsAfterFailure(
+ ExtensionFetch* fetch,
+ const net::URLRequestStatus& status,
+ int response_code) {
+ bool auth_failure = status.status() == net::URLRequestStatus::CANCELED ||
+ (status.status() == net::URLRequestStatus::SUCCESS &&
+ (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_provider_) {
+ 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_provider_.get());
+ OAuth2TokenService::ScopeSet webstore_scopes;
+ webstore_scopes.insert(kWebstoreOAuth2Scope);
+ identity_provider_->GetTokenService()->InvalidateAccessToken(
+ identity_provider_->GetActiveAccountId(), 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::OnGetTokenSuccess(
+ const OAuth2TokenService::Request* request,
+ const std::string& access_token,
+ const base::Time& expiration_time) {
+ access_token_ = access_token;
+ extension_fetcher_->AddExtraRequestHeader(
+ base::StringPrintf("%s: Bearer %s",
+ net::HttpRequestHeaders::kAuthorization,
+ access_token_.c_str()));
+ extension_fetcher_->Start();
+}
+
+void ExtensionDownloader::OnGetTokenFailure(
+ const OAuth2TokenService::Request* request,
+ const GoogleServiceAuthError& error) {
+ // If we fail to get an access token, kick the pending fetch and let it fall
+ // back on cookies.
+ extension_fetcher_->Start();
+}
+
+ManifestFetchData* ExtensionDownloader::CreateManifestFetchData(
+ const GURL& update_url,
+ int request_id) {
+ 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);
+}
+
+} // namespace extensions