| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "chrome/browser/extensions/updater/extension_downloader.h" | |
| 6 | |
| 7 #include <utility> | |
| 8 | |
| 9 #include "base/bind.h" | |
| 10 #include "base/command_line.h" | |
| 11 #include "base/files/file_path.h" | |
| 12 #include "base/location.h" | |
| 13 #include "base/logging.h" | |
| 14 #include "base/metrics/histogram.h" | |
| 15 #include "base/metrics/sparse_histogram.h" | |
| 16 #include "base/profiler/scoped_profile.h" | |
| 17 #include "base/stl_util.h" | |
| 18 #include "base/strings/string_number_conversions.h" | |
| 19 #include "base/strings/string_util.h" | |
| 20 #include "base/strings/stringprintf.h" | |
| 21 #include "base/time/time.h" | |
| 22 #include "base/version.h" | |
| 23 #include "content/public/browser/browser_thread.h" | |
| 24 #include "content/public/browser/notification_details.h" | |
| 25 #include "content/public/browser/notification_service.h" | |
| 26 #include "extensions/browser/extensions_browser_client.h" | |
| 27 #include "extensions/browser/notification_types.h" | |
| 28 #include "extensions/browser/updater/extension_cache.h" | |
| 29 #include "extensions/browser/updater/request_queue_impl.h" | |
| 30 #include "extensions/browser/updater/safe_manifest_parser.h" | |
| 31 #include "extensions/common/extension_urls.h" | |
| 32 #include "extensions/common/manifest_url_handlers.h" | |
| 33 #include "google_apis/gaia/identity_provider.h" | |
| 34 #include "net/base/backoff_entry.h" | |
| 35 #include "net/base/load_flags.h" | |
| 36 #include "net/base/net_errors.h" | |
| 37 #include "net/http/http_request_headers.h" | |
| 38 #include "net/http/http_status_code.h" | |
| 39 #include "net/url_request/url_fetcher.h" | |
| 40 #include "net/url_request/url_request_context_getter.h" | |
| 41 #include "net/url_request/url_request_status.h" | |
| 42 | |
| 43 using base::Time; | |
| 44 using base::TimeDelta; | |
| 45 using content::BrowserThread; | |
| 46 | |
| 47 namespace extensions { | |
| 48 | |
| 49 const char ExtensionDownloader::kBlacklistAppID[] = "com.google.crx.blacklist"; | |
| 50 | |
| 51 namespace { | |
| 52 | |
| 53 const net::BackoffEntry::Policy kDefaultBackoffPolicy = { | |
| 54 // Number of initial errors (in sequence) to ignore before applying | |
| 55 // exponential back-off rules. | |
| 56 0, | |
| 57 | |
| 58 // Initial delay for exponential back-off in ms. | |
| 59 2000, | |
| 60 | |
| 61 // Factor by which the waiting time will be multiplied. | |
| 62 2, | |
| 63 | |
| 64 // Fuzzing percentage. ex: 10% will spread requests randomly | |
| 65 // between 90%-100% of the calculated time. | |
| 66 0.1, | |
| 67 | |
| 68 // Maximum amount of time we are willing to delay our request in ms. | |
| 69 -1, | |
| 70 | |
| 71 // Time to keep an entry from being discarded even when it | |
| 72 // has no significant state, -1 to never discard. | |
| 73 -1, | |
| 74 | |
| 75 // Don't use initial delay unless the last request was an error. | |
| 76 false, | |
| 77 }; | |
| 78 | |
| 79 const char kAuthUserQueryKey[] = "authuser"; | |
| 80 | |
| 81 const int kMaxAuthUserValue = 10; | |
| 82 const int kMaxOAuth2Attempts = 3; | |
| 83 | |
| 84 const char kNotFromWebstoreInstallSource[] = "notfromwebstore"; | |
| 85 const char kDefaultInstallSource[] = ""; | |
| 86 | |
| 87 const char kGoogleDotCom[] = "google.com"; | |
| 88 const char kTokenServiceConsumerId[] = "extension_downloader"; | |
| 89 const char kWebstoreOAuth2Scope[] = | |
| 90 "https://www.googleapis.com/auth/chromewebstore.readonly"; | |
| 91 | |
| 92 #define RETRY_HISTOGRAM(name, retry_count, url) \ | |
| 93 if ((url).DomainIs(kGoogleDotCom)) { \ | |
| 94 UMA_HISTOGRAM_CUSTOM_COUNTS("Extensions." name "RetryCountGoogleUrl", \ | |
| 95 retry_count, \ | |
| 96 1, \ | |
| 97 kMaxRetries, \ | |
| 98 kMaxRetries + 1); \ | |
| 99 } else { \ | |
| 100 UMA_HISTOGRAM_CUSTOM_COUNTS("Extensions." name "RetryCountOtherUrl", \ | |
| 101 retry_count, \ | |
| 102 1, \ | |
| 103 kMaxRetries, \ | |
| 104 kMaxRetries + 1); \ | |
| 105 } | |
| 106 | |
| 107 bool ShouldRetryRequest(const net::URLRequestStatus& status, | |
| 108 int response_code) { | |
| 109 // Retry if the response code is a server error, or the request failed because | |
| 110 // of network errors as opposed to file errors. | |
| 111 return ((response_code >= 500 && status.is_success()) || | |
| 112 status.status() == net::URLRequestStatus::FAILED); | |
| 113 } | |
| 114 | |
| 115 // This parses and updates a URL query such that the value of the |authuser| | |
| 116 // query parameter is incremented by 1. If parameter was not present in the URL, | |
| 117 // it will be added with a value of 1. All other query keys and values are | |
| 118 // preserved as-is. Returns |false| if the user index exceeds a hard-coded | |
| 119 // maximum. | |
| 120 bool IncrementAuthUserIndex(GURL* url) { | |
| 121 int user_index = 0; | |
| 122 std::string old_query = url->query(); | |
| 123 std::vector<std::string> new_query_parts; | |
| 124 url::Component query(0, old_query.length()); | |
| 125 url::Component key, value; | |
| 126 while (url::ExtractQueryKeyValue(old_query.c_str(), &query, &key, &value)) { | |
| 127 std::string key_string = old_query.substr(key.begin, key.len); | |
| 128 std::string value_string = old_query.substr(value.begin, value.len); | |
| 129 if (key_string == kAuthUserQueryKey) { | |
| 130 base::StringToInt(value_string, &user_index); | |
| 131 } else { | |
| 132 new_query_parts.push_back(base::StringPrintf( | |
| 133 "%s=%s", key_string.c_str(), value_string.c_str())); | |
| 134 } | |
| 135 } | |
| 136 if (user_index >= kMaxAuthUserValue) | |
| 137 return false; | |
| 138 new_query_parts.push_back( | |
| 139 base::StringPrintf("%s=%d", kAuthUserQueryKey, user_index + 1)); | |
| 140 std::string new_query_string = JoinString(new_query_parts, '&'); | |
| 141 url::Component new_query(0, new_query_string.size()); | |
| 142 url::Replacements<char> replacements; | |
| 143 replacements.SetQuery(new_query_string.c_str(), new_query); | |
| 144 *url = url->ReplaceComponents(replacements); | |
| 145 return true; | |
| 146 } | |
| 147 | |
| 148 } // namespace | |
| 149 | |
| 150 UpdateDetails::UpdateDetails(const std::string& id, const Version& version) | |
| 151 : id(id), version(version) {} | |
| 152 | |
| 153 UpdateDetails::~UpdateDetails() {} | |
| 154 | |
| 155 ExtensionDownloader::ExtensionFetch::ExtensionFetch() | |
| 156 : url(), credentials(CREDENTIALS_NONE) { | |
| 157 } | |
| 158 | |
| 159 ExtensionDownloader::ExtensionFetch::ExtensionFetch( | |
| 160 const std::string& id, | |
| 161 const GURL& url, | |
| 162 const std::string& package_hash, | |
| 163 const std::string& version, | |
| 164 const std::set<int>& request_ids) | |
| 165 : id(id), | |
| 166 url(url), | |
| 167 package_hash(package_hash), | |
| 168 version(version), | |
| 169 request_ids(request_ids), | |
| 170 credentials(CREDENTIALS_NONE), | |
| 171 oauth2_attempt_count(0) { | |
| 172 } | |
| 173 | |
| 174 ExtensionDownloader::ExtensionFetch::~ExtensionFetch() {} | |
| 175 | |
| 176 ExtensionDownloader::ExtensionDownloader( | |
| 177 ExtensionDownloaderDelegate* delegate, | |
| 178 net::URLRequestContextGetter* request_context) | |
| 179 : OAuth2TokenService::Consumer(kTokenServiceConsumerId), | |
| 180 delegate_(delegate), | |
| 181 request_context_(request_context), | |
| 182 manifests_queue_(&kDefaultBackoffPolicy, | |
| 183 base::Bind(&ExtensionDownloader::CreateManifestFetcher, | |
| 184 base::Unretained(this))), | |
| 185 extensions_queue_(&kDefaultBackoffPolicy, | |
| 186 base::Bind(&ExtensionDownloader::CreateExtensionFetcher, | |
| 187 base::Unretained(this))), | |
| 188 extension_cache_(NULL), | |
| 189 enable_extra_update_metrics_(false), | |
| 190 weak_ptr_factory_(this) { | |
| 191 DCHECK(delegate_); | |
| 192 DCHECK(request_context_.get()); | |
| 193 } | |
| 194 | |
| 195 ExtensionDownloader::~ExtensionDownloader() {} | |
| 196 | |
| 197 bool ExtensionDownloader::AddExtension(const Extension& extension, | |
| 198 int request_id) { | |
| 199 // Skip extensions with empty update URLs converted from user | |
| 200 // scripts. | |
| 201 if (extension.converted_from_user_script() && | |
| 202 ManifestURL::GetUpdateURL(&extension).is_empty()) { | |
| 203 return false; | |
| 204 } | |
| 205 | |
| 206 // If the extension updates itself from the gallery, ignore any update URL | |
| 207 // data. At the moment there is no extra data that an extension can | |
| 208 // communicate to the the gallery update servers. | |
| 209 std::string update_url_data; | |
| 210 if (!ManifestURL::UpdatesFromGallery(&extension)) | |
| 211 update_url_data = delegate_->GetUpdateUrlData(extension.id()); | |
| 212 | |
| 213 std::string install_source; | |
| 214 bool force_update = delegate_->ShouldForceUpdate(extension.id(), | |
| 215 &install_source); | |
| 216 return AddExtensionData(extension.id(), | |
| 217 *extension.version(), | |
| 218 extension.GetType(), | |
| 219 ManifestURL::GetUpdateURL(&extension), | |
| 220 update_url_data, | |
| 221 request_id, | |
| 222 force_update, | |
| 223 install_source); | |
| 224 } | |
| 225 | |
| 226 bool ExtensionDownloader::AddPendingExtension(const std::string& id, | |
| 227 const GURL& update_url, | |
| 228 int request_id) { | |
| 229 // Use a zero version to ensure that a pending extension will always | |
| 230 // be updated, and thus installed (assuming all extensions have | |
| 231 // non-zero versions). | |
| 232 Version version("0.0.0.0"); | |
| 233 DCHECK(version.IsValid()); | |
| 234 | |
| 235 return AddExtensionData(id, | |
| 236 version, | |
| 237 Manifest::TYPE_UNKNOWN, | |
| 238 update_url, | |
| 239 std::string(), | |
| 240 request_id, | |
| 241 false, | |
| 242 std::string()); | |
| 243 } | |
| 244 | |
| 245 void ExtensionDownloader::StartAllPending(ExtensionCache* cache) { | |
| 246 if (cache) { | |
| 247 extension_cache_ = cache; | |
| 248 extension_cache_->Start(base::Bind( | |
| 249 &ExtensionDownloader::DoStartAllPending, | |
| 250 weak_ptr_factory_.GetWeakPtr())); | |
| 251 } else { | |
| 252 DoStartAllPending(); | |
| 253 } | |
| 254 } | |
| 255 | |
| 256 void ExtensionDownloader::DoStartAllPending() { | |
| 257 ReportStats(); | |
| 258 url_stats_ = URLStats(); | |
| 259 | |
| 260 for (FetchMap::iterator it = fetches_preparing_.begin(); | |
| 261 it != fetches_preparing_.end(); ++it) { | |
| 262 std::vector<linked_ptr<ManifestFetchData> >& list = it->second; | |
| 263 for (size_t i = 0; i < list.size(); ++i) { | |
| 264 StartUpdateCheck(scoped_ptr<ManifestFetchData>(list[i].release())); | |
| 265 } | |
| 266 } | |
| 267 fetches_preparing_.clear(); | |
| 268 } | |
| 269 | |
| 270 void ExtensionDownloader::StartBlacklistUpdate( | |
| 271 const std::string& version, | |
| 272 const ManifestFetchData::PingData& ping_data, | |
| 273 int request_id) { | |
| 274 // Note: it is very important that we use the https version of the update | |
| 275 // url here to avoid DNS hijacking of the blacklist, which is not validated | |
| 276 // by a public key signature like .crx files are. | |
| 277 scoped_ptr<ManifestFetchData> blacklist_fetch(CreateManifestFetchData( | |
| 278 extension_urls::GetWebstoreUpdateUrl(), request_id)); | |
| 279 DCHECK(blacklist_fetch->base_url().SchemeIsSecure()); | |
| 280 blacklist_fetch->AddExtension(kBlacklistAppID, | |
| 281 version, | |
| 282 &ping_data, | |
| 283 std::string(), | |
| 284 kDefaultInstallSource, | |
| 285 false); | |
| 286 StartUpdateCheck(blacklist_fetch.Pass()); | |
| 287 } | |
| 288 | |
| 289 void ExtensionDownloader::SetWebstoreIdentityProvider( | |
| 290 scoped_ptr<IdentityProvider> identity_provider) { | |
| 291 identity_provider_.swap(identity_provider); | |
| 292 } | |
| 293 | |
| 294 bool ExtensionDownloader::AddExtensionData( | |
| 295 const std::string& id, | |
| 296 const Version& version, | |
| 297 Manifest::Type extension_type, | |
| 298 const GURL& extension_update_url, | |
| 299 const std::string& update_url_data, | |
| 300 int request_id, | |
| 301 bool force_update, | |
| 302 const std::string& install_source_override) { | |
| 303 GURL update_url(extension_update_url); | |
| 304 // Skip extensions with non-empty invalid update URLs. | |
| 305 if (!update_url.is_empty() && !update_url.is_valid()) { | |
| 306 LOG(WARNING) << "Extension " << id << " has invalid update url " | |
| 307 << update_url; | |
| 308 return false; | |
| 309 } | |
| 310 | |
| 311 // Make sure we use SSL for store-hosted extensions. | |
| 312 if (extension_urls::IsWebstoreUpdateUrl(update_url) && | |
| 313 !update_url.SchemeIsSecure()) | |
| 314 update_url = extension_urls::GetWebstoreUpdateUrl(); | |
| 315 | |
| 316 // Skip extensions with empty IDs. | |
| 317 if (id.empty()) { | |
| 318 LOG(WARNING) << "Found extension with empty ID"; | |
| 319 return false; | |
| 320 } | |
| 321 | |
| 322 if (update_url.DomainIs(kGoogleDotCom)) { | |
| 323 url_stats_.google_url_count++; | |
| 324 } else if (update_url.is_empty()) { | |
| 325 url_stats_.no_url_count++; | |
| 326 // Fill in default update URL. | |
| 327 update_url = extension_urls::GetWebstoreUpdateUrl(); | |
| 328 } else { | |
| 329 url_stats_.other_url_count++; | |
| 330 } | |
| 331 | |
| 332 switch (extension_type) { | |
| 333 case Manifest::TYPE_THEME: | |
| 334 ++url_stats_.theme_count; | |
| 335 break; | |
| 336 case Manifest::TYPE_EXTENSION: | |
| 337 case Manifest::TYPE_USER_SCRIPT: | |
| 338 ++url_stats_.extension_count; | |
| 339 break; | |
| 340 case Manifest::TYPE_HOSTED_APP: | |
| 341 case Manifest::TYPE_LEGACY_PACKAGED_APP: | |
| 342 ++url_stats_.app_count; | |
| 343 break; | |
| 344 case Manifest::TYPE_PLATFORM_APP: | |
| 345 ++url_stats_.platform_app_count; | |
| 346 break; | |
| 347 case Manifest::TYPE_UNKNOWN: | |
| 348 default: | |
| 349 ++url_stats_.pending_count; | |
| 350 break; | |
| 351 } | |
| 352 | |
| 353 std::vector<GURL> update_urls; | |
| 354 update_urls.push_back(update_url); | |
| 355 // If metrics are enabled, also add to ManifestFetchData for the | |
| 356 // webstore update URL. | |
| 357 if (!extension_urls::IsWebstoreUpdateUrl(update_url) && | |
| 358 enable_extra_update_metrics_) { | |
| 359 update_urls.push_back(extension_urls::GetWebstoreUpdateUrl()); | |
| 360 } | |
| 361 | |
| 362 for (size_t i = 0; i < update_urls.size(); ++i) { | |
| 363 DCHECK(!update_urls[i].is_empty()); | |
| 364 DCHECK(update_urls[i].is_valid()); | |
| 365 | |
| 366 std::string install_source = i == 0 ? | |
| 367 kDefaultInstallSource : kNotFromWebstoreInstallSource; | |
| 368 if (!install_source_override.empty()) { | |
| 369 install_source = install_source_override; | |
| 370 } | |
| 371 | |
| 372 ManifestFetchData::PingData ping_data; | |
| 373 ManifestFetchData::PingData* optional_ping_data = NULL; | |
| 374 if (delegate_->GetPingDataForExtension(id, &ping_data)) | |
| 375 optional_ping_data = &ping_data; | |
| 376 | |
| 377 // Find or create a ManifestFetchData to add this extension to. | |
| 378 bool added = false; | |
| 379 FetchMap::iterator existing_iter = fetches_preparing_.find( | |
| 380 std::make_pair(request_id, update_urls[i])); | |
| 381 if (existing_iter != fetches_preparing_.end() && | |
| 382 !existing_iter->second.empty()) { | |
| 383 // Try to add to the ManifestFetchData at the end of the list. | |
| 384 ManifestFetchData* existing_fetch = existing_iter->second.back().get(); | |
| 385 if (existing_fetch->AddExtension(id, version.GetString(), | |
| 386 optional_ping_data, update_url_data, | |
| 387 install_source, | |
| 388 force_update)) { | |
| 389 added = true; | |
| 390 } | |
| 391 } | |
| 392 if (!added) { | |
| 393 // Otherwise add a new element to the list, if the list doesn't exist or | |
| 394 // if its last element is already full. | |
| 395 linked_ptr<ManifestFetchData> fetch( | |
| 396 CreateManifestFetchData(update_urls[i], request_id)); | |
| 397 fetches_preparing_[std::make_pair(request_id, update_urls[i])]. | |
| 398 push_back(fetch); | |
| 399 added = fetch->AddExtension(id, version.GetString(), | |
| 400 optional_ping_data, | |
| 401 update_url_data, | |
| 402 install_source, | |
| 403 force_update); | |
| 404 DCHECK(added); | |
| 405 } | |
| 406 } | |
| 407 | |
| 408 return true; | |
| 409 } | |
| 410 | |
| 411 void ExtensionDownloader::ReportStats() const { | |
| 412 UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckExtension", | |
| 413 url_stats_.extension_count); | |
| 414 UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckTheme", | |
| 415 url_stats_.theme_count); | |
| 416 UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckApp", | |
| 417 url_stats_.app_count); | |
| 418 UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckPackagedApp", | |
| 419 url_stats_.platform_app_count); | |
| 420 UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckPending", | |
| 421 url_stats_.pending_count); | |
| 422 UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckGoogleUrl", | |
| 423 url_stats_.google_url_count); | |
| 424 UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckOtherUrl", | |
| 425 url_stats_.other_url_count); | |
| 426 UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckNoUrl", | |
| 427 url_stats_.no_url_count); | |
| 428 } | |
| 429 | |
| 430 void ExtensionDownloader::StartUpdateCheck( | |
| 431 scoped_ptr<ManifestFetchData> fetch_data) { | |
| 432 const std::set<std::string>& id_set(fetch_data->extension_ids()); | |
| 433 | |
| 434 if (!ExtensionsBrowserClient::Get()->IsBackgroundUpdateAllowed()) { | |
| 435 NotifyExtensionsDownloadFailed(id_set, | |
| 436 fetch_data->request_ids(), | |
| 437 ExtensionDownloaderDelegate::DISABLED); | |
| 438 } | |
| 439 | |
| 440 RequestQueue<ManifestFetchData>::iterator i; | |
| 441 for (i = manifests_queue_.begin(); i != manifests_queue_.end(); ++i) { | |
| 442 if (fetch_data->full_url() == i->full_url()) { | |
| 443 // This url is already scheduled to be fetched. | |
| 444 i->Merge(*fetch_data); | |
| 445 return; | |
| 446 } | |
| 447 } | |
| 448 | |
| 449 if (manifests_queue_.active_request() && | |
| 450 manifests_queue_.active_request()->full_url() == fetch_data->full_url()) { | |
| 451 manifests_queue_.active_request()->Merge(*fetch_data); | |
| 452 } else { | |
| 453 UMA_HISTOGRAM_COUNTS("Extensions.UpdateCheckUrlLength", | |
| 454 fetch_data->full_url().possibly_invalid_spec().length()); | |
| 455 | |
| 456 manifests_queue_.ScheduleRequest(fetch_data.Pass()); | |
| 457 } | |
| 458 } | |
| 459 | |
| 460 void ExtensionDownloader::CreateManifestFetcher() { | |
| 461 if (VLOG_IS_ON(2)) { | |
| 462 std::vector<std::string> id_vector( | |
| 463 manifests_queue_.active_request()->extension_ids().begin(), | |
| 464 manifests_queue_.active_request()->extension_ids().end()); | |
| 465 std::string id_list = JoinString(id_vector, ','); | |
| 466 VLOG(2) << "Fetching " << manifests_queue_.active_request()->full_url() | |
| 467 << " for " << id_list; | |
| 468 } | |
| 469 | |
| 470 manifest_fetcher_.reset(net::URLFetcher::Create( | |
| 471 kManifestFetcherId, manifests_queue_.active_request()->full_url(), | |
| 472 net::URLFetcher::GET, this)); | |
| 473 manifest_fetcher_->SetRequestContext(request_context_.get()); | |
| 474 manifest_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES | | |
| 475 net::LOAD_DO_NOT_SAVE_COOKIES | | |
| 476 net::LOAD_DISABLE_CACHE); | |
| 477 // Update checks can be interrupted if a network change is detected; this is | |
| 478 // common for the retail mode AppPack on ChromeOS. Retrying once should be | |
| 479 // enough to recover in those cases; let the fetcher retry up to 3 times | |
| 480 // just in case. http://crosbug.com/130602 | |
| 481 manifest_fetcher_->SetAutomaticallyRetryOnNetworkChanges(3); | |
| 482 manifest_fetcher_->Start(); | |
| 483 } | |
| 484 | |
| 485 void ExtensionDownloader::OnURLFetchComplete( | |
| 486 const net::URLFetcher* source) { | |
| 487 // TODO(vadimt): Remove ScopedProfile below once crbug.com/422577 is fixed. | |
| 488 tracked_objects::ScopedProfile tracking_profile( | |
| 489 FROM_HERE_WITH_EXPLICIT_FUNCTION( | |
| 490 "422577 ExtensionDownloader::OnURLFetchComplete")); | |
| 491 | |
| 492 VLOG(2) << source->GetResponseCode() << " " << source->GetURL(); | |
| 493 | |
| 494 if (source == manifest_fetcher_.get()) { | |
| 495 std::string data; | |
| 496 source->GetResponseAsString(&data); | |
| 497 OnManifestFetchComplete(source->GetURL(), | |
| 498 source->GetStatus(), | |
| 499 source->GetResponseCode(), | |
| 500 source->GetBackoffDelay(), | |
| 501 data); | |
| 502 } else if (source == extension_fetcher_.get()) { | |
| 503 OnCRXFetchComplete(source, | |
| 504 source->GetURL(), | |
| 505 source->GetStatus(), | |
| 506 source->GetResponseCode(), | |
| 507 source->GetBackoffDelay()); | |
| 508 } else { | |
| 509 NOTREACHED(); | |
| 510 } | |
| 511 } | |
| 512 | |
| 513 void ExtensionDownloader::OnManifestFetchComplete( | |
| 514 const GURL& url, | |
| 515 const net::URLRequestStatus& status, | |
| 516 int response_code, | |
| 517 const base::TimeDelta& backoff_delay, | |
| 518 const std::string& data) { | |
| 519 // We want to try parsing the manifest, and if it indicates updates are | |
| 520 // available, we want to fire off requests to fetch those updates. | |
| 521 if (status.status() == net::URLRequestStatus::SUCCESS && | |
| 522 (response_code == 200 || (url.SchemeIsFile() && data.length() > 0))) { | |
| 523 RETRY_HISTOGRAM("ManifestFetchSuccess", | |
| 524 manifests_queue_.active_request_failure_count(), url); | |
| 525 VLOG(2) << "beginning manifest parse for " << url; | |
| 526 scoped_refptr<SafeManifestParser> safe_parser( | |
| 527 new SafeManifestParser( | |
| 528 data, | |
| 529 manifests_queue_.reset_active_request().release(), | |
| 530 base::Bind(&ExtensionDownloader::HandleManifestResults, | |
| 531 weak_ptr_factory_.GetWeakPtr()))); | |
| 532 safe_parser->Start(); | |
| 533 } else { | |
| 534 VLOG(1) << "Failed to fetch manifest '" << url.possibly_invalid_spec() | |
| 535 << "' response code:" << response_code; | |
| 536 if (ShouldRetryRequest(status, response_code) && | |
| 537 manifests_queue_.active_request_failure_count() < kMaxRetries) { | |
| 538 manifests_queue_.RetryRequest(backoff_delay); | |
| 539 } else { | |
| 540 RETRY_HISTOGRAM("ManifestFetchFailure", | |
| 541 manifests_queue_.active_request_failure_count(), url); | |
| 542 NotifyExtensionsDownloadFailed( | |
| 543 manifests_queue_.active_request()->extension_ids(), | |
| 544 manifests_queue_.active_request()->request_ids(), | |
| 545 ExtensionDownloaderDelegate::MANIFEST_FETCH_FAILED); | |
| 546 } | |
| 547 } | |
| 548 manifest_fetcher_.reset(); | |
| 549 manifests_queue_.reset_active_request(); | |
| 550 | |
| 551 // If we have any pending manifest requests, fire off the next one. | |
| 552 manifests_queue_.StartNextRequest(); | |
| 553 } | |
| 554 | |
| 555 void ExtensionDownloader::HandleManifestResults( | |
| 556 const ManifestFetchData& fetch_data, | |
| 557 const UpdateManifest::Results* results) { | |
| 558 // Keep a list of extensions that will not be updated, so that the |delegate_| | |
| 559 // can be notified once we're done here. | |
| 560 std::set<std::string> not_updated(fetch_data.extension_ids()); | |
| 561 | |
| 562 if (!results) { | |
| 563 NotifyExtensionsDownloadFailed( | |
| 564 not_updated, | |
| 565 fetch_data.request_ids(), | |
| 566 ExtensionDownloaderDelegate::MANIFEST_INVALID); | |
| 567 return; | |
| 568 } | |
| 569 | |
| 570 // Examine the parsed manifest and kick off fetches of any new crx files. | |
| 571 std::vector<int> updates; | |
| 572 DetermineUpdates(fetch_data, *results, &updates); | |
| 573 for (size_t i = 0; i < updates.size(); i++) { | |
| 574 const UpdateManifest::Result* update = &(results->list.at(updates[i])); | |
| 575 const std::string& id = update->extension_id; | |
| 576 not_updated.erase(id); | |
| 577 | |
| 578 GURL crx_url = update->crx_url; | |
| 579 if (id != kBlacklistAppID) { | |
| 580 NotifyUpdateFound(update->extension_id, update->version); | |
| 581 } else { | |
| 582 // The URL of the blacklist file is returned by the server and we need to | |
| 583 // be sure that we continue to be able to reliably detect whether a URL | |
| 584 // references a blacklist file. | |
| 585 DCHECK(extension_urls::IsBlacklistUpdateUrl(crx_url)) << crx_url; | |
| 586 | |
| 587 // Force https (crbug.com/129587). | |
| 588 if (!crx_url.SchemeIsSecure()) { | |
| 589 url::Replacements<char> replacements; | |
| 590 std::string scheme("https"); | |
| 591 replacements.SetScheme(scheme.c_str(), | |
| 592 url::Component(0, scheme.size())); | |
| 593 crx_url = crx_url.ReplaceComponents(replacements); | |
| 594 } | |
| 595 } | |
| 596 scoped_ptr<ExtensionFetch> fetch(new ExtensionFetch( | |
| 597 update->extension_id, crx_url, update->package_hash, | |
| 598 update->version, fetch_data.request_ids())); | |
| 599 FetchUpdatedExtension(fetch.Pass()); | |
| 600 } | |
| 601 | |
| 602 // If the manifest response included a <daystart> element, we want to save | |
| 603 // that value for any extensions which had sent a ping in the request. | |
| 604 if (fetch_data.base_url().DomainIs(kGoogleDotCom) && | |
| 605 results->daystart_elapsed_seconds >= 0) { | |
| 606 Time day_start = | |
| 607 Time::Now() - TimeDelta::FromSeconds(results->daystart_elapsed_seconds); | |
| 608 | |
| 609 const std::set<std::string>& extension_ids = fetch_data.extension_ids(); | |
| 610 std::set<std::string>::const_iterator i; | |
| 611 for (i = extension_ids.begin(); i != extension_ids.end(); i++) { | |
| 612 const std::string& id = *i; | |
| 613 ExtensionDownloaderDelegate::PingResult& result = ping_results_[id]; | |
| 614 result.did_ping = fetch_data.DidPing(id, ManifestFetchData::ROLLCALL); | |
| 615 result.day_start = day_start; | |
| 616 } | |
| 617 } | |
| 618 | |
| 619 NotifyExtensionsDownloadFailed( | |
| 620 not_updated, | |
| 621 fetch_data.request_ids(), | |
| 622 ExtensionDownloaderDelegate::NO_UPDATE_AVAILABLE); | |
| 623 } | |
| 624 | |
| 625 void ExtensionDownloader::DetermineUpdates( | |
| 626 const ManifestFetchData& fetch_data, | |
| 627 const UpdateManifest::Results& possible_updates, | |
| 628 std::vector<int>* result) { | |
| 629 for (size_t i = 0; i < possible_updates.list.size(); i++) { | |
| 630 const UpdateManifest::Result* update = &possible_updates.list[i]; | |
| 631 const std::string& id = update->extension_id; | |
| 632 | |
| 633 if (!fetch_data.Includes(id)) { | |
| 634 VLOG(2) << "Ignoring " << id << " from this manifest"; | |
| 635 continue; | |
| 636 } | |
| 637 | |
| 638 if (VLOG_IS_ON(2)) { | |
| 639 if (update->version.empty()) | |
| 640 VLOG(2) << "manifest indicates " << id << " has no update"; | |
| 641 else | |
| 642 VLOG(2) << "manifest indicates " << id | |
| 643 << " latest version is '" << update->version << "'"; | |
| 644 } | |
| 645 | |
| 646 if (!delegate_->IsExtensionPending(id)) { | |
| 647 // If we're not installing pending extension, and the update | |
| 648 // version is the same or older than what's already installed, | |
| 649 // we don't want it. | |
| 650 std::string version; | |
| 651 if (!delegate_->GetExtensionExistingVersion(id, &version)) { | |
| 652 VLOG(2) << id << " is not installed"; | |
| 653 continue; | |
| 654 } | |
| 655 | |
| 656 VLOG(2) << id << " is at '" << version << "'"; | |
| 657 | |
| 658 // We should skip the version check if update was forced. | |
| 659 if (!fetch_data.DidForceUpdate(id)) { | |
| 660 Version existing_version(version); | |
| 661 Version update_version(update->version); | |
| 662 if (!update_version.IsValid() || | |
| 663 update_version.CompareTo(existing_version) <= 0) { | |
| 664 continue; | |
| 665 } | |
| 666 } | |
| 667 } | |
| 668 | |
| 669 // If the update specifies a browser minimum version, do we qualify? | |
| 670 if (update->browser_min_version.length() > 0 && | |
| 671 !ExtensionsBrowserClient::Get()->IsMinBrowserVersionSupported( | |
| 672 update->browser_min_version)) { | |
| 673 // TODO(asargent) - We may want this to show up in the extensions UI | |
| 674 // eventually. (http://crbug.com/12547). | |
| 675 LOG(WARNING) << "Updated version of extension " << id | |
| 676 << " available, but requires chrome version " | |
| 677 << update->browser_min_version; | |
| 678 continue; | |
| 679 } | |
| 680 VLOG(2) << "will try to update " << id; | |
| 681 result->push_back(i); | |
| 682 } | |
| 683 } | |
| 684 | |
| 685 // Begins (or queues up) download of an updated extension. | |
| 686 void ExtensionDownloader::FetchUpdatedExtension( | |
| 687 scoped_ptr<ExtensionFetch> fetch_data) { | |
| 688 if (!fetch_data->url.is_valid()) { | |
| 689 // TODO(asargent): This can sometimes be invalid. See crbug.com/130881. | |
| 690 LOG(ERROR) << "Invalid URL: '" << fetch_data->url.possibly_invalid_spec() | |
| 691 << "' for extension " << fetch_data->id; | |
| 692 return; | |
| 693 } | |
| 694 | |
| 695 for (RequestQueue<ExtensionFetch>::iterator iter = | |
| 696 extensions_queue_.begin(); | |
| 697 iter != extensions_queue_.end(); ++iter) { | |
| 698 if (iter->id == fetch_data->id || iter->url == fetch_data->url) { | |
| 699 iter->request_ids.insert(fetch_data->request_ids.begin(), | |
| 700 fetch_data->request_ids.end()); | |
| 701 return; // already scheduled | |
| 702 } | |
| 703 } | |
| 704 | |
| 705 if (extensions_queue_.active_request() && | |
| 706 extensions_queue_.active_request()->url == fetch_data->url) { | |
| 707 extensions_queue_.active_request()->request_ids.insert( | |
| 708 fetch_data->request_ids.begin(), fetch_data->request_ids.end()); | |
| 709 } else { | |
| 710 std::string version; | |
| 711 if (extension_cache_ && | |
| 712 extension_cache_->GetExtension(fetch_data->id, NULL, &version) && | |
| 713 version == fetch_data->version) { | |
| 714 base::FilePath crx_path; | |
| 715 // Now get .crx file path and mark extension as used. | |
| 716 extension_cache_->GetExtension(fetch_data->id, &crx_path, &version); | |
| 717 NotifyDelegateDownloadFinished(fetch_data.Pass(), crx_path, false); | |
| 718 } else { | |
| 719 extensions_queue_.ScheduleRequest(fetch_data.Pass()); | |
| 720 } | |
| 721 } | |
| 722 } | |
| 723 | |
| 724 void ExtensionDownloader::NotifyDelegateDownloadFinished( | |
| 725 scoped_ptr<ExtensionFetch> fetch_data, | |
| 726 const base::FilePath& crx_path, | |
| 727 bool file_ownership_passed) { | |
| 728 delegate_->OnExtensionDownloadFinished(fetch_data->id, crx_path, | |
| 729 file_ownership_passed, fetch_data->url, fetch_data->version, | |
| 730 ping_results_[fetch_data->id], fetch_data->request_ids); | |
| 731 ping_results_.erase(fetch_data->id); | |
| 732 } | |
| 733 | |
| 734 void ExtensionDownloader::CreateExtensionFetcher() { | |
| 735 const ExtensionFetch* fetch = extensions_queue_.active_request(); | |
| 736 extension_fetcher_.reset(net::URLFetcher::Create( | |
| 737 kExtensionFetcherId, fetch->url, net::URLFetcher::GET, this)); | |
| 738 extension_fetcher_->SetRequestContext(request_context_.get()); | |
| 739 extension_fetcher_->SetAutomaticallyRetryOnNetworkChanges(3); | |
| 740 | |
| 741 int load_flags = net::LOAD_DISABLE_CACHE; | |
| 742 bool is_secure = fetch->url.SchemeIsSecure(); | |
| 743 if (fetch->credentials != ExtensionFetch::CREDENTIALS_COOKIES || !is_secure) { | |
| 744 load_flags |= net::LOAD_DO_NOT_SEND_COOKIES | | |
| 745 net::LOAD_DO_NOT_SAVE_COOKIES; | |
| 746 } | |
| 747 extension_fetcher_->SetLoadFlags(load_flags); | |
| 748 | |
| 749 // Download CRX files to a temp file. The blacklist is small and will be | |
| 750 // processed in memory, so it is fetched into a string. | |
| 751 if (fetch->id != kBlacklistAppID) { | |
| 752 extension_fetcher_->SaveResponseToTemporaryFile( | |
| 753 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE)); | |
| 754 } | |
| 755 | |
| 756 if (fetch->credentials == ExtensionFetch::CREDENTIALS_OAUTH2_TOKEN && | |
| 757 is_secure) { | |
| 758 if (access_token_.empty()) { | |
| 759 // We should try OAuth2, but we have no token cached. This | |
| 760 // ExtensionFetcher will be started once the token fetch is complete, | |
| 761 // in either OnTokenFetchSuccess or OnTokenFetchFailure. | |
| 762 DCHECK(identity_provider_.get()); | |
| 763 OAuth2TokenService::ScopeSet webstore_scopes; | |
| 764 webstore_scopes.insert(kWebstoreOAuth2Scope); | |
| 765 access_token_request_ = | |
| 766 identity_provider_->GetTokenService()->StartRequest( | |
| 767 identity_provider_->GetActiveAccountId(), | |
| 768 webstore_scopes, | |
| 769 this); | |
| 770 return; | |
| 771 } | |
| 772 extension_fetcher_->AddExtraRequestHeader( | |
| 773 base::StringPrintf("%s: Bearer %s", | |
| 774 net::HttpRequestHeaders::kAuthorization, | |
| 775 access_token_.c_str())); | |
| 776 } | |
| 777 | |
| 778 VLOG(2) << "Starting fetch of " << fetch->url << " for " << fetch->id; | |
| 779 extension_fetcher_->Start(); | |
| 780 } | |
| 781 | |
| 782 void ExtensionDownloader::OnCRXFetchComplete( | |
| 783 const net::URLFetcher* source, | |
| 784 const GURL& url, | |
| 785 const net::URLRequestStatus& status, | |
| 786 int response_code, | |
| 787 const base::TimeDelta& backoff_delay) { | |
| 788 ExtensionFetch& active_request = *extensions_queue_.active_request(); | |
| 789 const std::string& id = active_request.id; | |
| 790 if (status.status() == net::URLRequestStatus::SUCCESS && | |
| 791 (response_code == 200 || url.SchemeIsFile())) { | |
| 792 RETRY_HISTOGRAM("CrxFetchSuccess", | |
| 793 extensions_queue_.active_request_failure_count(), url); | |
| 794 base::FilePath crx_path; | |
| 795 // Take ownership of the file at |crx_path|. | |
| 796 CHECK(source->GetResponseAsFilePath(true, &crx_path)); | |
| 797 scoped_ptr<ExtensionFetch> fetch_data = | |
| 798 extensions_queue_.reset_active_request(); | |
| 799 if (extension_cache_) { | |
| 800 const std::string& version = fetch_data->version; | |
| 801 extension_cache_->PutExtension(id, crx_path, version, | |
| 802 base::Bind(&ExtensionDownloader::NotifyDelegateDownloadFinished, | |
| 803 weak_ptr_factory_.GetWeakPtr(), | |
| 804 base::Passed(&fetch_data))); | |
| 805 } else { | |
| 806 NotifyDelegateDownloadFinished(fetch_data.Pass(), crx_path, true); | |
| 807 } | |
| 808 } else if (IterateFetchCredentialsAfterFailure( | |
| 809 &active_request, | |
| 810 status, | |
| 811 response_code)) { | |
| 812 extensions_queue_.RetryRequest(backoff_delay); | |
| 813 } else { | |
| 814 const std::set<int>& request_ids = active_request.request_ids; | |
| 815 const ExtensionDownloaderDelegate::PingResult& ping = ping_results_[id]; | |
| 816 VLOG(1) << "Failed to fetch extension '" << url.possibly_invalid_spec() | |
| 817 << "' response code:" << response_code; | |
| 818 if (ShouldRetryRequest(status, response_code) && | |
| 819 extensions_queue_.active_request_failure_count() < kMaxRetries) { | |
| 820 extensions_queue_.RetryRequest(backoff_delay); | |
| 821 } else { | |
| 822 RETRY_HISTOGRAM("CrxFetchFailure", | |
| 823 extensions_queue_.active_request_failure_count(), url); | |
| 824 // status.error() is 0 (net::OK) or negative. (See net/base/net_errors.h) | |
| 825 UMA_HISTOGRAM_SPARSE_SLOWLY("Extensions.CrxFetchError", -status.error()); | |
| 826 delegate_->OnExtensionDownloadFailed( | |
| 827 id, ExtensionDownloaderDelegate::CRX_FETCH_FAILED, ping, request_ids); | |
| 828 } | |
| 829 ping_results_.erase(id); | |
| 830 extensions_queue_.reset_active_request(); | |
| 831 } | |
| 832 | |
| 833 extension_fetcher_.reset(); | |
| 834 | |
| 835 // If there are any pending downloads left, start the next one. | |
| 836 extensions_queue_.StartNextRequest(); | |
| 837 } | |
| 838 | |
| 839 void ExtensionDownloader::NotifyExtensionsDownloadFailed( | |
| 840 const std::set<std::string>& extension_ids, | |
| 841 const std::set<int>& request_ids, | |
| 842 ExtensionDownloaderDelegate::Error error) { | |
| 843 for (std::set<std::string>::const_iterator it = extension_ids.begin(); | |
| 844 it != extension_ids.end(); ++it) { | |
| 845 const ExtensionDownloaderDelegate::PingResult& ping = ping_results_[*it]; | |
| 846 delegate_->OnExtensionDownloadFailed(*it, error, ping, request_ids); | |
| 847 ping_results_.erase(*it); | |
| 848 } | |
| 849 } | |
| 850 | |
| 851 void ExtensionDownloader::NotifyUpdateFound(const std::string& id, | |
| 852 const std::string& version) { | |
| 853 UpdateDetails updateInfo(id, Version(version)); | |
| 854 content::NotificationService::current()->Notify( | |
| 855 extensions::NOTIFICATION_EXTENSION_UPDATE_FOUND, | |
| 856 content::NotificationService::AllBrowserContextsAndSources(), | |
| 857 content::Details<UpdateDetails>(&updateInfo)); | |
| 858 } | |
| 859 | |
| 860 bool ExtensionDownloader::IterateFetchCredentialsAfterFailure( | |
| 861 ExtensionFetch* fetch, | |
| 862 const net::URLRequestStatus& status, | |
| 863 int response_code) { | |
| 864 bool auth_failure = status.status() == net::URLRequestStatus::CANCELED || | |
| 865 (status.status() == net::URLRequestStatus::SUCCESS && | |
| 866 (response_code == net::HTTP_UNAUTHORIZED || | |
| 867 response_code == net::HTTP_FORBIDDEN)); | |
| 868 if (!auth_failure) { | |
| 869 return false; | |
| 870 } | |
| 871 // Here we decide what to do next if the server refused to authorize this | |
| 872 // fetch. | |
| 873 switch (fetch->credentials) { | |
| 874 case ExtensionFetch::CREDENTIALS_NONE: | |
| 875 if (fetch->url.DomainIs(kGoogleDotCom) && identity_provider_) { | |
| 876 fetch->credentials = ExtensionFetch::CREDENTIALS_OAUTH2_TOKEN; | |
| 877 } else { | |
| 878 fetch->credentials = ExtensionFetch::CREDENTIALS_COOKIES; | |
| 879 } | |
| 880 return true; | |
| 881 case ExtensionFetch::CREDENTIALS_OAUTH2_TOKEN: | |
| 882 fetch->oauth2_attempt_count++; | |
| 883 // OAuth2 may fail due to an expired access token, in which case we | |
| 884 // should invalidate the token and try again. | |
| 885 if (response_code == net::HTTP_UNAUTHORIZED && | |
| 886 fetch->oauth2_attempt_count <= kMaxOAuth2Attempts) { | |
| 887 DCHECK(identity_provider_.get()); | |
| 888 OAuth2TokenService::ScopeSet webstore_scopes; | |
| 889 webstore_scopes.insert(kWebstoreOAuth2Scope); | |
| 890 identity_provider_->GetTokenService()->InvalidateToken( | |
| 891 identity_provider_->GetActiveAccountId(), | |
| 892 webstore_scopes, | |
| 893 access_token_); | |
| 894 access_token_.clear(); | |
| 895 return true; | |
| 896 } | |
| 897 // Either there is no Gaia identity available, the active identity | |
| 898 // doesn't have access to this resource, or the server keeps returning | |
| 899 // 401s and we've retried too many times. Fall back on cookies. | |
| 900 if (access_token_.empty() || | |
| 901 response_code == net::HTTP_FORBIDDEN || | |
| 902 fetch->oauth2_attempt_count > kMaxOAuth2Attempts) { | |
| 903 fetch->credentials = ExtensionFetch::CREDENTIALS_COOKIES; | |
| 904 return true; | |
| 905 } | |
| 906 // Something else is wrong. Time to give up. | |
| 907 return false; | |
| 908 case ExtensionFetch::CREDENTIALS_COOKIES: | |
| 909 if (response_code == net::HTTP_FORBIDDEN) { | |
| 910 // Try the next session identity, up to some maximum. | |
| 911 return IncrementAuthUserIndex(&fetch->url); | |
| 912 } | |
| 913 return false; | |
| 914 default: | |
| 915 NOTREACHED(); | |
| 916 } | |
| 917 NOTREACHED(); | |
| 918 return false; | |
| 919 } | |
| 920 | |
| 921 void ExtensionDownloader::OnGetTokenSuccess( | |
| 922 const OAuth2TokenService::Request* request, | |
| 923 const std::string& access_token, | |
| 924 const base::Time& expiration_time) { | |
| 925 access_token_ = access_token; | |
| 926 extension_fetcher_->AddExtraRequestHeader( | |
| 927 base::StringPrintf("%s: Bearer %s", | |
| 928 net::HttpRequestHeaders::kAuthorization, | |
| 929 access_token_.c_str())); | |
| 930 extension_fetcher_->Start(); | |
| 931 } | |
| 932 | |
| 933 void ExtensionDownloader::OnGetTokenFailure( | |
| 934 const OAuth2TokenService::Request* request, | |
| 935 const GoogleServiceAuthError& error) { | |
| 936 // If we fail to get an access token, kick the pending fetch and let it fall | |
| 937 // back on cookies. | |
| 938 extension_fetcher_->Start(); | |
| 939 } | |
| 940 | |
| 941 ManifestFetchData* ExtensionDownloader::CreateManifestFetchData( | |
| 942 const GURL& update_url, | |
| 943 int request_id) { | |
| 944 ManifestFetchData::PingMode ping_mode = ManifestFetchData::NO_PING; | |
| 945 if (update_url.DomainIs(ping_enabled_domain_.c_str())) { | |
| 946 if (enable_extra_update_metrics_) { | |
| 947 ping_mode = ManifestFetchData::PING_WITH_METRICS; | |
| 948 } else { | |
| 949 ping_mode = ManifestFetchData::PING; | |
| 950 } | |
| 951 } | |
| 952 return new ManifestFetchData( | |
| 953 update_url, request_id, brand_code_, manifest_query_params_, ping_mode); | |
| 954 } | |
| 955 | |
| 956 } // namespace extensions | |
| OLD | NEW |