blob: 2294c1dd5570a2d3e4a3dd706d9d5651a69408ed [file] [log] [blame]
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/extension_updater.h"
#include <algorithm>
#include <set>
#include "base/bind.h"
#include "base/compiler_specific.h"
#include "base/file_util.h"
#include "base/logging.h"
#include "base/memory/scoped_handle.h"
#include "base/metrics/histogram.h"
#include "base/rand_util.h"
#include "base/stl_util.h"
#include "base/string_number_conversions.h"
#include "base/string_split.h"
#include "base/string_util.h"
#include "base/threading/thread.h"
#include "base/time.h"
#include "base/version.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/extensions/crx_installer.h"
#include "chrome/browser/extensions/extension_error_reporter.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/google/google_util.h"
#include "chrome/browser/prefs/pref_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_notification_types.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/chrome_utility_messages.h"
#include "chrome/common/chrome_version_info.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/common/extensions/extension_file_util.h"
#include "chrome/common/pref_names.h"
#include "content/browser/utility_process_host.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_source.h"
#include "crypto/sha2.h"
#include "googleurl/src/gurl.h"
#include "net/base/escape.h"
#include "net/base/load_flags.h"
#include "net/url_request/url_request_status.h"
#if defined(OS_MACOSX)
#include "base/sys_string_conversions.h"
#endif
#define SEND_ACTIVE_PINGS 1
using base::RandDouble;
using base::RandInt;
using base::Time;
using base::TimeDelta;
using prefs::kExtensionBlacklistUpdateVersion;
using prefs::kLastExtensionsUpdateCheck;
using prefs::kNextExtensionsUpdateCheck;
// Update AppID for extension blacklist.
const char* ExtensionUpdater::kBlacklistAppID = "com.google.crx.blacklist";
namespace {
// Wait at least 5 minutes after browser startup before we do any checks. If you
// change this value, make sure to update comments where it is used.
const int kStartupWaitSeconds = 60 * 5;
// For sanity checking on update frequency - enforced in release mode only.
static const int kMinUpdateFrequencySeconds = 30;
static const int kMaxUpdateFrequencySeconds = 60 * 60 * 24 * 7; // 7 days
// Maximum length of an extension manifest update check url, since it is a GET
// request. We want to stay under 2K because of proxies, etc.
static const int kExtensionsManifestMaxURLSize = 2000;
// TODO(skerner): It would be nice to know if the file system failure
// happens when creating a temp file or when writing to it. Knowing this
// will require changes to URLFetcher.
enum FileWriteResult {
SUCCESS = 0,
CANT_CREATE_OR_WRITE_TEMP_CRX,
CANT_READ_CRX_FILE,
NUM_FILE_WRITE_RESULTS
};
// Prototypes allow the functions to be defined in the order they run.
void CheckThatCRXIsReadable(const FilePath& crx_path);
void RecordFileUpdateHistogram(FileWriteResult file_write_result);
// Record the result of writing a CRX file. Will be used to understand
// high failure rates of CRX installs in the field. If |success| is
// true, |crx_path| should be set to the path to the CRX file.
void RecordCRXWriteHistogram(bool success, const FilePath& crx_path) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (!success) {
// We know there was an error writing the file.
RecordFileUpdateHistogram(CANT_CREATE_OR_WRITE_TEMP_CRX);
} else {
// Test that the file can be read. Based on histograms in
// SandboxExtensionUnpacker, we know that many CRX files
// can not be read. Try reading.
BrowserThread::PostTask(
BrowserThread::FILE, FROM_HERE,
base::Bind(CheckThatCRXIsReadable, crx_path));
}
}
void CheckThatCRXIsReadable(const FilePath& crx_path) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
FileWriteResult file_write_result = SUCCESS;
// Open the file in the same way
// SandboxExtensionUnpacker::ValidateSigniture() will.
ScopedStdioHandle file(file_util::OpenFile(crx_path, "rb"));
if (!file.get()) {
LOG(ERROR) << "Can't read CRX file written for update at path "
<< crx_path.value().c_str();
file_write_result = CANT_READ_CRX_FILE;
}
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(RecordFileUpdateHistogram, file_write_result));
}
void RecordFileUpdateHistogram(FileWriteResult file_write_result) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
UMA_HISTOGRAM_ENUMERATION("Extensions.UpdaterWriteCrxAsFile",
file_write_result,
NUM_FILE_WRITE_RESULTS);
}
} // namespace
ManifestFetchData::ManifestFetchData(const GURL& update_url)
: base_url_(update_url),
full_url_(update_url) {
}
ManifestFetchData::~ManifestFetchData() {}
// The format for request parameters in update checks is:
//
// ?x=EXT1_INFO&x=EXT2_INFO
//
// where EXT1_INFO and EXT2_INFO are url-encoded strings of the form:
//
// id=EXTENSION_ID&v=VERSION&uc
//
// Additionally, we may include the parameter ping=PING_DATA where PING_DATA
// looks like r=DAYS or a=DAYS for extensions in the Chrome extensions gallery.
// ('r' refers to 'roll call' ie installation, and 'a' refers to 'active').
// These values will each be present at most once every 24 hours, and indicate
// the number of days since the last time it was present in an update check.
//
// So for two extensions like:
// Extension 1- id:aaaa version:1.1
// Extension 2- id:bbbb version:2.0
//
// the full update url would be:
// http://somehost/path?x=id%3Daaaa%26v%3D1.1%26uc&x=id%3Dbbbb%26v%3D2.0%26uc
//
// (Note that '=' is %3D and '&' is %26 when urlencoded.)
bool ManifestFetchData::AddExtension(std::string id, std::string version,
const PingData& ping_data,
const std::string& update_url_data) {
if (extension_ids_.find(id) != extension_ids_.end()) {
NOTREACHED() << "Duplicate extension id " << id;
return false;
}
// Compute the string we'd append onto the full_url_, and see if it fits.
std::vector<std::string> parts;
parts.push_back("id=" + id);
parts.push_back("v=" + version);
parts.push_back("uc");
if (!update_url_data.empty()) {
// Make sure the update_url_data string is escaped before using it so that
// there is no chance of overriding the id or v other parameter value
// we place into the x= value.
parts.push_back("ap=" + net::EscapeQueryParamValue(update_url_data, true));
}
// Append brand code, rollcall and active ping parameters.
if (base_url_.DomainIs("google.com")) {
#if defined(GOOGLE_CHROME_BUILD)
std::string brand;
google_util::GetBrand(&brand);
if (!brand.empty() && !google_util::IsOrganic(brand))
parts.push_back("brand=" + brand);
#endif
std::string ping_value;
pings_[id] = PingData(0, 0);
if (ping_data.rollcall_days == kNeverPinged ||
ping_data.rollcall_days > 0) {
ping_value += "r=" + base::IntToString(ping_data.rollcall_days);
pings_[id].rollcall_days = ping_data.rollcall_days;
}
#if SEND_ACTIVE_PINGS
if (ping_data.active_days == kNeverPinged || ping_data.active_days > 0) {
if (!ping_value.empty())
ping_value += "&";
ping_value += "a=" + base::IntToString(ping_data.active_days);
pings_[id].active_days = ping_data.active_days;
}
#endif // SEND_ACTIVE_PINGS
if (!ping_value.empty())
parts.push_back("ping=" + net::EscapeQueryParamValue(ping_value, true));
}
std::string extra = full_url_.has_query() ? "&" : "?";
extra += "x=" + net::EscapeQueryParamValue(JoinString(parts, '&'), true);
// Check against our max url size, exempting the first extension added.
int new_size = full_url_.possibly_invalid_spec().size() + extra.size();
if (!extension_ids_.empty() && new_size > kExtensionsManifestMaxURLSize) {
UMA_HISTOGRAM_PERCENTAGE("Extensions.UpdateCheckHitUrlSizeLimit", 1);
return false;
}
UMA_HISTOGRAM_PERCENTAGE("Extensions.UpdateCheckHitUrlSizeLimit", 0);
// We have room so go ahead and add the extension.
extension_ids_.insert(id);
full_url_ = GURL(full_url_.possibly_invalid_spec() + extra);
return true;
}
bool ManifestFetchData::Includes(const std::string& extension_id) const {
return extension_ids_.find(extension_id) != extension_ids_.end();
}
bool ManifestFetchData::DidPing(std::string extension_id, PingType type) const {
std::map<std::string, PingData>::const_iterator i = pings_.find(extension_id);
if (i == pings_.end())
return false;
int value = 0;
if (type == ROLLCALL)
value = i->second.rollcall_days;
else if (type == ACTIVE)
value = i->second.active_days;
else
NOTREACHED();
return value == kNeverPinged || value > 0;
}
namespace {
// When we've computed a days value, we want to make sure we don't send a
// negative value (due to the system clock being set backwards, etc.), since -1
// is a special sentinel value that means "never pinged", and other negative
// values don't make sense.
static int SanitizeDays(int days) {
if (days < 0)
return 0;
return days;
}
// Calculates the value to use for the ping days parameter.
static int CalculatePingDays(const Time& last_ping_day) {
int days = ManifestFetchData::kNeverPinged;
if (!last_ping_day.is_null()) {
days = SanitizeDays((Time::Now() - last_ping_day).InDays());
}
return days;
}
static int CalculateActivePingDays(const Time& last_active_ping_day,
bool hasActiveBit) {
if (!hasActiveBit)
return 0;
if (last_active_ping_day.is_null())
return ManifestFetchData::kNeverPinged;
return SanitizeDays((Time::Now() - last_active_ping_day).InDays());
}
} // namespace
ManifestFetchesBuilder::ManifestFetchesBuilder(
ExtensionServiceInterface* service,
ExtensionPrefs* prefs)
: service_(service), prefs_(prefs) {
DCHECK(service_);
DCHECK(prefs_);
}
ManifestFetchesBuilder::~ManifestFetchesBuilder() {}
void ManifestFetchesBuilder::AddExtension(const Extension& extension) {
// Skip extensions with empty update URLs converted from user
// scripts.
if (extension.converted_from_user_script() &&
extension.update_url().is_empty()) {
return;
}
// If the extension updates itself from the gallery, ignore any update URL
// data. At the moment there is no extra data that an extension can
// communicate to the the gallery update servers.
std::string update_url_data;
if (!extension.UpdatesFromGallery())
update_url_data = prefs_->GetUpdateUrlData(extension.id());
AddExtensionData(extension.location(),
extension.id(),
*extension.version(),
extension.GetType(),
extension.update_url(), update_url_data);
}
void ManifestFetchesBuilder::AddPendingExtension(
const std::string& id,
const PendingExtensionInfo& info) {
// Use a zero version to ensure that a pending extension will always
// be updated, and thus installed (assuming all extensions have
// non-zero versions).
Version version("0.0.0.0");
DCHECK(version.IsValid());
AddExtensionData(info.install_source(), id, version,
Extension::TYPE_UNKNOWN, info.update_url(), "");
}
void ManifestFetchesBuilder::ReportStats() const {
UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckExtension",
url_stats_.extension_count);
UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckTheme",
url_stats_.theme_count);
UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckApp",
url_stats_.app_count);
UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckPending",
url_stats_.pending_count);
UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckGoogleUrl",
url_stats_.google_url_count);
UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckOtherUrl",
url_stats_.other_url_count);
UMA_HISTOGRAM_COUNTS_100("Extensions.UpdateCheckNoUrl",
url_stats_.no_url_count);
}
std::vector<ManifestFetchData*> ManifestFetchesBuilder::GetFetches() {
std::vector<ManifestFetchData*> fetches;
fetches.reserve(fetches_.size());
for (std::multimap<GURL, ManifestFetchData*>::iterator it =
fetches_.begin(); it != fetches_.end(); ++it) {
fetches.push_back(it->second);
}
fetches_.clear();
url_stats_ = URLStats();
return fetches;
}
void ManifestFetchesBuilder::AddExtensionData(
Extension::Location location,
const std::string& id,
const Version& version,
Extension::Type extension_type,
GURL update_url,
const std::string& update_url_data) {
if (!Extension::IsAutoUpdateableLocation(location)) {
return;
}
// Skip extensions with non-empty invalid update URLs.
if (!update_url.is_empty() && !update_url.is_valid()) {
LOG(WARNING) << "Extension " << id << " has invalid update url "
<< update_url;
return;
}
// Skip extensions with empty IDs.
if (id.empty()) {
LOG(WARNING) << "Found extension with empty ID";
return;
}
if (update_url.DomainIs("google.com")) {
url_stats_.google_url_count++;
} else if (update_url.is_empty()) {
url_stats_.no_url_count++;
// Fill in default update URL.
//
// TODO(akalin): Figure out if we should use the HTTPS version.
update_url = extension_urls::GetWebstoreUpdateUrl(false);
} else {
url_stats_.other_url_count++;
}
switch (extension_type) {
case Extension::TYPE_THEME:
++url_stats_.theme_count;
break;
case Extension::TYPE_EXTENSION:
case Extension::TYPE_USER_SCRIPT:
++url_stats_.extension_count;
break;
case Extension::TYPE_HOSTED_APP:
case Extension::TYPE_PACKAGED_APP:
++url_stats_.app_count;
break;
case Extension::TYPE_UNKNOWN:
default:
++url_stats_.pending_count;
break;
}
DCHECK(!update_url.is_empty());
DCHECK(update_url.is_valid());
ManifestFetchData* fetch = NULL;
std::multimap<GURL, ManifestFetchData*>::iterator existing_iter =
fetches_.find(update_url);
// Find or create a ManifestFetchData to add this extension to.
ManifestFetchData::PingData ping_data;
ping_data.rollcall_days = CalculatePingDays(prefs_->LastPingDay(id));
ping_data.active_days =
CalculateActivePingDays(prefs_->LastActivePingDay(id),
prefs_->GetActiveBit(id));
while (existing_iter != fetches_.end()) {
if (existing_iter->second->AddExtension(id, version.GetString(),
ping_data, update_url_data)) {
fetch = existing_iter->second;
break;
}
existing_iter++;
}
if (!fetch) {
fetch = new ManifestFetchData(update_url);
fetches_.insert(std::pair<GURL, ManifestFetchData*>(update_url, fetch));
bool added = fetch->AddExtension(id, version.GetString(), ping_data,
update_url_data);
DCHECK(added);
}
}
ExtensionUpdater::ExtensionFetch::ExtensionFetch()
: id(""),
url(),
package_hash(""),
version("") {}
ExtensionUpdater::ExtensionFetch::ExtensionFetch(const std::string& i,
const GURL& u,
const std::string& h,
const std::string& v)
: id(i), url(u), package_hash(h), version(v) {}
ExtensionUpdater::ExtensionFetch::~ExtensionFetch() {}
ExtensionUpdater::FetchedCRXFile::FetchedCRXFile(const std::string& i,
const FilePath& p,
const GURL& u)
: id(i),
path(p),
download_url(u) {}
ExtensionUpdater::FetchedCRXFile::FetchedCRXFile()
: id(""),
path(),
download_url() {}
ExtensionUpdater::FetchedCRXFile::~FetchedCRXFile() {}
ExtensionUpdater::ExtensionUpdater(ExtensionServiceInterface* service,
ExtensionPrefs* extension_prefs,
PrefService* prefs,
Profile* profile,
int frequency_seconds)
: alive_(false),
weak_ptr_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)),
service_(service), frequency_seconds_(frequency_seconds),
will_check_soon_(false), extension_prefs_(extension_prefs),
prefs_(prefs), profile_(profile), blacklist_checks_enabled_(true),
crx_install_is_running_(false) {
Init();
}
void ExtensionUpdater::Init() {
DCHECK_GE(frequency_seconds_, 5);
DCHECK_LE(frequency_seconds_, kMaxUpdateFrequencySeconds);
#ifdef NDEBUG
// In Release mode we enforce that update checks don't happen too often.
frequency_seconds_ = std::max(frequency_seconds_, kMinUpdateFrequencySeconds);
#endif
frequency_seconds_ = std::min(frequency_seconds_, kMaxUpdateFrequencySeconds);
}
ExtensionUpdater::~ExtensionUpdater() {
Stop();
}
static void EnsureInt64PrefRegistered(PrefService* prefs,
const char name[]) {
if (!prefs->FindPreference(name))
prefs->RegisterInt64Pref(name, 0, PrefService::UNSYNCABLE_PREF);
}
static void EnsureBlacklistVersionPrefRegistered(PrefService* prefs) {
if (!prefs->FindPreference(kExtensionBlacklistUpdateVersion)) {
prefs->RegisterStringPref(kExtensionBlacklistUpdateVersion,
"0",
PrefService::UNSYNCABLE_PREF);
}
}
// The overall goal here is to balance keeping clients up to date while
// avoiding a thundering herd against update servers.
TimeDelta ExtensionUpdater::DetermineFirstCheckDelay() {
DCHECK(alive_);
// If someone's testing with a quick frequency, just allow it.
if (frequency_seconds_ < kStartupWaitSeconds)
return TimeDelta::FromSeconds(frequency_seconds_);
// If we've never scheduled a check before, start at frequency_seconds_.
if (!prefs_->HasPrefPath(kNextExtensionsUpdateCheck))
return TimeDelta::FromSeconds(frequency_seconds_);
// If it's been a long time since our last actual check, we want to do one
// relatively soon.
Time now = Time::Now();
Time last = Time::FromInternalValue(prefs_->GetInt64(
kLastExtensionsUpdateCheck));
int days = (now - last).InDays();
if (days >= 30) {
// Wait 5-10 minutes.
return TimeDelta::FromSeconds(RandInt(kStartupWaitSeconds,
kStartupWaitSeconds * 2));
} else if (days >= 14) {
// Wait 10-20 minutes.
return TimeDelta::FromSeconds(RandInt(kStartupWaitSeconds * 2,
kStartupWaitSeconds * 4));
} else if (days >= 3) {
// Wait 20-40 minutes.
return TimeDelta::FromSeconds(RandInt(kStartupWaitSeconds * 4,
kStartupWaitSeconds * 8));
}
// Read the persisted next check time, and use that if it isn't too soon.
// Otherwise pick something random.
Time saved_next = Time::FromInternalValue(prefs_->GetInt64(
kNextExtensionsUpdateCheck));
Time earliest = now + TimeDelta::FromSeconds(kStartupWaitSeconds);
if (saved_next >= earliest) {
return saved_next - now;
} else {
return TimeDelta::FromSeconds(RandInt(kStartupWaitSeconds,
frequency_seconds_));
}
}
void ExtensionUpdater::Start() {
DCHECK(!alive_);
// If these are NULL, then that means we've been called after Stop()
// has been called.
DCHECK(service_);
DCHECK(extension_prefs_);
DCHECK(prefs_);
DCHECK(profile_);
DCHECK(!weak_ptr_factory_.HasWeakPtrs());
alive_ = true;
// Make sure our prefs are registered, then schedule the first check.
EnsureInt64PrefRegistered(prefs_, kLastExtensionsUpdateCheck);
EnsureInt64PrefRegistered(prefs_, kNextExtensionsUpdateCheck);
EnsureBlacklistVersionPrefRegistered(prefs_);
ScheduleNextCheck(DetermineFirstCheckDelay());
}
void ExtensionUpdater::Stop() {
weak_ptr_factory_.InvalidateWeakPtrs();
alive_ = false;
service_ = NULL;
extension_prefs_ = NULL;
prefs_ = NULL;
profile_ = NULL;
timer_.Stop();
will_check_soon_ = false;
manifest_fetcher_.reset();
extension_fetcher_.reset();
STLDeleteElements(&manifests_pending_);
manifests_pending_.clear();
extensions_pending_.clear();
}
void ExtensionUpdater::OnURLFetchComplete(const URLFetcher* source) {
// Stop() destroys all our URLFetchers, which means we shouldn't be
// called after Stop() is called.
DCHECK(alive_);
if (source == manifest_fetcher_.get()) {
std::string data;
CHECK(source->GetResponseAsString(&data));
OnManifestFetchComplete(source->url(),
source->status(),
source->response_code(),
data);
} else if (source == extension_fetcher_.get()) {
OnCRXFetchComplete(source,
source->url(),
source->status(),
source->response_code());
} else {
NOTREACHED();
}
NotifyIfFinished();
}
// Utility class to handle doing xml parsing in a sandboxed utility process.
class SafeManifestParser : public UtilityProcessHost::Client {
public:
// Takes ownership of |fetch_data|.
SafeManifestParser(const std::string& xml, ManifestFetchData* fetch_data,
base::WeakPtr<ExtensionUpdater> updater)
: xml_(xml), updater_(updater) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
fetch_data_.reset(fetch_data);
}
// Posts a task over to the IO loop to start the parsing of xml_ in a
// utility process.
void Start() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (!BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(
&SafeManifestParser::ParseInSandbox, this,
g_browser_process->resource_dispatcher_host()))) {
NOTREACHED();
}
}
// Creates the sandboxed utility process and tells it to start parsing.
void ParseInSandbox(ResourceDispatcherHost* rdh) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
// TODO(asargent) we shouldn't need to do this branch here - instead
// UtilityProcessHost should handle it for us. (http://crbug.com/19192)
bool use_utility_process = rdh &&
!CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess);
if (use_utility_process) {
UtilityProcessHost* host = new UtilityProcessHost(
this, BrowserThread::UI);
host->Send(new ChromeUtilityMsg_ParseUpdateManifest(xml_));
} else {
UpdateManifest manifest;
if (manifest.Parse(xml_)) {
if (!BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(
&SafeManifestParser::OnParseUpdateManifestSucceeded, this,
manifest.results()))) {
NOTREACHED();
}
} else {
if (!BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(
&SafeManifestParser::OnParseUpdateManifestFailed, this,
manifest.errors()))) {
NOTREACHED();
}
}
}
}
// UtilityProcessHost::Client
virtual bool OnMessageReceived(const IPC::Message& message) {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(SafeManifestParser, message)
IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_ParseUpdateManifest_Succeeded,
OnParseUpdateManifestSucceeded)
IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_ParseUpdateManifest_Failed,
OnParseUpdateManifestFailed)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
}
void OnParseUpdateManifestSucceeded(
const UpdateManifest::Results& results) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (!updater_) {
return;
}
updater_->HandleManifestResults(*fetch_data_, &results);
}
void OnParseUpdateManifestFailed(const std::string& error_message) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (!updater_) {
return;
}
LOG(WARNING) << "Error parsing update manifest:\n" << error_message;
updater_->HandleManifestResults(*fetch_data_, NULL);
}
private:
~SafeManifestParser() {
// If we're using UtilityProcessHost, we may not be destroyed on
// the UI or IO thread.
}
const std::string xml_;
// Should be accessed only on UI thread.
scoped_ptr<ManifestFetchData> fetch_data_;
base::WeakPtr<ExtensionUpdater> updater_;
};
void ExtensionUpdater::OnManifestFetchComplete(
const GURL& url,
const net::URLRequestStatus& status,
int response_code,
const std::string& data) {
// We want to try parsing the manifest, and if it indicates updates are
// available, we want to fire off requests to fetch those updates.
if (status.status() == net::URLRequestStatus::SUCCESS &&
(response_code == 200 || (url.SchemeIsFile() && data.length() > 0))) {
scoped_refptr<SafeManifestParser> safe_parser(
new SafeManifestParser(data, current_manifest_fetch_.release(),
weak_ptr_factory_.GetWeakPtr()));
safe_parser->Start();
} else {
// TODO(asargent) Do exponential backoff here. (http://crbug.com/12546).
VLOG(1) << "Failed to fetch manifest '" << url.possibly_invalid_spec()
<< "' response code:" << response_code;
RemoveFromInProgress(current_manifest_fetch_->extension_ids());
}
manifest_fetcher_.reset();
current_manifest_fetch_.reset();
// If we have any pending manifest requests, fire off the next one.
if (!manifests_pending_.empty()) {
ManifestFetchData* manifest_fetch = manifests_pending_.front();
manifests_pending_.pop_front();
StartUpdateCheck(manifest_fetch);
}
}
void ExtensionUpdater::HandleManifestResults(
const ManifestFetchData& fetch_data,
const UpdateManifest::Results* results) {
DCHECK(alive_);
// Remove all the ids's from in_progress_ids_ (we will add them back in
// below if they actually have updates we need to fetch and install).
RemoveFromInProgress(fetch_data.extension_ids());
if (!results) {
NotifyIfFinished();
return;
}
// Examine the parsed manifest and kick off fetches of any new crx files.
std::vector<int> updates = DetermineUpdates(fetch_data, *results);
for (size_t i = 0; i < updates.size(); i++) {
const UpdateManifest::Result* update = &(results->list.at(updates[i]));
const std::string& id = update->extension_id;
in_progress_ids_.insert(id);
if (id != std::string(kBlacklistAppID))
NotifyUpdateFound(update->extension_id);
FetchUpdatedExtension(update->extension_id, update->crx_url,
update->package_hash, update->version);
}
// If the manifest response included a <daystart> element, we want to save
// that value for any extensions which had sent a ping in the request.
if (fetch_data.base_url().DomainIs("google.com") &&
results->daystart_elapsed_seconds >= 0) {
Time daystart =
Time::Now() - TimeDelta::FromSeconds(results->daystart_elapsed_seconds);
const std::set<std::string>& extension_ids = fetch_data.extension_ids();
std::set<std::string>::const_iterator i;
for (i = extension_ids.begin(); i != extension_ids.end(); i++) {
if (fetch_data.DidPing(*i, ManifestFetchData::ROLLCALL)) {
if (*i == kBlacklistAppID) {
extension_prefs_->SetBlacklistLastPingDay(daystart);
} else if (service_->GetExtensionById(*i, true) != NULL) {
extension_prefs_->SetLastPingDay(*i, daystart);
}
}
if (extension_prefs_->GetActiveBit(*i)) {
extension_prefs_->SetActiveBit(*i, false);
extension_prefs_->SetLastActivePingDay(*i, daystart);
}
}
}
NotifyIfFinished();
}
void ExtensionUpdater::ProcessBlacklist(const std::string& data) {
DCHECK(alive_);
// Verify sha256 hash value.
char sha256_hash_value[crypto::kSHA256Length];
crypto::SHA256HashString(data, sha256_hash_value, crypto::kSHA256Length);
std::string hash_in_hex = base::HexEncode(sha256_hash_value,
crypto::kSHA256Length);
if (current_extension_fetch_.package_hash != hash_in_hex) {
NOTREACHED() << "Fetched blacklist checksum is not as expected. "
<< "Expected: " << current_extension_fetch_.package_hash
<< " Actual: " << hash_in_hex;
return;
}
std::vector<std::string> blacklist;
base::SplitString(data, '\n', &blacklist);
// Tell ExtensionService to update prefs.
service_->UpdateExtensionBlacklist(blacklist);
// Update the pref value for blacklist version
prefs_->SetString(kExtensionBlacklistUpdateVersion,
current_extension_fetch_.version);
prefs_->ScheduleSavePersistentPrefs();
}
void ExtensionUpdater::OnCRXFetchComplete(
const URLFetcher* source,
const GURL& url,
const net::URLRequestStatus& status,
int response_code) {
base::PlatformFileError error_code = base::PLATFORM_FILE_OK;
if (source->FileErrorOccurred(&error_code)) {
LOG(ERROR) << "Failed to write update CRX with id "
<< current_extension_fetch_.id << ". "
<< "Error code is "<< error_code;
RecordCRXWriteHistogram(false, FilePath());
OnCRXFileWriteError(current_extension_fetch_.id);
} else if (status.status() == net::URLRequestStatus::SUCCESS &&
(response_code == 200 || url.SchemeIsFile())) {
if (current_extension_fetch_.id == kBlacklistAppID) {
std::string data;
CHECK(source->GetResponseAsString(&data));
ProcessBlacklist(data);
in_progress_ids_.erase(current_extension_fetch_.id);
} else {
FilePath crx_path;
// Take ownership of the file at |crx_path|.
CHECK(source->GetResponseAsFilePath(true, &crx_path));
RecordCRXWriteHistogram(true, crx_path);
OnCRXFileWritten(current_extension_fetch_.id, crx_path, url);
}
} else {
// TODO(asargent) do things like exponential backoff, handling
// 503 Service Unavailable / Retry-After headers, etc. here.
// (http://crbug.com/12546).
VLOG(1) << "Failed to fetch extension '" << url.possibly_invalid_spec()
<< "' response code:" << response_code;
}
extension_fetcher_.reset();
current_extension_fetch_ = ExtensionFetch();
// If there are any pending downloads left, start the next one.
if (!extensions_pending_.empty()) {
ExtensionFetch next = extensions_pending_.front();
extensions_pending_.pop_front();
FetchUpdatedExtension(next.id, next.url, next.package_hash, next.version);
}
}
void ExtensionUpdater::OnCRXFileWritten(const std::string& id,
const FilePath& path,
const GURL& download_url) {
DCHECK(alive_);
FetchedCRXFile fetched(id, path, download_url);
fetched_crx_files_.push(fetched);
MaybeInstallCRXFile();
}
bool ExtensionUpdater::MaybeInstallCRXFile() {
if (crx_install_is_running_)
return false;
while (!fetched_crx_files_.empty() && !crx_install_is_running_) {
const FetchedCRXFile& crx_file = fetched_crx_files_.top();
// The ExtensionService is now responsible for cleaning up the temp file
// at |extension_file.path|.
CrxInstaller* installer = NULL;
if (service_->UpdateExtension(crx_file.id,
crx_file.path,
crx_file.download_url,
&installer)) {
crx_install_is_running_ = true;
// Source parameter ensures that we only see the completion event for the
// the installer we started.
registrar_.Add(this,
chrome::NOTIFICATION_CRX_INSTALLER_DONE,
content::Source<CrxInstaller>(installer));
}
in_progress_ids_.erase(crx_file.id);
fetched_crx_files_.pop();
}
// If an updater is running, it was started above.
return crx_install_is_running_;
}
void ExtensionUpdater::Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
DCHECK(type == chrome::NOTIFICATION_CRX_INSTALLER_DONE);
// No need to listen for CRX_INSTALLER_DONE anymore.
registrar_.Remove(this,
chrome::NOTIFICATION_CRX_INSTALLER_DONE,
source);
crx_install_is_running_ = false;
// If any files are available to update, start one.
MaybeInstallCRXFile();
}
void ExtensionUpdater::OnCRXFileWriteError(const std::string& id) {
DCHECK(alive_);
in_progress_ids_.erase(id);
}
void ExtensionUpdater::ScheduleNextCheck(const TimeDelta& target_delay) {
DCHECK(alive_);
DCHECK(!timer_.IsRunning());
DCHECK(target_delay >= TimeDelta::FromSeconds(1));
// Add +/- 10% random jitter.
double delay_ms = target_delay.InMillisecondsF();
double jitter_factor = (RandDouble() * .2) - 0.1;
delay_ms += delay_ms * jitter_factor;
TimeDelta actual_delay = TimeDelta::FromMilliseconds(
static_cast<int64>(delay_ms));
// Save the time of next check.
Time next = Time::Now() + actual_delay;
prefs_->SetInt64(kNextExtensionsUpdateCheck, next.ToInternalValue());
prefs_->ScheduleSavePersistentPrefs();
timer_.Start(FROM_HERE, actual_delay, this, &ExtensionUpdater::TimerFired);
}
void ExtensionUpdater::TimerFired() {
DCHECK(alive_);
CheckNow();
// If the user has overridden the update frequency, don't bother reporting
// this.
if (frequency_seconds_ == ExtensionService::kDefaultUpdateFrequencySeconds) {
Time last = Time::FromInternalValue(prefs_->GetInt64(
kLastExtensionsUpdateCheck));
if (last.ToInternalValue() != 0) {
// Use counts rather than time so we can use minutes rather than millis.
UMA_HISTOGRAM_CUSTOM_COUNTS("Extensions.UpdateCheckGap",
(Time::Now() - last).InMinutes(),
base::TimeDelta::FromSeconds(kStartupWaitSeconds).InMinutes(),
base::TimeDelta::FromDays(40).InMinutes(),
50); // 50 buckets seems to be the default.
}
}
// Save the last check time, and schedule the next check.
int64 now = Time::Now().ToInternalValue();
prefs_->SetInt64(kLastExtensionsUpdateCheck, now);
ScheduleNextCheck(TimeDelta::FromSeconds(frequency_seconds_));
}
void ExtensionUpdater::CheckSoon() {
DCHECK(alive_);
if (will_check_soon_) {
return;
}
if (BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&ExtensionUpdater::DoCheckSoon,
weak_ptr_factory_.GetWeakPtr()))) {
will_check_soon_ = true;
} else {
NOTREACHED();
}
}
bool ExtensionUpdater::WillCheckSoon() const {
return will_check_soon_;
}
void ExtensionUpdater::DoCheckSoon() {
DCHECK(will_check_soon_);
CheckNow();
will_check_soon_ = false;
}
void ExtensionUpdater::CheckNow() {
DCHECK(alive_);
NotifyStarted();
ManifestFetchesBuilder fetches_builder(service_, extension_prefs_);
const PendingExtensionManager* pending_extension_manager =
service_->pending_extension_manager();
std::set<std::string> pending_ids;
PendingExtensionManager::const_iterator iter;
for (iter = pending_extension_manager->begin();
iter != pending_extension_manager->end(); iter++) {
// TODO(skerner): Move the determination of what gets fetched into
// class PendingExtensionManager.
Extension::Location location = iter->second.install_source();
if (location != Extension::EXTERNAL_PREF &&
location != Extension::EXTERNAL_REGISTRY) {
fetches_builder.AddPendingExtension(iter->first, iter->second);
pending_ids.insert(iter->first);
}
}
const ExtensionList* extensions = service_->extensions();
for (ExtensionList::const_iterator iter = extensions->begin();
iter != extensions->end(); ++iter) {
// An extension might be overwritten by policy, and have its update url
// changed. Make sure existing extensions aren't fetched again, if a
// pending fetch for an extension with the same id already exists.
if (!ContainsKey(pending_ids, (*iter)->id())) {
fetches_builder.AddExtension(**iter);
}
}
fetches_builder.ReportStats();
std::vector<ManifestFetchData*> fetches(fetches_builder.GetFetches());
// Start a fetch of the blacklist if needed.
if (blacklist_checks_enabled_) {
// Note: it is very important that we use the https version of the update
// url here to avoid DNS hijacking of the blacklist, which is not validated
// by a public key signature like .crx files are.
ManifestFetchData* blacklist_fetch =
new ManifestFetchData(extension_urls::GetWebstoreUpdateUrl(true));
std::string version = prefs_->GetString(kExtensionBlacklistUpdateVersion);
ManifestFetchData::PingData ping_data;
ping_data.rollcall_days =
CalculatePingDays(extension_prefs_->BlacklistLastPingDay());
blacklist_fetch->AddExtension(kBlacklistAppID, version, ping_data, "");
StartUpdateCheck(blacklist_fetch);
}
// Now start fetching regular extension updates
for (std::vector<ManifestFetchData*>::const_iterator it = fetches.begin();
it != fetches.end(); ++it) {
// StartUpdateCheck makes sure the url isn't already downloading or
// scheduled, so we don't need to check before calling it. Ownership of
// fetch is transferred here.
StartUpdateCheck(*it);
}
// We don't want to use fetches after this since StartUpdateCheck()
// takes ownership of its argument.
fetches.clear();
NotifyIfFinished();
}
bool ExtensionUpdater::GetExistingVersion(const std::string& id,
std::string* version) {
DCHECK(alive_);
if (id == kBlacklistAppID) {
*version = prefs_->GetString(kExtensionBlacklistUpdateVersion);
return true;
}
const Extension* extension = service_->GetExtensionById(id, false);
if (!extension) {
return false;
}
*version = extension->version()->GetString();
return true;
}
std::vector<int> ExtensionUpdater::DetermineUpdates(
const ManifestFetchData& fetch_data,
const UpdateManifest::Results& possible_updates) {
DCHECK(alive_);
std::vector<int> result;
// This will only be valid if one of possible_updates specifies
// browser_min_version.
Version browser_version;
PendingExtensionManager* pending_extension_manager =
service_->pending_extension_manager();
for (size_t i = 0; i < possible_updates.list.size(); i++) {
const UpdateManifest::Result* update = &possible_updates.list[i];
if (!fetch_data.Includes(update->extension_id))
continue;
if (!pending_extension_manager->IsIdPending(update->extension_id)) {
// If we're not installing pending extension, and the update
// version is the same or older than what's already installed,
// we don't want it.
std::string version;
if (!GetExistingVersion(update->extension_id, &version))
continue;
Version existing_version(version);
Version update_version(update->version);
if (!update_version.IsValid() ||
update_version.CompareTo(existing_version) <= 0) {
continue;
}
}
// If the update specifies a browser minimum version, do we qualify?
if (update->browser_min_version.length() > 0) {
// First determine the browser version if we haven't already.
if (!browser_version.IsValid()) {
chrome::VersionInfo version_info;
if (version_info.is_valid())
browser_version = Version(version_info.Version());
}
Version browser_min_version(update->browser_min_version);
if (browser_version.IsValid() && browser_min_version.IsValid() &&
browser_min_version.CompareTo(browser_version) > 0) {
// TODO(asargent) - We may want this to show up in the extensions UI
// eventually. (http://crbug.com/12547).
LOG(WARNING) << "Updated version of extension " << update->extension_id
<< " available, but requires chrome version "
<< update->browser_min_version;
continue;
}
}
result.push_back(i);
}
return result;
}
void ExtensionUpdater::StartUpdateCheck(ManifestFetchData* fetch_data) {
AddToInProgress(fetch_data->extension_ids());
scoped_ptr<ManifestFetchData> scoped_fetch_data(fetch_data);
if (CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisableBackgroundNetworking))
return;
std::deque<ManifestFetchData*>::const_iterator i;
for (i = manifests_pending_.begin(); i != manifests_pending_.end(); i++) {
if (fetch_data->full_url() == (*i)->full_url()) {
// This url is already scheduled to be fetched.
return;
}
}
if (manifest_fetcher_.get() != NULL) {
if (manifest_fetcher_->url() != fetch_data->full_url()) {
manifests_pending_.push_back(scoped_fetch_data.release());
}
} else {
UMA_HISTOGRAM_COUNTS("Extensions.UpdateCheckUrlLength",
fetch_data->full_url().possibly_invalid_spec().length());
current_manifest_fetch_.swap(scoped_fetch_data);
manifest_fetcher_.reset(
URLFetcher::Create(kManifestFetcherId, fetch_data->full_url(),
URLFetcher::GET, this));
manifest_fetcher_->set_request_context(
profile_->GetRequestContext());
manifest_fetcher_->set_load_flags(net::LOAD_DO_NOT_SEND_COOKIES |
net::LOAD_DO_NOT_SAVE_COOKIES |
net::LOAD_DISABLE_CACHE);
manifest_fetcher_->Start();
}
}
void ExtensionUpdater::FetchUpdatedExtension(const std::string& id,
const GURL& url,
const std::string& hash,
const std::string& version) {
for (std::deque<ExtensionFetch>::const_iterator iter =
extensions_pending_.begin();
iter != extensions_pending_.end(); ++iter) {
if (iter->id == id || iter->url == url) {
return; // already scheduled
}
}
if (extension_fetcher_.get() != NULL) {
if (extension_fetcher_->url() != url) {
extensions_pending_.push_back(ExtensionFetch(id, url, hash, version));
}
} else {
extension_fetcher_.reset(
URLFetcher::Create(kExtensionFetcherId, url, URLFetcher::GET, this));
extension_fetcher_->set_request_context(
profile_->GetRequestContext());
extension_fetcher_->set_load_flags(net::LOAD_DO_NOT_SEND_COOKIES |
net::LOAD_DO_NOT_SAVE_COOKIES |
net::LOAD_DISABLE_CACHE);
// Download CRX files to a temp file. The blacklist is small and will be
// processed in memory, so it is fetched into a string.
if (id != ExtensionUpdater::kBlacklistAppID) {
extension_fetcher_->SaveResponseToTemporaryFile(
BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE));
}
extension_fetcher_->Start();
current_extension_fetch_ = ExtensionFetch(id, url, hash, version);
}
}
void ExtensionUpdater::NotifyStarted() {
content::NotificationService::current()->Notify(
chrome::NOTIFICATION_EXTENSION_UPDATING_STARTED,
content::Source<Profile>(profile_),
content::NotificationService::NoDetails());
}
void ExtensionUpdater::NotifyUpdateFound(const std::string& extension_id) {
content::NotificationService::current()->Notify(
chrome::NOTIFICATION_EXTENSION_UPDATE_FOUND,
content::Source<Profile>(profile_),
content::Details<const std::string>(&extension_id));
}
void ExtensionUpdater::NotifyIfFinished() {
if (in_progress_ids_.empty()) {
content::NotificationService::current()->Notify(
chrome::NOTIFICATION_EXTENSION_UPDATING_FINISHED,
content::Source<Profile>(profile_),
content::NotificationService::NoDetails());
VLOG(1) << "Sending EXTENSION_UPDATING_FINISHED";
}
}
void ExtensionUpdater::AddToInProgress(const std::set<std::string>& ids) {
std::set<std::string>::const_iterator i;
for (i = ids.begin(); i != ids.end(); ++i)
in_progress_ids_.insert(*i);
}
void ExtensionUpdater::RemoveFromInProgress(const std::set<std::string>& ids) {
std::set<std::string>::const_iterator i;
for (i = ids.begin(); i != ids.end(); ++i)
in_progress_ids_.erase(*i);
}