1326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek// Use of this source code is governed by a BSD-style license that can be 3326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek// found in the LICENSE file. 4326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek 5326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek#include "chrome/browser/extensions/updater/extension_downloader.h" 6326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek 7326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek#include <utility> 8326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek 9326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek#include "base/bind.h" 10ba243b59a1074e0962f6abfa3bb9aa984eac1245David Blaikie#include "base/command_line.h" 11ba243b59a1074e0962f6abfa3bb9aa984eac1245David Blaikie#include "base/files/file_path.h" 12326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek#include "base/location.h" 13326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek#include "base/logging.h" 14326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek#include "base/metrics/histogram.h" 15326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek#include "base/metrics/sparse_histogram.h" 16326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek#include "base/stl_util.h" 17326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek#include "base/strings/string_number_conversions.h" 18326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek#include "base/strings/string_util.h" 19892697dd2287caf7c29aaaa82909b0e90b8b63feTed Kremenek#include "base/strings/stringprintf.h" 20b8ad5ee345fa1fdd1fa9253f2d01f69becc88a04Ted Kremenek#include "base/time/time.h" 2130a2e16f6c27f888dd11eba6bbbae1e980078fcbChandler Carruth#include "base/version.h" 22326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek#include "chrome/browser/chrome_notification_types.h" 2330a2e16f6c27f888dd11eba6bbbae1e980078fcbChandler Carruth#include "chrome/browser/extensions/updater/extension_cache.h" 2430a2e16f6c27f888dd11eba6bbbae1e980078fcbChandler Carruth#include "chrome/browser/extensions/updater/request_queue_impl.h" 25326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek#include "chrome/common/chrome_switches.h" 26326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek#include "chrome/common/chrome_version_info.h" 27326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek#include "chrome/common/extensions/manifest_url_handler.h" 28326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek#include "content/public/browser/browser_thread.h" 29326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek#include "content/public/browser/notification_details.h" 30326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek#include "content/public/browser/notification_service.h" 31326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek#include "extensions/browser/updater/safe_manifest_parser.h" 32af13d5b25b360e698cc1cf1055ad7d14e008e505Ted Kremenek#include "extensions/common/extension_urls.h" 33283a358aecb75e30fcd486f2206f6c03c5e7f11dTed Kremenek#include "google_apis/gaia/identity_provider.h" 34326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek#include "net/base/backoff_entry.h" 35a5937bbfd19e61d651a58b0f0ffeef68457902a5Ted Kremenek#include "net/base/load_flags.h" 36326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek#include "net/base/net_errors.h" 37db34ab70961ca4b24b600eb47053d7af304659f5Tom Care#include "net/http/http_request_headers.h" 38326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek#include "net/http/http_status_code.h" 39326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek#include "net/url_request/url_fetcher.h" 40326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek#include "net/url_request/url_request_context_getter.h" 417fa9b4f258636d89342eda28f21a986c8ac353b1Ted Kremenek#include "net/url_request/url_request_status.h" 421d26f48dc2eea1c07431ca1519d7034a21b9bcffTed Kremenek 43ba243b59a1074e0962f6abfa3bb9aa984eac1245David Blaikieusing base::Time; 44b1b5daf30d2597e066936772bd206500232d7d65Ted Kremenekusing base::TimeDelta; 45c6238d2786cfd961b94580b3d3675a1b3ff0721cZhongxing Xuusing content::BrowserThread; 46c6238d2786cfd961b94580b3d3675a1b3ff0721cZhongxing Xu 47a5937bbfd19e61d651a58b0f0ffeef68457902a5Ted Kremeneknamespace extensions { 481d26f48dc2eea1c07431ca1519d7034a21b9bcffTed Kremenek 49a5937bbfd19e61d651a58b0f0ffeef68457902a5Ted Kremenekconst char ExtensionDownloader::kBlacklistAppID[] = "com.google.crx.blacklist"; 50a5937bbfd19e61d651a58b0f0ffeef68457902a5Ted Kremenek 51a5937bbfd19e61d651a58b0f0ffeef68457902a5Ted Kremeneknamespace { 52a5937bbfd19e61d651a58b0f0ffeef68457902a5Ted Kremenek 53a5937bbfd19e61d651a58b0f0ffeef68457902a5Ted Kremenekconst net::BackoffEntry::Policy kDefaultBackoffPolicy = { 54ba243b59a1074e0962f6abfa3bb9aa984eac1245David Blaikie // Number of initial errors (in sequence) to ignore before applying 55a5937bbfd19e61d651a58b0f0ffeef68457902a5Ted Kremenek // exponential back-off rules. 56a5937bbfd19e61d651a58b0f0ffeef68457902a5Ted Kremenek 0, 57a5937bbfd19e61d651a58b0f0ffeef68457902a5Ted Kremenek 58a5937bbfd19e61d651a58b0f0ffeef68457902a5Ted Kremenek // Initial delay for exponential back-off in ms. 59a5937bbfd19e61d651a58b0f0ffeef68457902a5Ted Kremenek 2000, 60a5937bbfd19e61d651a58b0f0ffeef68457902a5Ted Kremenek 61a5937bbfd19e61d651a58b0f0ffeef68457902a5Ted Kremenek // Factor by which the waiting time will be multiplied. 621d26f48dc2eea1c07431ca1519d7034a21b9bcffTed Kremenek 2, 63a5937bbfd19e61d651a58b0f0ffeef68457902a5Ted Kremenek 641d26f48dc2eea1c07431ca1519d7034a21b9bcffTed Kremenek // Fuzzing percentage. ex: 10% will spread requests randomly 65a5937bbfd19e61d651a58b0f0ffeef68457902a5Ted Kremenek // between 90%-100% of the calculated time. 66ba243b59a1074e0962f6abfa3bb9aa984eac1245David Blaikie 0.1, 67ba243b59a1074e0962f6abfa3bb9aa984eac1245David Blaikie 68ba243b59a1074e0962f6abfa3bb9aa984eac1245David Blaikie // Maximum amount of time we are willing to delay our request in ms. 69ba243b59a1074e0962f6abfa3bb9aa984eac1245David Blaikie -1, 701d26f48dc2eea1c07431ca1519d7034a21b9bcffTed Kremenek 71ba243b59a1074e0962f6abfa3bb9aa984eac1245David Blaikie // Time to keep an entry from being discarded even when it 72ba243b59a1074e0962f6abfa3bb9aa984eac1245David Blaikie // has no significant state, -1 to never discard. 731d26f48dc2eea1c07431ca1519d7034a21b9bcffTed Kremenek -1, 74ba243b59a1074e0962f6abfa3bb9aa984eac1245David Blaikie 75cca300a91966df70c9c320e477a3c26ba622673dTed Kremenek // Don't use initial delay unless the last request was an error. 76326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek false, 776f42b62b6194f53bcbc349f5d17388e1936535d7Dylan Noblesmith}; 786f42b62b6194f53bcbc349f5d17388e1936535d7Dylan Noblesmith 79b8ad5ee345fa1fdd1fa9253f2d01f69becc88a04Ted Kremenekconst char kAuthUserQueryKey[] = "authuser"; 80b8ad5ee345fa1fdd1fa9253f2d01f69becc88a04Ted Kremenek 81b8ad5ee345fa1fdd1fa9253f2d01f69becc88a04Ted Kremenekconst int kMaxAuthUserValue = 10; 82ba243b59a1074e0962f6abfa3bb9aa984eac1245David Blaikieconst int kMaxOAuth2Attempts = 3; 83ad5a894df1841698c824381b414630799adc26caTed Kremenek 846f42b62b6194f53bcbc349f5d17388e1936535d7Dylan Noblesmithconst char kNotFromWebstoreInstallSource[] = "notfromwebstore"; 856f42b62b6194f53bcbc349f5d17388e1936535d7Dylan Noblesmithconst char kDefaultInstallSource[] = ""; 866f42b62b6194f53bcbc349f5d17388e1936535d7Dylan Noblesmith 87b8ad5ee345fa1fdd1fa9253f2d01f69becc88a04Ted Kremenekconst char kGoogleDotCom[] = "google.com"; 88326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenekconst char kTokenServiceConsumerId[] = "extension_downloader"; 89b8ad5ee345fa1fdd1fa9253f2d01f69becc88a04Ted Kremenekconst char kWebstoreOAuth2Scope[] = 90b8ad5ee345fa1fdd1fa9253f2d01f69becc88a04Ted Kremenek "https://www.googleapis.com/auth/chromewebstore.readonly"; 91b8ad5ee345fa1fdd1fa9253f2d01f69becc88a04Ted Kremenek 92a5937bbfd19e61d651a58b0f0ffeef68457902a5Ted Kremenek#define RETRY_HISTOGRAM(name, retry_count, url) \ 93a5937bbfd19e61d651a58b0f0ffeef68457902a5Ted Kremenek if ((url).DomainIs(kGoogleDotCom)) { \ 94326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek UMA_HISTOGRAM_CUSTOM_COUNTS("Extensions." name "RetryCountGoogleUrl", \ 951d26f48dc2eea1c07431ca1519d7034a21b9bcffTed Kremenek retry_count, \ 96d200187bd27f9ad68699693a6e57f9ee3ff260faJordy Rose 1, \ 97bc5cb8a5fe2b88f917d47ceb58b53696a121e57eTed Kremenek kMaxRetries, \ 981d26f48dc2eea1c07431ca1519d7034a21b9bcffTed Kremenek kMaxRetries + 1); \ 99b1b5daf30d2597e066936772bd206500232d7d65Ted Kremenek } else { \ 100b1b5daf30d2597e066936772bd206500232d7d65Ted Kremenek UMA_HISTOGRAM_CUSTOM_COUNTS("Extensions." name "RetryCountOtherUrl", \ 101326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek retry_count, \ 1021d26f48dc2eea1c07431ca1519d7034a21b9bcffTed Kremenek 1, \ 103326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek kMaxRetries, \ 104445895a97ae3f1d7bad3480839d31ed3ebcc9c83Ted Kremenek kMaxRetries + 1); \ 105c6238d2786cfd961b94580b3d3675a1b3ff0721cZhongxing Xu } 106c6238d2786cfd961b94580b3d3675a1b3ff0721cZhongxing Xu 107ddc0c4814788dda4ef224cd4d22d07154a6ede49Ted Kremenekbool ShouldRetryRequest(const net::URLRequestStatus& status, 108ddc0c4814788dda4ef224cd4d22d07154a6ede49Ted Kremenek int response_code) { 109ddc0c4814788dda4ef224cd4d22d07154a6ede49Ted Kremenek // Retry if the response code is a server error, or the request failed because 110ddc0c4814788dda4ef224cd4d22d07154a6ede49Ted Kremenek // of network errors as opposed to file errors. 111ddc0c4814788dda4ef224cd4d22d07154a6ede49Ted Kremenek return ((response_code >= 500 && status.is_success()) || 112ddc0c4814788dda4ef224cd4d22d07154a6ede49Ted Kremenek status.status() == net::URLRequestStatus::FAILED); 11374fb1a493cf5d2dd0fb51a4eadf74e85e10a3457Ted Kremenek} 11474fb1a493cf5d2dd0fb51a4eadf74e85e10a3457Ted Kremenek 11574fb1a493cf5d2dd0fb51a4eadf74e85e10a3457Ted Kremenek// This parses and updates a URL query such that the value of the |authuser| 11674fb1a493cf5d2dd0fb51a4eadf74e85e10a3457Ted Kremenek// query parameter is incremented by 1. If parameter was not present in the URL, 11774fb1a493cf5d2dd0fb51a4eadf74e85e10a3457Ted Kremenek// it will be added with a value of 1. All other query keys and values are 11874fb1a493cf5d2dd0fb51a4eadf74e85e10a3457Ted Kremenek// preserved as-is. Returns |false| if the user index exceeds a hard-coded 11974fb1a493cf5d2dd0fb51a4eadf74e85e10a3457Ted Kremenek// maximum. 12074fb1a493cf5d2dd0fb51a4eadf74e85e10a3457Ted Kremenekbool IncrementAuthUserIndex(GURL* url) { 121ba243b59a1074e0962f6abfa3bb9aa984eac1245David Blaikie int user_index = 0; 122f3477c13eeaf11b32a41f181398fb5deffd0dd73Sylvestre Ledru std::string old_query = url->query(); 123326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek std::vector<std::string> new_query_parts; 124326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek url::Component query(0, old_query.length()); 125326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek url::Component key, value; 126ba243b59a1074e0962f6abfa3bb9aa984eac1245David Blaikie while (url::ExtractQueryKeyValue(old_query.c_str(), &query, &key, &value)) { 127b8ad5ee345fa1fdd1fa9253f2d01f69becc88a04Ted Kremenek std::string key_string = old_query.substr(key.begin, key.len); 128bc5cb8a5fe2b88f917d47ceb58b53696a121e57eTed Kremenek std::string value_string = old_query.substr(value.begin, value.len); 129b8ad5ee345fa1fdd1fa9253f2d01f69becc88a04Ted Kremenek if (key_string == kAuthUserQueryKey) { 130b8ad5ee345fa1fdd1fa9253f2d01f69becc88a04Ted Kremenek base::StringToInt(value_string, &user_index); 131b8ad5ee345fa1fdd1fa9253f2d01f69becc88a04Ted Kremenek } else { 1329b823e8e1ccb8a2cb49923bad22a80ca96f41f92Ted Kremenek new_query_parts.push_back(base::StringPrintf( 1330d28d360b5559abda755e50b855ba5e59727d9cdTed Kremenek "%s=%s", key_string.c_str(), value_string.c_str())); 1340d28d360b5559abda755e50b855ba5e59727d9cdTed Kremenek } 135ba243b59a1074e0962f6abfa3bb9aa984eac1245David Blaikie } 136a2d7e6511a8767dc67381c210601b895a8ebae39Anna Zaks if (user_index >= kMaxAuthUserValue) 137326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek return false; 138ba243b59a1074e0962f6abfa3bb9aa984eac1245David Blaikie new_query_parts.push_back( 139283a358aecb75e30fcd486f2206f6c03c5e7f11dTed Kremenek base::StringPrintf("%s=%d", kAuthUserQueryKey, user_index + 1)); 14004eeba43040969c05cfcb563195ef5b199297b62Anders Carlsson std::string new_query_string = JoinString(new_query_parts, '&'); 141af13d5b25b360e698cc1cf1055ad7d14e008e505Ted Kremenek url::Component new_query(0, new_query_string.size()); 142ba243b59a1074e0962f6abfa3bb9aa984eac1245David Blaikie url::Replacements<char> replacements; 143ad5a894df1841698c824381b414630799adc26caTed Kremenek replacements.SetQuery(new_query_string.c_str(), new_query); 144ad5a894df1841698c824381b414630799adc26caTed Kremenek *url = url->ReplaceComponents(replacements); 145ad5a894df1841698c824381b414630799adc26caTed Kremenek return true; 146682060c5d95f6e4f79536013781ab0870cdd3850Ted Kremenek} 14704eeba43040969c05cfcb563195ef5b199297b62Anders Carlsson 1485d98994c7749312a43ce6adf45537979a98e7afdChandler Carruth} // namespace 1495d98994c7749312a43ce6adf45537979a98e7afdChandler Carruth 1505d98994c7749312a43ce6adf45537979a98e7afdChandler CarruthUpdateDetails::UpdateDetails(const std::string& id, const Version& version) 1515d98994c7749312a43ce6adf45537979a98e7afdChandler Carruth : id(id), version(version) {} 1525d98994c7749312a43ce6adf45537979a98e7afdChandler Carruth 153326be568e2cb04285c84e6e26a3e6b3822607361Ted KremenekUpdateDetails::~UpdateDetails() {} 154db34ab70961ca4b24b600eb47053d7af304659f5Tom Care 155326be568e2cb04285c84e6e26a3e6b3822607361Ted KremenekExtensionDownloader::ExtensionFetch::ExtensionFetch() 156326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek : url(), credentials(CREDENTIALS_NONE) { 157326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek} 158326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek 159326be568e2cb04285c84e6e26a3e6b3822607361Ted KremenekExtensionDownloader::ExtensionFetch::ExtensionFetch( 160d064fdc4b7b64ca55b40b70490c79d6f569df78eTed Kremenek const std::string& id, 161326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek const GURL& url, 1621d26f48dc2eea1c07431ca1519d7034a21b9bcffTed Kremenek const std::string& package_hash, 163326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek const std::string& version, 164ba243b59a1074e0962f6abfa3bb9aa984eac1245David Blaikie const std::set<int>& request_ids) 165b1b5daf30d2597e066936772bd206500232d7d65Ted Kremenek : id(id), 166b1b5daf30d2597e066936772bd206500232d7d65Ted Kremenek url(url), 167b1b5daf30d2597e066936772bd206500232d7d65Ted Kremenek package_hash(package_hash), 168ba243b59a1074e0962f6abfa3bb9aa984eac1245David Blaikie version(version), 1697fa9b4f258636d89342eda28f21a986c8ac353b1Ted Kremenek request_ids(request_ids), 1707fa9b4f258636d89342eda28f21a986c8ac353b1Ted Kremenek credentials(CREDENTIALS_NONE), 1717fa9b4f258636d89342eda28f21a986c8ac353b1Ted Kremenek oauth2_attempt_count(0) { 1727fa9b4f258636d89342eda28f21a986c8ac353b1Ted Kremenek} 1737fa9b4f258636d89342eda28f21a986c8ac353b1Ted Kremenek 174ba243b59a1074e0962f6abfa3bb9aa984eac1245David BlaikieExtensionDownloader::ExtensionFetch::~ExtensionFetch() {} 175a5937bbfd19e61d651a58b0f0ffeef68457902a5Ted Kremenek 176a5937bbfd19e61d651a58b0f0ffeef68457902a5Ted KremenekExtensionDownloader::ExtensionDownloader( 177a5937bbfd19e61d651a58b0f0ffeef68457902a5Ted Kremenek ExtensionDownloaderDelegate* delegate, 178a5937bbfd19e61d651a58b0f0ffeef68457902a5Ted Kremenek net::URLRequestContextGetter* request_context) 179a5937bbfd19e61d651a58b0f0ffeef68457902a5Ted Kremenek : OAuth2TokenService::Consumer(kTokenServiceConsumerId), 180a5937bbfd19e61d651a58b0f0ffeef68457902a5Ted Kremenek delegate_(delegate), 181a5937bbfd19e61d651a58b0f0ffeef68457902a5Ted Kremenek request_context_(request_context), 182a5937bbfd19e61d651a58b0f0ffeef68457902a5Ted Kremenek manifests_queue_(&kDefaultBackoffPolicy, 183a5937bbfd19e61d651a58b0f0ffeef68457902a5Ted Kremenek base::Bind(&ExtensionDownloader::CreateManifestFetcher, 184a5937bbfd19e61d651a58b0f0ffeef68457902a5Ted Kremenek base::Unretained(this))), 185a5937bbfd19e61d651a58b0f0ffeef68457902a5Ted Kremenek extensions_queue_(&kDefaultBackoffPolicy, 186a5937bbfd19e61d651a58b0f0ffeef68457902a5Ted Kremenek base::Bind(&ExtensionDownloader::CreateExtensionFetcher, 187a5937bbfd19e61d651a58b0f0ffeef68457902a5Ted Kremenek base::Unretained(this))), 188ba243b59a1074e0962f6abfa3bb9aa984eac1245David Blaikie extension_cache_(NULL), 189b1b5daf30d2597e066936772bd206500232d7d65Ted Kremenek enable_extra_update_metrics_(false), 190326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek weak_ptr_factory_(this) { 191326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek DCHECK(delegate_); 192326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek DCHECK(request_context_.get()); 193326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek} 194326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek 195326be568e2cb04285c84e6e26a3e6b3822607361Ted KremenekExtensionDownloader::~ExtensionDownloader() {} 196326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek 197326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenekbool ExtensionDownloader::AddExtension(const Extension& extension, 198a02d893f15d4663bdba3bd92ade10070bf0510e4Zhongxing Xu int request_id) { 199ba243b59a1074e0962f6abfa3bb9aa984eac1245David Blaikie // Skip extensions with empty update URLs converted from user 200ba243b59a1074e0962f6abfa3bb9aa984eac1245David Blaikie // scripts. 2011d26f48dc2eea1c07431ca1519d7034a21b9bcffTed Kremenek if (extension.converted_from_user_script() && 202a02d893f15d4663bdba3bd92ade10070bf0510e4Zhongxing Xu ManifestURL::GetUpdateURL(&extension).is_empty()) { 203326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek return false; 204326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek } 205326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek 2061d26f48dc2eea1c07431ca1519d7034a21b9bcffTed Kremenek // If the extension updates itself from the gallery, ignore any update URL 207326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek // data. At the moment there is no extra data that an extension can 208326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek // communicate to the the gallery update servers. 209326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek std::string update_url_data; 210326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek if (!ManifestURL::UpdatesFromGallery(&extension)) 211326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek update_url_data = delegate_->GetUpdateUrlData(extension.id()); 212d064fdc4b7b64ca55b40b70490c79d6f569df78eTed Kremenek 213326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek std::string install_source; 214326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek bool force_update = delegate_->ShouldForceUpdate(extension.id(), 2151d26f48dc2eea1c07431ca1519d7034a21b9bcffTed Kremenek &install_source); 216326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek return AddExtensionData(extension.id(), 217326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek *extension.version(), 218326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek extension.GetType(), 2198ddf7cead8a67342a4584a203e0bf736b7efedbeZhongxing Xu ManifestURL::GetUpdateURL(&extension), 2208ddf7cead8a67342a4584a203e0bf736b7efedbeZhongxing Xu update_url_data, 2211d26f48dc2eea1c07431ca1519d7034a21b9bcffTed Kremenek request_id, 222326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek force_update, 2231d26f48dc2eea1c07431ca1519d7034a21b9bcffTed Kremenek install_source); 224326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek} 225a5937bbfd19e61d651a58b0f0ffeef68457902a5Ted Kremenek 226a5937bbfd19e61d651a58b0f0ffeef68457902a5Ted Kremenekbool ExtensionDownloader::AddPendingExtension(const std::string& id, 2271d26f48dc2eea1c07431ca1519d7034a21b9bcffTed Kremenek const GURL& update_url, 228326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek int request_id) { 229326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek // Use a zero version to ensure that a pending extension will always 230d064fdc4b7b64ca55b40b70490c79d6f569df78eTed Kremenek // be updated, and thus installed (assuming all extensions have 2311d26f48dc2eea1c07431ca1519d7034a21b9bcffTed Kremenek // non-zero versions). 232326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek Version version("0.0.0.0"); 233326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek DCHECK(version.IsValid()); 234326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek 235326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek return AddExtensionData(id, 236326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek version, 237d064fdc4b7b64ca55b40b70490c79d6f569df78eTed Kremenek Manifest::TYPE_UNKNOWN, 238326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek update_url, 239326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek std::string(), 240fadcd5d5bbe1bfc1c6b8d819cc2242f780a49fecAnna Zaks request_id, 241fadcd5d5bbe1bfc1c6b8d819cc2242f780a49fecAnna Zaks false, 242fadcd5d5bbe1bfc1c6b8d819cc2242f780a49fecAnna Zaks std::string()); 243326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek} 244326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek 245326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenekvoid ExtensionDownloader::StartAllPending(ExtensionCache* cache) { 246326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek if (cache) { 247326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek extension_cache_ = cache; 2481d26f48dc2eea1c07431ca1519d7034a21b9bcffTed Kremenek extension_cache_->Start(base::Bind( 249326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek &ExtensionDownloader::DoStartAllPending, 2509c378f705405d37f49795d5e915989de774fe11fTed Kremenek weak_ptr_factory_.GetWeakPtr())); 251326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek } else { 252326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek DoStartAllPending(); 253326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek } 25426c9cb55cb96643c0759c08d037c16c309864087Zhongxing Xu} 255892697dd2287caf7c29aaaa82909b0e90b8b63feTed Kremenek 256326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenekvoid ExtensionDownloader::DoStartAllPending() { 257326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek ReportStats(); 258326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek url_stats_ = URLStats(); 259326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek 260326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek for (FetchMap::iterator it = fetches_preparing_.begin(); 261326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek it != fetches_preparing_.end(); ++it) { 262326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek std::vector<linked_ptr<ManifestFetchData> >& list = it->second; 263326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek for (size_t i = 0; i < list.size(); ++i) { 2641d26f48dc2eea1c07431ca1519d7034a21b9bcffTed Kremenek StartUpdateCheck(scoped_ptr<ManifestFetchData>(list[i].release())); 265ba243b59a1074e0962f6abfa3bb9aa984eac1245David Blaikie } 266d706434b0231c76fd9acf30060646a7aa8f69aefZhongxing Xu } 267892697dd2287caf7c29aaaa82909b0e90b8b63feTed Kremenek fetches_preparing_.clear(); 268d706434b0231c76fd9acf30060646a7aa8f69aefZhongxing Xu} 269326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek 270326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenekvoid ExtensionDownloader::StartBlacklistUpdate( 271326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek const std::string& version, 272326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek const ManifestFetchData::PingData& ping_data, 273892697dd2287caf7c29aaaa82909b0e90b8b63feTed Kremenek int request_id) { 274d706434b0231c76fd9acf30060646a7aa8f69aefZhongxing Xu // Note: it is very important that we use the https version of the update 275326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek // url here to avoid DNS hijacking of the blacklist, which is not validated 276326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek // by a public key signature like .crx files are. 277fadcd5d5bbe1bfc1c6b8d819cc2242f780a49fecAnna Zaks scoped_ptr<ManifestFetchData> blacklist_fetch(CreateManifestFetchData( 278fadcd5d5bbe1bfc1c6b8d819cc2242f780a49fecAnna Zaks extension_urls::GetWebstoreUpdateUrl(), request_id)); 279fadcd5d5bbe1bfc1c6b8d819cc2242f780a49fecAnna Zaks DCHECK(blacklist_fetch->base_url().SchemeIsSecure()); 280326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek blacklist_fetch->AddExtension(kBlacklistAppID, 281326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek version, 282326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek &ping_data, 283d064fdc4b7b64ca55b40b70490c79d6f569df78eTed Kremenek std::string(), 2841d26f48dc2eea1c07431ca1519d7034a21b9bcffTed Kremenek kDefaultInstallSource, 285d064fdc4b7b64ca55b40b70490c79d6f569df78eTed Kremenek false); 286892697dd2287caf7c29aaaa82909b0e90b8b63feTed Kremenek StartUpdateCheck(blacklist_fetch.Pass()); 287326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek} 288326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek 289326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenekvoid ExtensionDownloader::SetWebstoreIdentityProvider( 290326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek scoped_ptr<IdentityProvider> identity_provider) { 291326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek identity_provider_.swap(identity_provider); 2929c378f705405d37f49795d5e915989de774fe11fTed Kremenek} 293326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek 294326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenekbool ExtensionDownloader::AddExtensionData( 295326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek const std::string& id, 296326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek const Version& version, 297326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek Manifest::Type extension_type, 298326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek const GURL& extension_update_url, 299d064fdc4b7b64ca55b40b70490c79d6f569df78eTed Kremenek const std::string& update_url_data, 300326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek int request_id, 3011d26f48dc2eea1c07431ca1519d7034a21b9bcffTed Kremenek bool force_update, 302326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek const std::string& install_source_override) { 303326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek GURL update_url(extension_update_url); 304326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek // Skip extensions with non-empty invalid update URLs. 305326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek if (!update_url.is_empty() && !update_url.is_valid()) { 306326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek LOG(WARNING) << "Extension " << id << " has invalid update url " 307326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek << update_url; 308326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek return false; 309326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek } 3101d26f48dc2eea1c07431ca1519d7034a21b9bcffTed Kremenek 311326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek // Make sure we use SSL for store-hosted extensions. 312326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek if (extension_urls::IsWebstoreUpdateUrl(update_url) && 313326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek !update_url.SchemeIsSecure()) 314326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek update_url = extension_urls::GetWebstoreUpdateUrl(); 3159c378f705405d37f49795d5e915989de774fe11fTed Kremenek 316326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek // Skip extensions with empty IDs. 317326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek if (id.empty()) { 318326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek LOG(WARNING) << "Found extension with empty ID"; 319326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek return false; 320326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek } 321326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek 3227fa9b4f258636d89342eda28f21a986c8ac353b1Ted Kremenek if (update_url.DomainIs(kGoogleDotCom)) { 3237fa9b4f258636d89342eda28f21a986c8ac353b1Ted Kremenek url_stats_.google_url_count++; 3247fa9b4f258636d89342eda28f21a986c8ac353b1Ted Kremenek } else if (update_url.is_empty()) { 325326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek url_stats_.no_url_count++; 326326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek // Fill in default update URL. 327326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek update_url = extension_urls::GetWebstoreUpdateUrl(); 328ba243b59a1074e0962f6abfa3bb9aa984eac1245David Blaikie } else { 329ba243b59a1074e0962f6abfa3bb9aa984eac1245David Blaikie url_stats_.other_url_count++; 3307fa9b4f258636d89342eda28f21a986c8ac353b1Ted Kremenek } 3317fa9b4f258636d89342eda28f21a986c8ac353b1Ted Kremenek 332326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek switch (extension_type) { 333326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek case Manifest::TYPE_THEME: 334326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek ++url_stats_.theme_count; 335326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek break; 336326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek case Manifest::TYPE_EXTENSION: 3377fa9b4f258636d89342eda28f21a986c8ac353b1Ted Kremenek case Manifest::TYPE_USER_SCRIPT: 3387fa9b4f258636d89342eda28f21a986c8ac353b1Ted Kremenek ++url_stats_.extension_count; 339326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek break; 340326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek case Manifest::TYPE_HOSTED_APP: 341326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek case Manifest::TYPE_LEGACY_PACKAGED_APP: 3421d26f48dc2eea1c07431ca1519d7034a21b9bcffTed Kremenek ++url_stats_.app_count; 3437fa9b4f258636d89342eda28f21a986c8ac353b1Ted Kremenek break; 3447fa9b4f258636d89342eda28f21a986c8ac353b1Ted Kremenek case Manifest::TYPE_PLATFORM_APP: 345326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek ++url_stats_.platform_app_count; 3467fa9b4f258636d89342eda28f21a986c8ac353b1Ted Kremenek break; 347326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek case Manifest::TYPE_UNKNOWN: 348d064fdc4b7b64ca55b40b70490c79d6f569df78eTed Kremenek default: 3499c378f705405d37f49795d5e915989de774fe11fTed Kremenek ++url_stats_.pending_count; 350326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek break; 351326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek } 352326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek 353326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek std::vector<GURL> update_urls; 354326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek update_urls.push_back(update_url); 355326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek // If metrics are enabled, also add to ManifestFetchData for the 356326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek // webstore update URL. 357326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek if (!extension_urls::IsWebstoreUpdateUrl(update_url) && 358d064fdc4b7b64ca55b40b70490c79d6f569df78eTed Kremenek enable_extra_update_metrics_) { 3591d26f48dc2eea1c07431ca1519d7034a21b9bcffTed Kremenek update_urls.push_back(extension_urls::GetWebstoreUpdateUrl()); 360326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek } 361892697dd2287caf7c29aaaa82909b0e90b8b63feTed Kremenek 362d706434b0231c76fd9acf30060646a7aa8f69aefZhongxing Xu for (size_t i = 0; i < update_urls.size(); ++i) { 363326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek DCHECK(!update_urls[i].is_empty()); 3641d26f48dc2eea1c07431ca1519d7034a21b9bcffTed Kremenek DCHECK(update_urls[i].is_valid()); 365326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek 366326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek std::string install_source = i == 0 ? 3677fa9b4f258636d89342eda28f21a986c8ac353b1Ted Kremenek kDefaultInstallSource : kNotFromWebstoreInstallSource; 3687fa9b4f258636d89342eda28f21a986c8ac353b1Ted Kremenek if (!install_source_override.empty()) { 3697fa9b4f258636d89342eda28f21a986c8ac353b1Ted Kremenek install_source = install_source_override; 3707fa9b4f258636d89342eda28f21a986c8ac353b1Ted Kremenek } 3717fa9b4f258636d89342eda28f21a986c8ac353b1Ted Kremenek 3727fa9b4f258636d89342eda28f21a986c8ac353b1Ted Kremenek ManifestFetchData::PingData ping_data; 373d064fdc4b7b64ca55b40b70490c79d6f569df78eTed Kremenek ManifestFetchData::PingData* optional_ping_data = NULL; 374326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek if (delegate_->GetPingDataForExtension(id, &ping_data)) 375326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek optional_ping_data = &ping_data; 376326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek 377326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek // Find or create a ManifestFetchData to add this extension to. 3781d26f48dc2eea1c07431ca1519d7034a21b9bcffTed Kremenek bool added = false; 379326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek FetchMap::iterator existing_iter = fetches_preparing_.find( 380326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek std::make_pair(request_id, update_urls[i])); 381326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek if (existing_iter != fetches_preparing_.end() && 382326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek !existing_iter->second.empty()) { 3831d26f48dc2eea1c07431ca1519d7034a21b9bcffTed Kremenek // Try to add to the ManifestFetchData at the end of the list. 3841d26f48dc2eea1c07431ca1519d7034a21b9bcffTed Kremenek ManifestFetchData* existing_fetch = existing_iter->second.back().get(); 385ba243b59a1074e0962f6abfa3bb9aa984eac1245David Blaikie if (existing_fetch->AddExtension(id, version.GetString(), 386b1b5daf30d2597e066936772bd206500232d7d65Ted Kremenek optional_ping_data, update_url_data, 387b1b5daf30d2597e066936772bd206500232d7d65Ted Kremenek install_source, 388b1b5daf30d2597e066936772bd206500232d7d65Ted Kremenek force_update)) { 389a43df9539644bf1c258e12710cd69d79b0b078cdTed Kremenek added = true; 390a43df9539644bf1c258e12710cd69d79b0b078cdTed Kremenek } 391a43df9539644bf1c258e12710cd69d79b0b078cdTed Kremenek } 392a43df9539644bf1c258e12710cd69d79b0b078cdTed Kremenek if (!added) { 393ba243b59a1074e0962f6abfa3bb9aa984eac1245David Blaikie // Otherwise add a new element to the list, if the list doesn't exist or 394b1b5daf30d2597e066936772bd206500232d7d65Ted Kremenek // if its last element is already full. 3951d26f48dc2eea1c07431ca1519d7034a21b9bcffTed Kremenek linked_ptr<ManifestFetchData> fetch( 3965a1ffe98b04120846a15f7105905b5f363b08635Jordan Rose CreateManifestFetchData(update_urls[i], request_id)); 3975a1ffe98b04120846a15f7105905b5f363b08635Jordan Rose fetches_preparing_[std::make_pair(request_id, update_urls[i])]. 398a43df9539644bf1c258e12710cd69d79b0b078cdTed Kremenek push_back(fetch); 399a43df9539644bf1c258e12710cd69d79b0b078cdTed Kremenek added = fetch->AddExtension(id, version.GetString(), 400ba243b59a1074e0962f6abfa3bb9aa984eac1245David Blaikie optional_ping_data, 4011d26f48dc2eea1c07431ca1519d7034a21b9bcffTed Kremenek update_url_data, 402ba243b59a1074e0962f6abfa3bb9aa984eac1245David Blaikie install_source, 403d200187bd27f9ad68699693a6e57f9ee3ff260faJordy Rose force_update); 404ba243b59a1074e0962f6abfa3bb9aa984eac1245David Blaikie DCHECK(added); 405b1b5daf30d2597e066936772bd206500232d7d65Ted Kremenek } 406b1b5daf30d2597e066936772bd206500232d7d65Ted Kremenek } 407b1b5daf30d2597e066936772bd206500232d7d65Ted Kremenek 408ba243b59a1074e0962f6abfa3bb9aa984eac1245David Blaikie return true; 409b1b5daf30d2597e066936772bd206500232d7d65Ted Kremenek} 410b1b5daf30d2597e066936772bd206500232d7d65Ted Kremenek 411b1b5daf30d2597e066936772bd206500232d7d65Ted Kremenekvoid ExtensionDownloader::ReportStats() const { 412a43df9539644bf1c258e12710cd69d79b0b078cdTed Kremenek UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckExtension", 413a43df9539644bf1c258e12710cd69d79b0b078cdTed Kremenek url_stats_.extension_count); 414a43df9539644bf1c258e12710cd69d79b0b078cdTed Kremenek UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckTheme", 415a43df9539644bf1c258e12710cd69d79b0b078cdTed Kremenek url_stats_.theme_count); 416ba243b59a1074e0962f6abfa3bb9aa984eac1245David Blaikie UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckApp", 4171d26f48dc2eea1c07431ca1519d7034a21b9bcffTed Kremenek url_stats_.app_count); 418b1b5daf30d2597e066936772bd206500232d7d65Ted Kremenek UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckPackagedApp", 419b1b5daf30d2597e066936772bd206500232d7d65Ted Kremenek url_stats_.platform_app_count); 420b1b5daf30d2597e066936772bd206500232d7d65Ted Kremenek UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckPending", 421b1b5daf30d2597e066936772bd206500232d7d65Ted Kremenek url_stats_.pending_count); 422b1b5daf30d2597e066936772bd206500232d7d65Ted Kremenek UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckGoogleUrl", 423b1b5daf30d2597e066936772bd206500232d7d65Ted Kremenek url_stats_.google_url_count); 424ba243b59a1074e0962f6abfa3bb9aa984eac1245David Blaikie UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckOtherUrl", 425b1b5daf30d2597e066936772bd206500232d7d65Ted Kremenek url_stats_.other_url_count); 426d200187bd27f9ad68699693a6e57f9ee3ff260faJordy Rose UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckNoUrl", 427d200187bd27f9ad68699693a6e57f9ee3ff260faJordy Rose url_stats_.no_url_count); 428b1b5daf30d2597e066936772bd206500232d7d65Ted Kremenek} 429ba243b59a1074e0962f6abfa3bb9aa984eac1245David Blaikie 430b1b5daf30d2597e066936772bd206500232d7d65Ted Kremenekvoid ExtensionDownloader::StartUpdateCheck( 431ba243b59a1074e0962f6abfa3bb9aa984eac1245David Blaikie scoped_ptr<ManifestFetchData> fetch_data) { 432b1b5daf30d2597e066936772bd206500232d7d65Ted Kremenek const std::set<std::string>& id_set(fetch_data->extension_ids()); 433b1b5daf30d2597e066936772bd206500232d7d65Ted Kremenek 434b1b5daf30d2597e066936772bd206500232d7d65Ted Kremenek if (CommandLine::ForCurrentProcess()->HasSwitch( 435b1b5daf30d2597e066936772bd206500232d7d65Ted Kremenek switches::kDisableBackgroundNetworking)) { 436b1b5daf30d2597e066936772bd206500232d7d65Ted Kremenek NotifyExtensionsDownloadFailed(id_set, 437b1b5daf30d2597e066936772bd206500232d7d65Ted Kremenek fetch_data->request_ids(), 438b1b5daf30d2597e066936772bd206500232d7d65Ted Kremenek ExtensionDownloaderDelegate::DISABLED); 4391d26f48dc2eea1c07431ca1519d7034a21b9bcffTed Kremenek return; 440b1b5daf30d2597e066936772bd206500232d7d65Ted Kremenek } 441b1b5daf30d2597e066936772bd206500232d7d65Ted Kremenek 442b1b5daf30d2597e066936772bd206500232d7d65Ted Kremenek RequestQueue<ManifestFetchData>::iterator i; 4431d26f48dc2eea1c07431ca1519d7034a21b9bcffTed Kremenek for (i = manifests_queue_.begin(); i != manifests_queue_.end(); ++i) { 444b1b5daf30d2597e066936772bd206500232d7d65Ted Kremenek if (fetch_data->full_url() == i->full_url()) { 445b1b5daf30d2597e066936772bd206500232d7d65Ted Kremenek // This url is already scheduled to be fetched. 446b1b5daf30d2597e066936772bd206500232d7d65Ted Kremenek i->Merge(*fetch_data); 447b1b5daf30d2597e066936772bd206500232d7d65Ted Kremenek return; 448b1b5daf30d2597e066936772bd206500232d7d65Ted Kremenek } 449b1b5daf30d2597e066936772bd206500232d7d65Ted Kremenek } 450326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek 451326be568e2cb04285c84e6e26a3e6b3822607361Ted Kremenek if (manifests_queue_.active_request() && 452 manifests_queue_.active_request()->full_url() == fetch_data->full_url()) { 453 manifests_queue_.active_request()->Merge(*fetch_data); 454 } else { 455 UMA_HISTOGRAM_COUNTS("Extensions.UpdateCheckUrlLength", 456 fetch_data->full_url().possibly_invalid_spec().length()); 457 458 manifests_queue_.ScheduleRequest(fetch_data.Pass()); 459 } 460} 461 462void ExtensionDownloader::CreateManifestFetcher() { 463 if (VLOG_IS_ON(2)) { 464 std::vector<std::string> id_vector( 465 manifests_queue_.active_request()->extension_ids().begin(), 466 manifests_queue_.active_request()->extension_ids().end()); 467 std::string id_list = JoinString(id_vector, ','); 468 VLOG(2) << "Fetching " << manifests_queue_.active_request()->full_url() 469 << " for " << id_list; 470 } 471 472 manifest_fetcher_.reset(net::URLFetcher::Create( 473 kManifestFetcherId, manifests_queue_.active_request()->full_url(), 474 net::URLFetcher::GET, this)); 475 manifest_fetcher_->SetRequestContext(request_context_.get()); 476 manifest_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES | 477 net::LOAD_DO_NOT_SAVE_COOKIES | 478 net::LOAD_DISABLE_CACHE); 479 // Update checks can be interrupted if a network change is detected; this is 480 // common for the retail mode AppPack on ChromeOS. Retrying once should be 481 // enough to recover in those cases; let the fetcher retry up to 3 times 482 // just in case. http://crosbug.com/130602 483 manifest_fetcher_->SetAutomaticallyRetryOnNetworkChanges(3); 484 manifest_fetcher_->Start(); 485} 486 487void ExtensionDownloader::OnURLFetchComplete( 488 const net::URLFetcher* source) { 489 VLOG(2) << source->GetResponseCode() << " " << source->GetURL(); 490 491 if (source == manifest_fetcher_.get()) { 492 std::string data; 493 source->GetResponseAsString(&data); 494 OnManifestFetchComplete(source->GetURL(), 495 source->GetStatus(), 496 source->GetResponseCode(), 497 source->GetBackoffDelay(), 498 data); 499 } else if (source == extension_fetcher_.get()) { 500 OnCRXFetchComplete(source, 501 source->GetURL(), 502 source->GetStatus(), 503 source->GetResponseCode(), 504 source->GetBackoffDelay()); 505 } else { 506 NOTREACHED(); 507 } 508} 509 510void ExtensionDownloader::OnManifestFetchComplete( 511 const GURL& url, 512 const net::URLRequestStatus& status, 513 int response_code, 514 const base::TimeDelta& backoff_delay, 515 const std::string& data) { 516 // We want to try parsing the manifest, and if it indicates updates are 517 // available, we want to fire off requests to fetch those updates. 518 if (status.status() == net::URLRequestStatus::SUCCESS && 519 (response_code == 200 || (url.SchemeIsFile() && data.length() > 0))) { 520 RETRY_HISTOGRAM("ManifestFetchSuccess", 521 manifests_queue_.active_request_failure_count(), url); 522 VLOG(2) << "beginning manifest parse for " << url; 523 scoped_refptr<SafeManifestParser> safe_parser( 524 new SafeManifestParser( 525 data, 526 manifests_queue_.reset_active_request().release(), 527 base::Bind(&ExtensionDownloader::HandleManifestResults, 528 weak_ptr_factory_.GetWeakPtr()))); 529 safe_parser->Start(); 530 } else { 531 VLOG(1) << "Failed to fetch manifest '" << url.possibly_invalid_spec() 532 << "' response code:" << response_code; 533 if (ShouldRetryRequest(status, response_code) && 534 manifests_queue_.active_request_failure_count() < kMaxRetries) { 535 manifests_queue_.RetryRequest(backoff_delay); 536 } else { 537 RETRY_HISTOGRAM("ManifestFetchFailure", 538 manifests_queue_.active_request_failure_count(), url); 539 NotifyExtensionsDownloadFailed( 540 manifests_queue_.active_request()->extension_ids(), 541 manifests_queue_.active_request()->request_ids(), 542 ExtensionDownloaderDelegate::MANIFEST_FETCH_FAILED); 543 } 544 } 545 manifest_fetcher_.reset(); 546 manifests_queue_.reset_active_request(); 547 548 // If we have any pending manifest requests, fire off the next one. 549 manifests_queue_.StartNextRequest(); 550} 551 552void ExtensionDownloader::HandleManifestResults( 553 const ManifestFetchData& fetch_data, 554 const UpdateManifest::Results* results) { 555 // Keep a list of extensions that will not be updated, so that the |delegate_| 556 // can be notified once we're done here. 557 std::set<std::string> not_updated(fetch_data.extension_ids()); 558 559 if (!results) { 560 NotifyExtensionsDownloadFailed( 561 not_updated, 562 fetch_data.request_ids(), 563 ExtensionDownloaderDelegate::MANIFEST_INVALID); 564 return; 565 } 566 567 // Examine the parsed manifest and kick off fetches of any new crx files. 568 std::vector<int> updates; 569 DetermineUpdates(fetch_data, *results, &updates); 570 for (size_t i = 0; i < updates.size(); i++) { 571 const UpdateManifest::Result* update = &(results->list.at(updates[i])); 572 const std::string& id = update->extension_id; 573 not_updated.erase(id); 574 575 GURL crx_url = update->crx_url; 576 if (id != kBlacklistAppID) { 577 NotifyUpdateFound(update->extension_id, update->version); 578 } else { 579 // The URL of the blacklist file is returned by the server and we need to 580 // be sure that we continue to be able to reliably detect whether a URL 581 // references a blacklist file. 582 DCHECK(extension_urls::IsBlacklistUpdateUrl(crx_url)) << crx_url; 583 584 // Force https (crbug.com/129587). 585 if (!crx_url.SchemeIsSecure()) { 586 url::Replacements<char> replacements; 587 std::string scheme("https"); 588 replacements.SetScheme(scheme.c_str(), 589 url::Component(0, scheme.size())); 590 crx_url = crx_url.ReplaceComponents(replacements); 591 } 592 } 593 scoped_ptr<ExtensionFetch> fetch(new ExtensionFetch( 594 update->extension_id, crx_url, update->package_hash, 595 update->version, fetch_data.request_ids())); 596 FetchUpdatedExtension(fetch.Pass()); 597 } 598 599 // If the manifest response included a <daystart> element, we want to save 600 // that value for any extensions which had sent a ping in the request. 601 if (fetch_data.base_url().DomainIs(kGoogleDotCom) && 602 results->daystart_elapsed_seconds >= 0) { 603 Time day_start = 604 Time::Now() - TimeDelta::FromSeconds(results->daystart_elapsed_seconds); 605 606 const std::set<std::string>& extension_ids = fetch_data.extension_ids(); 607 std::set<std::string>::const_iterator i; 608 for (i = extension_ids.begin(); i != extension_ids.end(); i++) { 609 const std::string& id = *i; 610 ExtensionDownloaderDelegate::PingResult& result = ping_results_[id]; 611 result.did_ping = fetch_data.DidPing(id, ManifestFetchData::ROLLCALL); 612 result.day_start = day_start; 613 } 614 } 615 616 NotifyExtensionsDownloadFailed( 617 not_updated, 618 fetch_data.request_ids(), 619 ExtensionDownloaderDelegate::NO_UPDATE_AVAILABLE); 620} 621 622void ExtensionDownloader::DetermineUpdates( 623 const ManifestFetchData& fetch_data, 624 const UpdateManifest::Results& possible_updates, 625 std::vector<int>* result) { 626 // This will only be valid if one of possible_updates specifies 627 // browser_min_version. 628 Version browser_version; 629 630 for (size_t i = 0; i < possible_updates.list.size(); i++) { 631 const UpdateManifest::Result* update = &possible_updates.list[i]; 632 const std::string& id = update->extension_id; 633 634 if (!fetch_data.Includes(id)) { 635 VLOG(2) << "Ignoring " << id << " from this manifest"; 636 continue; 637 } 638 639 if (VLOG_IS_ON(2)) { 640 if (update->version.empty()) 641 VLOG(2) << "manifest indicates " << id << " has no update"; 642 else 643 VLOG(2) << "manifest indicates " << id 644 << " latest version is '" << update->version << "'"; 645 } 646 647 if (!delegate_->IsExtensionPending(id)) { 648 // If we're not installing pending extension, and the update 649 // version is the same or older than what's already installed, 650 // we don't want it. 651 std::string version; 652 if (!delegate_->GetExtensionExistingVersion(id, &version)) { 653 VLOG(2) << id << " is not installed"; 654 continue; 655 } 656 657 VLOG(2) << id << " is at '" << version << "'"; 658 659 // We should skip the version check if update was forced. 660 if (!fetch_data.DidForceUpdate(id)) { 661 Version existing_version(version); 662 Version update_version(update->version); 663 if (!update_version.IsValid() || 664 update_version.CompareTo(existing_version) <= 0) { 665 continue; 666 } 667 } 668 } 669 670 // If the update specifies a browser minimum version, do we qualify? 671 if (update->browser_min_version.length() > 0) { 672 // First determine the browser version if we haven't already. 673 if (!browser_version.IsValid()) { 674 chrome::VersionInfo version_info; 675 if (version_info.is_valid()) 676 browser_version = Version(version_info.Version()); 677 } 678 Version browser_min_version(update->browser_min_version); 679 if (browser_version.IsValid() && browser_min_version.IsValid() && 680 browser_min_version.CompareTo(browser_version) > 0) { 681 // TODO(asargent) - We may want this to show up in the extensions UI 682 // eventually. (http://crbug.com/12547). 683 LOG(WARNING) << "Updated version of extension " << id 684 << " available, but requires chrome version " 685 << update->browser_min_version; 686 continue; 687 } 688 } 689 VLOG(2) << "will try to update " << id; 690 result->push_back(i); 691 } 692} 693 694 // Begins (or queues up) download of an updated extension. 695void ExtensionDownloader::FetchUpdatedExtension( 696 scoped_ptr<ExtensionFetch> fetch_data) { 697 if (!fetch_data->url.is_valid()) { 698 // TODO(asargent): This can sometimes be invalid. See crbug.com/130881. 699 LOG(ERROR) << "Invalid URL: '" << fetch_data->url.possibly_invalid_spec() 700 << "' for extension " << fetch_data->id; 701 return; 702 } 703 704 for (RequestQueue<ExtensionFetch>::iterator iter = 705 extensions_queue_.begin(); 706 iter != extensions_queue_.end(); ++iter) { 707 if (iter->id == fetch_data->id || iter->url == fetch_data->url) { 708 iter->request_ids.insert(fetch_data->request_ids.begin(), 709 fetch_data->request_ids.end()); 710 return; // already scheduled 711 } 712 } 713 714 if (extensions_queue_.active_request() && 715 extensions_queue_.active_request()->url == fetch_data->url) { 716 extensions_queue_.active_request()->request_ids.insert( 717 fetch_data->request_ids.begin(), fetch_data->request_ids.end()); 718 } else { 719 std::string version; 720 if (extension_cache_ && 721 extension_cache_->GetExtension(fetch_data->id, NULL, &version) && 722 version == fetch_data->version) { 723 base::FilePath crx_path; 724 // Now get .crx file path and mark extension as used. 725 extension_cache_->GetExtension(fetch_data->id, &crx_path, &version); 726 NotifyDelegateDownloadFinished(fetch_data.Pass(), crx_path, false); 727 } else { 728 extensions_queue_.ScheduleRequest(fetch_data.Pass()); 729 } 730 } 731} 732 733void ExtensionDownloader::NotifyDelegateDownloadFinished( 734 scoped_ptr<ExtensionFetch> fetch_data, 735 const base::FilePath& crx_path, 736 bool file_ownership_passed) { 737 delegate_->OnExtensionDownloadFinished(fetch_data->id, crx_path, 738 file_ownership_passed, fetch_data->url, fetch_data->version, 739 ping_results_[fetch_data->id], fetch_data->request_ids); 740 ping_results_.erase(fetch_data->id); 741} 742 743void ExtensionDownloader::CreateExtensionFetcher() { 744 const ExtensionFetch* fetch = extensions_queue_.active_request(); 745 extension_fetcher_.reset(net::URLFetcher::Create( 746 kExtensionFetcherId, fetch->url, net::URLFetcher::GET, this)); 747 extension_fetcher_->SetRequestContext(request_context_.get()); 748 extension_fetcher_->SetAutomaticallyRetryOnNetworkChanges(3); 749 750 int load_flags = net::LOAD_DISABLE_CACHE; 751 bool is_secure = fetch->url.SchemeIsSecure(); 752 if (fetch->credentials != ExtensionFetch::CREDENTIALS_COOKIES || !is_secure) { 753 load_flags |= net::LOAD_DO_NOT_SEND_COOKIES | 754 net::LOAD_DO_NOT_SAVE_COOKIES; 755 } 756 extension_fetcher_->SetLoadFlags(load_flags); 757 758 // Download CRX files to a temp file. The blacklist is small and will be 759 // processed in memory, so it is fetched into a string. 760 if (fetch->id != kBlacklistAppID) { 761 extension_fetcher_->SaveResponseToTemporaryFile( 762 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE)); 763 } 764 765 if (fetch->credentials == ExtensionFetch::CREDENTIALS_OAUTH2_TOKEN && 766 is_secure) { 767 if (access_token_.empty()) { 768 // We should try OAuth2, but we have no token cached. This 769 // ExtensionFetcher will be started once the token fetch is complete, 770 // in either OnTokenFetchSuccess or OnTokenFetchFailure. 771 DCHECK(identity_provider_.get()); 772 OAuth2TokenService::ScopeSet webstore_scopes; 773 webstore_scopes.insert(kWebstoreOAuth2Scope); 774 access_token_request_ = 775 identity_provider_->GetTokenService()->StartRequest( 776 identity_provider_->GetActiveAccountId(), 777 webstore_scopes, 778 this); 779 return; 780 } 781 extension_fetcher_->AddExtraRequestHeader( 782 base::StringPrintf("%s: Bearer %s", 783 net::HttpRequestHeaders::kAuthorization, 784 access_token_.c_str())); 785 } 786 787 VLOG(2) << "Starting fetch of " << fetch->url << " for " << fetch->id; 788 extension_fetcher_->Start(); 789} 790 791void ExtensionDownloader::OnCRXFetchComplete( 792 const net::URLFetcher* source, 793 const GURL& url, 794 const net::URLRequestStatus& status, 795 int response_code, 796 const base::TimeDelta& backoff_delay) { 797 ExtensionFetch& active_request = *extensions_queue_.active_request(); 798 const std::string& id = active_request.id; 799 if (status.status() == net::URLRequestStatus::SUCCESS && 800 (response_code == 200 || url.SchemeIsFile())) { 801 RETRY_HISTOGRAM("CrxFetchSuccess", 802 extensions_queue_.active_request_failure_count(), url); 803 base::FilePath crx_path; 804 // Take ownership of the file at |crx_path|. 805 CHECK(source->GetResponseAsFilePath(true, &crx_path)); 806 scoped_ptr<ExtensionFetch> fetch_data = 807 extensions_queue_.reset_active_request(); 808 if (extension_cache_) { 809 const std::string& version = fetch_data->version; 810 extension_cache_->PutExtension(id, crx_path, version, 811 base::Bind(&ExtensionDownloader::NotifyDelegateDownloadFinished, 812 weak_ptr_factory_.GetWeakPtr(), 813 base::Passed(&fetch_data))); 814 } else { 815 NotifyDelegateDownloadFinished(fetch_data.Pass(), crx_path, true); 816 } 817 } else if (IterateFetchCredentialsAfterFailure( 818 &active_request, 819 status, 820 response_code)) { 821 extensions_queue_.RetryRequest(backoff_delay); 822 } else { 823 const std::set<int>& request_ids = active_request.request_ids; 824 const ExtensionDownloaderDelegate::PingResult& ping = ping_results_[id]; 825 VLOG(1) << "Failed to fetch extension '" << url.possibly_invalid_spec() 826 << "' response code:" << response_code; 827 if (ShouldRetryRequest(status, response_code) && 828 extensions_queue_.active_request_failure_count() < kMaxRetries) { 829 extensions_queue_.RetryRequest(backoff_delay); 830 } else { 831 RETRY_HISTOGRAM("CrxFetchFailure", 832 extensions_queue_.active_request_failure_count(), url); 833 // status.error() is 0 (net::OK) or negative. (See net/base/net_errors.h) 834 UMA_HISTOGRAM_SPARSE_SLOWLY("Extensions.CrxFetchError", -status.error()); 835 delegate_->OnExtensionDownloadFailed( 836 id, ExtensionDownloaderDelegate::CRX_FETCH_FAILED, ping, request_ids); 837 } 838 ping_results_.erase(id); 839 extensions_queue_.reset_active_request(); 840 } 841 842 extension_fetcher_.reset(); 843 844 // If there are any pending downloads left, start the next one. 845 extensions_queue_.StartNextRequest(); 846} 847 848void ExtensionDownloader::NotifyExtensionsDownloadFailed( 849 const std::set<std::string>& extension_ids, 850 const std::set<int>& request_ids, 851 ExtensionDownloaderDelegate::Error error) { 852 for (std::set<std::string>::const_iterator it = extension_ids.begin(); 853 it != extension_ids.end(); ++it) { 854 const ExtensionDownloaderDelegate::PingResult& ping = ping_results_[*it]; 855 delegate_->OnExtensionDownloadFailed(*it, error, ping, request_ids); 856 ping_results_.erase(*it); 857 } 858} 859 860void ExtensionDownloader::NotifyUpdateFound(const std::string& id, 861 const std::string& version) { 862 UpdateDetails updateInfo(id, Version(version)); 863 content::NotificationService::current()->Notify( 864 extensions::NOTIFICATION_EXTENSION_UPDATE_FOUND, 865 content::NotificationService::AllBrowserContextsAndSources(), 866 content::Details<UpdateDetails>(&updateInfo)); 867} 868 869bool ExtensionDownloader::IterateFetchCredentialsAfterFailure( 870 ExtensionFetch* fetch, 871 const net::URLRequestStatus& status, 872 int response_code) { 873 bool auth_failure = status.status() == net::URLRequestStatus::CANCELED || 874 (status.status() == net::URLRequestStatus::SUCCESS && 875 (response_code == net::HTTP_UNAUTHORIZED || 876 response_code == net::HTTP_FORBIDDEN)); 877 if (!auth_failure) { 878 return false; 879 } 880 // Here we decide what to do next if the server refused to authorize this 881 // fetch. 882 switch (fetch->credentials) { 883 case ExtensionFetch::CREDENTIALS_NONE: 884 if (fetch->url.DomainIs(kGoogleDotCom) && identity_provider_) { 885 fetch->credentials = ExtensionFetch::CREDENTIALS_OAUTH2_TOKEN; 886 } else { 887 fetch->credentials = ExtensionFetch::CREDENTIALS_COOKIES; 888 } 889 return true; 890 case ExtensionFetch::CREDENTIALS_OAUTH2_TOKEN: 891 fetch->oauth2_attempt_count++; 892 // OAuth2 may fail due to an expired access token, in which case we 893 // should invalidate the token and try again. 894 if (response_code == net::HTTP_UNAUTHORIZED && 895 fetch->oauth2_attempt_count <= kMaxOAuth2Attempts) { 896 DCHECK(identity_provider_.get()); 897 OAuth2TokenService::ScopeSet webstore_scopes; 898 webstore_scopes.insert(kWebstoreOAuth2Scope); 899 identity_provider_->GetTokenService()->InvalidateToken( 900 identity_provider_->GetActiveAccountId(), 901 webstore_scopes, 902 access_token_); 903 access_token_.clear(); 904 return true; 905 } 906 // Either there is no Gaia identity available, the active identity 907 // doesn't have access to this resource, or the server keeps returning 908 // 401s and we've retried too many times. Fall back on cookies. 909 if (access_token_.empty() || 910 response_code == net::HTTP_FORBIDDEN || 911 fetch->oauth2_attempt_count > kMaxOAuth2Attempts) { 912 fetch->credentials = ExtensionFetch::CREDENTIALS_COOKIES; 913 return true; 914 } 915 // Something else is wrong. Time to give up. 916 return false; 917 case ExtensionFetch::CREDENTIALS_COOKIES: 918 if (response_code == net::HTTP_FORBIDDEN) { 919 // Try the next session identity, up to some maximum. 920 return IncrementAuthUserIndex(&fetch->url); 921 } 922 return false; 923 default: 924 NOTREACHED(); 925 } 926 NOTREACHED(); 927 return false; 928} 929 930void ExtensionDownloader::OnGetTokenSuccess( 931 const OAuth2TokenService::Request* request, 932 const std::string& access_token, 933 const base::Time& expiration_time) { 934 access_token_ = access_token; 935 extension_fetcher_->AddExtraRequestHeader( 936 base::StringPrintf("%s: Bearer %s", 937 net::HttpRequestHeaders::kAuthorization, 938 access_token_.c_str())); 939 extension_fetcher_->Start(); 940} 941 942void ExtensionDownloader::OnGetTokenFailure( 943 const OAuth2TokenService::Request* request, 944 const GoogleServiceAuthError& error) { 945 // If we fail to get an access token, kick the pending fetch and let it fall 946 // back on cookies. 947 extension_fetcher_->Start(); 948} 949 950ManifestFetchData* ExtensionDownloader::CreateManifestFetchData( 951 const GURL& update_url, 952 int request_id) { 953 ManifestFetchData::PingMode ping_mode = ManifestFetchData::NO_PING; 954 if (update_url.DomainIs(ping_enabled_domain_.c_str())) { 955 if (enable_extra_update_metrics_) { 956 ping_mode = ManifestFetchData::PING_WITH_METRICS; 957 } else { 958 ping_mode = ManifestFetchData::PING; 959 } 960 } 961 return new ManifestFetchData( 962 update_url, request_id, brand_code_, manifest_query_params_, ping_mode); 963} 964 965} // namespace extensions 966