| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/extensions/updater/extension_updater.h" |
| |
| #include <stdint.h> |
| |
| #include <algorithm> |
| #include <set> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/containers/contains.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/logging.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/rand_util.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_split.h" |
| #include "chrome/browser/chrome_notification_types.h" |
| #include "chrome/browser/extensions/crx_installer.h" |
| #include "chrome/browser/extensions/extension_management.h" |
| #include "chrome/browser/extensions/extension_service.h" |
| #include "chrome/browser/extensions/forced_extensions/install_stage_tracker.h" |
| #include "chrome/browser/extensions/pending_extension_manager.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/profiles/profile_keep_alive_types.h" |
| #include "chrome/browser/profiles/scoped_profile_keep_alive.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/update_client/update_query_params.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/notification_details.h" |
| #include "content/public/browser/notification_service.h" |
| #include "content/public/browser/notification_source.h" |
| #include "crypto/sha2.h" |
| #include "extensions/browser/extension_file_task_runner.h" |
| #include "extensions/browser/extension_prefs.h" |
| #include "extensions/browser/extension_registry.h" |
| #include "extensions/browser/pref_names.h" |
| #include "extensions/browser/updater/extension_cache.h" |
| #include "extensions/browser/updater/extension_update_data.h" |
| #include "extensions/common/constants.h" |
| #include "extensions/common/extension.h" |
| #include "extensions/common/extension_set.h" |
| #include "extensions/common/extension_updater_uma.h" |
| #include "extensions/common/extension_urls.h" |
| #include "extensions/common/manifest.h" |
| #include "extensions/common/manifest_constants.h" |
| #include "extensions/common/manifest_url_handlers.h" |
| |
| using base::RandDouble; |
| using base::RandInt; |
| typedef extensions::ExtensionDownloaderDelegate::Error Error; |
| typedef extensions::ExtensionDownloaderDelegate::PingResult PingResult; |
| |
| namespace { |
| |
| bool g_should_immediately_update = false; |
| |
| // For sanity checking on update frequency - enforced in release mode only. |
| #if defined(NDEBUG) |
| const int kMinUpdateFrequencySeconds = 30; |
| #endif |
| const int kMaxUpdateFrequencySeconds = 60 * 60 * 24 * 7; // 7 days |
| |
| bool g_skip_scheduled_checks_for_tests = false; |
| |
| bool g_force_use_update_service_for_tests = false; |
| |
| // 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. |
| int SanitizeDays(int days) { |
| if (days < 0) |
| return 0; |
| return days; |
| } |
| |
| // Calculates the value to use for the ping days parameter. |
| int CalculatePingDaysForExtension(const base::Time& last_ping_day) { |
| int days = extensions::ManifestFetchData::kNeverPinged; |
| if (!last_ping_day.is_null()) { |
| days = SanitizeDays((base::Time::Now() - last_ping_day).InDays()); |
| } |
| return days; |
| } |
| |
| int CalculateActivePingDays(const base::Time& last_active_ping_day, |
| bool hasActiveBit) { |
| if (!hasActiveBit) |
| return 0; |
| if (last_active_ping_day.is_null()) |
| return extensions::ManifestFetchData::kNeverPinged; |
| return SanitizeDays((base::Time::Now() - last_active_ping_day).InDays()); |
| } |
| |
| std::string GetUpdateURLData(const extensions::ExtensionPrefs* prefs, |
| const std::string& extension_id) { |
| std::string data; |
| prefs->ReadPrefAsString(extension_id, extensions::kUpdateURLData, &data); |
| return data; |
| } |
| |
| } // namespace |
| |
| namespace extensions { |
| |
| ExtensionUpdater::CheckParams::CheckParams() = default; |
| |
| ExtensionUpdater::CheckParams::~CheckParams() = default; |
| |
| ExtensionUpdater::CheckParams::CheckParams( |
| ExtensionUpdater::CheckParams&& other) = default; |
| ExtensionUpdater::CheckParams& ExtensionUpdater::CheckParams::operator=( |
| ExtensionUpdater::CheckParams&& other) = default; |
| |
| ExtensionUpdater::FetchedCRXFile::FetchedCRXFile( |
| const CRXFileInfo& file, |
| bool file_ownership_passed, |
| const std::set<int>& request_ids, |
| InstallCallback callback) |
| : info(file), |
| file_ownership_passed(file_ownership_passed), |
| request_ids(request_ids), |
| callback(std::move(callback)) {} |
| |
| ExtensionUpdater::FetchedCRXFile::FetchedCRXFile() |
| : file_ownership_passed(true) {} |
| |
| ExtensionUpdater::FetchedCRXFile::FetchedCRXFile(FetchedCRXFile&& other) = |
| default; |
| |
| ExtensionUpdater::FetchedCRXFile& ExtensionUpdater::FetchedCRXFile::operator=( |
| FetchedCRXFile&& other) = default; |
| |
| ExtensionUpdater::FetchedCRXFile::~FetchedCRXFile() = default; |
| |
| ExtensionUpdater::InProgressCheck::InProgressCheck() = default; |
| |
| ExtensionUpdater::InProgressCheck::~InProgressCheck() = default; |
| |
| ExtensionUpdater::ExtensionUpdater( |
| ExtensionServiceInterface* service, |
| ExtensionPrefs* extension_prefs, |
| PrefService* prefs, |
| Profile* profile, |
| int frequency_seconds, |
| ExtensionCache* cache, |
| const ExtensionDownloader::Factory& downloader_factory) |
| : service_(service), |
| downloader_factory_(downloader_factory), |
| frequency_(base::Seconds(frequency_seconds)), |
| extension_prefs_(extension_prefs), |
| prefs_(prefs), |
| profile_(profile), |
| registry_(ExtensionRegistry::Get(profile)), |
| extension_cache_(cache) { |
| DCHECK_LE(frequency_seconds, kMaxUpdateFrequencySeconds); |
| #if defined(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); |
| frequency_ = base::Seconds(frequency_seconds); |
| } |
| |
| ExtensionUpdater::~ExtensionUpdater() { |
| Stop(); |
| } |
| |
| void ExtensionUpdater::EnsureDownloaderCreated() { |
| if (!downloader_.get()) { |
| downloader_ = downloader_factory_.Run(this); |
| } |
| if (!update_service_) { |
| update_service_ = UpdateService::Get(profile_); |
| } |
| } |
| |
| 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()); |
| DCHECK(registry_); |
| alive_ = true; |
| // Check soon, and set up the first delayed check. |
| if (!g_skip_scheduled_checks_for_tests) { |
| if (g_should_immediately_update) |
| CheckNow({}); |
| else |
| CheckSoon(); |
| ScheduleNextCheck(); |
| } |
| } |
| |
| void ExtensionUpdater::Stop() { |
| weak_ptr_factory_.InvalidateWeakPtrs(); |
| alive_ = false; |
| service_ = nullptr; |
| extension_prefs_ = nullptr; |
| prefs_ = nullptr; |
| profile_ = nullptr; |
| will_check_soon_ = false; |
| downloader_.reset(); |
| update_service_ = nullptr; |
| registry_ = nullptr; |
| } |
| |
| void ExtensionUpdater::ScheduleNextCheck() { |
| DCHECK(alive_); |
| // Jitter the frequency by +/- 20%. |
| const double jitter_factor = RandDouble() * 0.4 + 0.8; |
| base::TimeDelta delay = base::Milliseconds( |
| static_cast<int64_t>(frequency_.InMilliseconds() * jitter_factor)); |
| content::GetUIThreadTaskRunner({base::TaskPriority::BEST_EFFORT}) |
| ->PostDelayedTask(FROM_HERE, |
| base::BindOnce(&ExtensionUpdater::NextCheck, |
| weak_ptr_factory_.GetWeakPtr()), |
| delay); |
| } |
| |
| void ExtensionUpdater::NextCheck() { |
| if (!alive_) |
| return; |
| CheckNow(CheckParams()); |
| ScheduleNextCheck(); |
| } |
| |
| void ExtensionUpdater::CheckSoon() { |
| DCHECK(alive_); |
| if (will_check_soon_) |
| return; |
| if (content::GetUIThreadTaskRunner({base::TaskPriority::BEST_EFFORT}) |
| ->PostTask(FROM_HERE, |
| base::BindOnce(&ExtensionUpdater::DoCheckSoon, |
| weak_ptr_factory_.GetWeakPtr()))) { |
| will_check_soon_ = true; |
| } else { |
| NOTREACHED(); |
| } |
| } |
| |
| bool ExtensionUpdater::WillCheckSoon() const { |
| return will_check_soon_; |
| } |
| |
| void ExtensionUpdater::SetExtensionCacheForTesting( |
| ExtensionCache* extension_cache) { |
| extension_cache_ = extension_cache; |
| } |
| |
| void ExtensionUpdater::SetExtensionDownloaderForTesting( |
| std::unique_ptr<ExtensionDownloader> downloader) { |
| downloader_.swap(downloader); |
| } |
| |
| // static |
| void ExtensionUpdater::UpdateImmediatelyForFirstRun() { |
| g_should_immediately_update = true; |
| } |
| |
| void ExtensionUpdater::SetBackoffPolicyForTesting( |
| const net::BackoffEntry::Policy* backoff_policy) { |
| EnsureDownloaderCreated(); |
| downloader_->SetBackoffPolicyForTesting(backoff_policy); |
| } |
| |
| // static |
| base::AutoReset<bool> ExtensionUpdater::GetScopedUseUpdateServiceForTesting() { |
| return base::AutoReset<bool>(&g_force_use_update_service_for_tests, true); |
| } |
| |
| void ExtensionUpdater::DoCheckSoon() { |
| if (!will_check_soon_) { |
| // Another caller called CheckNow() between CheckSoon() and now. Skip this |
| // check. |
| return; |
| } |
| CheckNow(CheckParams()); |
| } |
| |
| void ExtensionUpdater::AddToDownloader( |
| const ExtensionSet* extensions, |
| const std::list<ExtensionId>& pending_ids, |
| int request_id, |
| ManifestFetchData::FetchPriority fetch_priority, |
| ExtensionUpdateCheckParams* update_check_params) { |
| DCHECK(update_service_); |
| InProgressCheck& request = requests_in_progress_[request_id]; |
| for (ExtensionSet::const_iterator extension_iter = extensions->begin(); |
| extension_iter != extensions->end(); ++extension_iter) { |
| const Extension& extension = **extension_iter; |
| const ExtensionId& extension_id = extension.id(); |
| if (!Manifest::IsAutoUpdateableLocation(extension.location())) { |
| VLOG(2) << "Extension " << extension_id << " is not auto updateable"; |
| continue; |
| } |
| // 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 (!base::Contains(pending_ids, extension_id)) { |
| if (CanUseUpdateService(extension_id)) { |
| update_check_params->update_info[extension_id] = ExtensionUpdateData(); |
| } else if (AddExtensionToDownloader(extension, request_id, |
| fetch_priority)) { |
| request.in_progress_ids.insert(extension_id); |
| } |
| } |
| } |
| } |
| |
| bool ExtensionUpdater::AddExtensionToDownloader( |
| const Extension& extension, |
| int request_id, |
| ManifestFetchData::FetchPriority fetch_priority) { |
| ExtensionManagement* extension_management = |
| ExtensionManagementFactory::GetForBrowserContext(profile_); |
| GURL update_url = extension_management->GetEffectiveUpdateURL(extension); |
| // Skip extensions with empty update URLs converted from user |
| // scripts. |
| if (extension.converted_from_user_script() && update_url.is_empty()) { |
| return false; |
| } |
| |
| DCHECK(alive_); |
| |
| // 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 gallery update servers. |
| std::string update_url_data; |
| if (!ManifestURL::UpdatesFromGallery(&extension)) |
| update_url_data = GetUpdateURLData(extension_prefs_, extension.id()); |
| |
| return downloader_->AddPendingExtensionWithVersion( |
| extension.id(), update_url, extension.location(), |
| false /*is_corrupt_reinstall*/, request_id, fetch_priority, |
| extension.version(), extension.GetType(), update_url_data); |
| } |
| |
| void ExtensionUpdater::CheckNow(CheckParams params) { |
| if (params.ids.empty()) { |
| // Checking all extensions. Cancel pending DoCheckSoon() call if there's |
| // one, as it would be redundant. |
| will_check_soon_ = false; |
| } |
| |
| int request_id = next_request_id_++; |
| |
| VLOG(2) << "Starting update check " << request_id; |
| if (params.ids.empty()) |
| NotifyStarted(); |
| |
| DCHECK(alive_); |
| |
| InProgressCheck& request = requests_in_progress_[request_id]; |
| request.callback = std::move(params.callback); |
| request.install_immediately = params.install_immediately; |
| request.profile_keep_alive = std::make_unique<ScopedProfileKeepAlive>( |
| profile_, ProfileKeepAliveOrigin::kExtensionUpdater); |
| |
| EnsureDownloaderCreated(); |
| |
| // Add fetch records for extensions that should be fetched by an update URL. |
| // These extensions are not yet installed. They come from group policy |
| // and external install sources. |
| const PendingExtensionManager* pending_extension_manager = |
| service_->pending_extension_manager(); |
| |
| ExtensionUpdateCheckParams update_check_params; |
| |
| if (params.ids.empty()) { |
| std::list<ExtensionId> pending_ids = |
| pending_extension_manager->GetPendingIdsForUpdateCheck(); |
| // If no extension ids are specified, check for updates for all extensions. |
| |
| for (const ExtensionId& pending_id : pending_ids) { |
| const PendingExtensionInfo* info = |
| pending_extension_manager->GetById(pending_id); |
| |
| const bool is_corrupt_reinstall = |
| pending_extension_manager->IsReinstallForCorruptionExpected( |
| pending_id); |
| |
| // Extensions from the webstore that are corrupted do not have |
| // PendingExtensionInfo but are still available in the extension registry. |
| // They should be disabled because they are corrupted and require to be |
| // repaired. |
| if (!info) { |
| const Extension* extension = registry_->GetExtensionById( |
| pending_id, extensions::ExtensionRegistry::EVERYTHING); |
| |
| // It is possible that the user deletes the extension between the time |
| // it was detected as corrupted and now. In that case, `extension` will |
| // be null and we should just skip it. |
| if (!extension) |
| continue; |
| // Policy installed extensions are not necessarily from the webstore, |
| // but should have an `info` and never hit this path. |
| DCHECK(extension->from_webstore()) << "Extension with id " << pending_id |
| << " is not from the webstore"; |
| DCHECK(is_corrupt_reinstall) << "Extension with id " << pending_id |
| << " is not a corrupt reinstall"; |
| update_check_params.update_info[pending_id] = ExtensionUpdateData(); |
| } else if (!Manifest::IsAutoUpdateableLocation(info->install_source())) { |
| VLOG(2) << "Extension " << pending_id << " is not auto updateable"; |
| continue; |
| } |
| // We have to mark high-priority extensions (such as policy-forced |
| // extensions or external component extensions) with foreground fetch |
| // priority; otherwise their installation may be throttled by bandwidth |
| // limits. |
| // See https://crbug.com/904600 and https://crbug.com/965686. |
| const bool is_high_priority_extension_pending = |
| pending_extension_manager->HasHighPriorityPendingExtension(); |
| if (CanUseUpdateService(pending_id)) { |
| update_check_params.update_info[pending_id].is_corrupt_reinstall = |
| is_corrupt_reinstall; |
| } else if (info && |
| downloader_->AddPendingExtension( |
| pending_id, info->update_url(), info->install_source(), |
| is_corrupt_reinstall, request_id, |
| is_high_priority_extension_pending |
| ? ManifestFetchData::FOREGROUND |
| : params.fetch_priority)) { |
| request.in_progress_ids.insert(pending_id); |
| InstallStageTracker::Get(profile_)->ReportInstallationStage( |
| pending_id, InstallStageTracker::Stage::DOWNLOADING); |
| } else { |
| InstallStageTracker::Get(profile_)->ReportFailure( |
| pending_id, |
| InstallStageTracker::FailureReason::DOWNLOADER_ADD_FAILED); |
| } |
| } |
| |
| AddToDownloader(®istry_->enabled_extensions(), pending_ids, request_id, |
| params.fetch_priority, &update_check_params); |
| AddToDownloader(®istry_->disabled_extensions(), pending_ids, request_id, |
| params.fetch_priority, &update_check_params); |
| ExtensionSet remotely_disabled_extensions; |
| for (auto extension : registry_->blocklisted_extensions()) { |
| if (extension_prefs_->HasDisableReason( |
| extension->id(), disable_reason::DISABLE_REMOTELY_FOR_MALWARE)) |
| remotely_disabled_extensions.Insert(extension); |
| } |
| AddToDownloader(&remotely_disabled_extensions, pending_ids, request_id, |
| params.fetch_priority, &update_check_params); |
| } else { |
| for (const ExtensionId& id : params.ids) { |
| const Extension* extension = registry_->GetExtensionById( |
| id, extensions::ExtensionRegistry::EVERYTHING); |
| if (extension) { |
| if (CanUseUpdateService(id)) { |
| update_check_params.update_info[id] = ExtensionUpdateData(); |
| } else if (AddExtensionToDownloader(*extension, request_id, |
| params.fetch_priority)) { |
| request.in_progress_ids.insert(extension->id()); |
| } |
| } |
| } |
| } |
| |
| // StartAllPending() might call OnExtensionDownloadFailed/Finished before |
| // it returns, which would cause NotifyIfFinished to incorrectly try to |
| // send out a notification. So check before we call StartAllPending if any |
| // extensions are going to be updated, and use that to figure out if |
| // NotifyIfFinished should be called. |
| bool empty_downloader = request.in_progress_ids.empty(); |
| bool awaiting_update_service = !update_check_params.update_info.empty(); |
| |
| request.awaiting_update_service = awaiting_update_service; |
| |
| // StartAllPending() will call OnExtensionDownloadFailed or |
| // OnExtensionDownloadFinished for each extension that was checked. |
| downloader_->StartAllPending(extension_cache_); |
| |
| if (awaiting_update_service) { |
| update_check_params.priority = |
| params.fetch_priority == ManifestFetchData::FetchPriority::BACKGROUND |
| ? ExtensionUpdateCheckParams::UpdateCheckPriority::BACKGROUND |
| : ExtensionUpdateCheckParams::UpdateCheckPriority::FOREGROUND; |
| update_check_params.install_immediately = params.install_immediately; |
| update_service_->StartUpdateCheck( |
| update_check_params, |
| base::BindOnce(&ExtensionUpdater::OnUpdateServiceFinished, |
| base::Unretained(this), request_id)); |
| } else if (empty_downloader) { |
| NotifyIfFinished(request_id); |
| } |
| } |
| |
| void ExtensionUpdater::OnExtensionDownloadStageChanged(const ExtensionId& id, |
| Stage stage) { |
| InstallStageTracker::Get(profile_)->ReportDownloadingStage(id, stage); |
| } |
| |
| void ExtensionUpdater::OnExtensionDownloadCacheStatusRetrieved( |
| const ExtensionId& id, |
| CacheStatus cache_status) { |
| InstallStageTracker::Get(profile_)->ReportDownloadingCacheStatus( |
| id, cache_status); |
| } |
| |
| void ExtensionUpdater::OnExtensionDownloadFailed( |
| const ExtensionId& id, |
| Error error, |
| const PingResult& ping, |
| const std::set<int>& request_ids, |
| const FailureData& data) { |
| DCHECK(alive_); |
| InstallStageTracker* install_stage_tracker = |
| InstallStageTracker::Get(profile_); |
| |
| switch (error) { |
| case Error::CRX_FETCH_FAILED: |
| install_stage_tracker->ReportFetchError( |
| id, InstallStageTracker::FailureReason::CRX_FETCH_FAILED, data); |
| break; |
| case Error::CRX_FETCH_URL_EMPTY: |
| DCHECK(data.additional_info); |
| install_stage_tracker->ReportInfoOnNoUpdatesFailure( |
| id, data.additional_info.value()); |
| install_stage_tracker->ReportFailure( |
| id, InstallStageTracker::FailureReason::CRX_FETCH_URL_EMPTY); |
| break; |
| case Error::CRX_FETCH_URL_INVALID: |
| install_stage_tracker->ReportFailure( |
| id, InstallStageTracker::FailureReason::CRX_FETCH_URL_INVALID); |
| break; |
| case Error::MANIFEST_FETCH_FAILED: |
| install_stage_tracker->ReportFetchError( |
| id, InstallStageTracker::FailureReason::MANIFEST_FETCH_FAILED, data); |
| break; |
| case Error::MANIFEST_INVALID: |
| DCHECK(data.manifest_invalid_error); |
| install_stage_tracker->ReportManifestInvalidFailure(id, data); |
| break; |
| case Error::NO_UPDATE_AVAILABLE: |
| install_stage_tracker->ReportFailure( |
| id, InstallStageTracker::FailureReason::NO_UPDATE); |
| break; |
| case Error::DISABLED: |
| // Error::DISABLED corresponds to the browser having disabled extension |
| // updates, the extension updater does not actually run when this error |
| // code is emitted. |
| break; |
| } |
| |
| UpdatePingData(id, ping); |
| bool install_immediately = false; |
| for (const int request_id : request_ids) { |
| InProgressCheck& request = requests_in_progress_[request_id]; |
| install_immediately |= request.install_immediately; |
| request.in_progress_ids.erase(id); |
| NotifyIfFinished(request_id); |
| } |
| |
| // This method is called if no updates were found. However a previous update |
| // check might have queued an update for this extension already. If a |
| // current update check has |install_immediately| set the previously |
| // queued update should be installed now. |
| if (install_immediately && service_->GetPendingExtensionUpdate(id)) |
| service_->FinishDelayedInstallationIfReady(id, install_immediately); |
| } |
| |
| void ExtensionUpdater::OnExtensionDownloadRetry(const ExtensionId& id, |
| const FailureData& data) { |
| InstallStageTracker::Get(profile_)->ReportFetchErrorCodes(id, data); |
| } |
| |
| void ExtensionUpdater::OnExtensionDownloadFinished( |
| const CRXFileInfo& file, |
| bool file_ownership_passed, |
| const GURL& download_url, |
| const PingResult& ping, |
| const std::set<int>& request_ids, |
| InstallCallback callback) { |
| DCHECK(alive_); |
| InstallStageTracker::Get(profile_)->ReportInstallationStage( |
| file.extension_id, InstallStageTracker::Stage::INSTALLING); |
| UpdatePingData(file.extension_id, ping); |
| |
| VLOG(2) << download_url << " written to " << file.path.value(); |
| |
| FetchedCRXFile fetched(file, file_ownership_passed, request_ids, |
| std::move(callback)); |
| // InstallCRXFile() removes extensions from |in_progress_ids| after starting |
| // the crx installer. |
| InstallCRXFile(std::move(fetched)); |
| } |
| |
| bool ExtensionUpdater::GetPingDataForExtension( |
| const ExtensionId& id, |
| ManifestFetchData::PingData* ping_data) { |
| DCHECK(alive_); |
| ping_data->rollcall_days = |
| CalculatePingDaysForExtension(extension_prefs_->LastPingDay(id)); |
| ping_data->is_enabled = service_->IsExtensionEnabled(id); |
| if (!ping_data->is_enabled) |
| ping_data->disable_reasons = extension_prefs_->GetDisableReasons(id); |
| ping_data->active_days = |
| CalculateActivePingDays(extension_prefs_->LastActivePingDay(id), |
| extension_prefs_->GetActiveBit(id)); |
| return true; |
| } |
| |
| bool ExtensionUpdater::IsExtensionPending(const ExtensionId& id) { |
| DCHECK(alive_); |
| return service_->pending_extension_manager()->IsIdPending(id); |
| } |
| |
| bool ExtensionUpdater::GetExtensionExistingVersion(const ExtensionId& id, |
| std::string* version) { |
| DCHECK(alive_); |
| const Extension* extension = registry_->GetExtensionById( |
| id, extensions::ExtensionRegistry::EVERYTHING); |
| if (!extension) |
| return false; |
| const Extension* update = service_->GetPendingExtensionUpdate(id); |
| if (update) |
| *version = update->VersionString(); |
| else |
| *version = extension->VersionString(); |
| return true; |
| } |
| |
| void ExtensionUpdater::UpdatePingData(const ExtensionId& id, |
| const PingResult& ping_result) { |
| DCHECK(alive_); |
| if (ping_result.did_ping) |
| extension_prefs_->SetLastPingDay(id, ping_result.day_start); |
| if (extension_prefs_->GetActiveBit(id)) { |
| extension_prefs_->SetActiveBit(id, false); |
| extension_prefs_->SetLastActivePingDay(id, ping_result.day_start); |
| } |
| } |
| |
| void ExtensionUpdater::PutExtensionInCache(const CRXFileInfo& crx_info) { |
| if (extension_cache_) { |
| const ExtensionId& extension_id = crx_info.extension_id; |
| const base::Version& expected_version = crx_info.expected_version; |
| const std::string& expected_hash = crx_info.expected_hash; |
| const base::FilePath& crx_path = crx_info.path; |
| DCHECK(expected_version.IsValid()); |
| extension_cache_->PutExtension( |
| extension_id, expected_hash, crx_path, expected_version.GetString(), |
| base::BindRepeating(&ExtensionUpdater::CleanUpCrxFileIfNeeded, |
| weak_ptr_factory_.GetWeakPtr())); |
| } else { |
| CleanUpCrxFileIfNeeded(crx_info.path, true); |
| } |
| } |
| |
| void ExtensionUpdater::CleanUpCrxFileIfNeeded(const base::FilePath& crx_path, |
| bool file_ownership_passed) { |
| if (file_ownership_passed && |
| !GetExtensionFileTaskRunner()->PostTask( |
| FROM_HERE, base::BindOnce(base::GetDeleteFileCallback(), crx_path))) { |
| NOTREACHED(); |
| } |
| } |
| |
| bool ExtensionUpdater::CanUseUpdateService( |
| const ExtensionId& extension_id) const { |
| if (g_force_use_update_service_for_tests) |
| return true; |
| |
| // Won't update extensions with empty IDs. |
| if (extension_id.empty()) |
| return false; |
| |
| // Update service can only update extensions that have been installed on the |
| // system. |
| const Extension* extension = registry_->GetInstalledExtension(extension_id); |
| if (extension == nullptr) |
| return false; |
| |
| // Furthermore, we can only update extensions that were installed from the |
| // default webstore or extensions with empty update URLs not converted from |
| // user scripts. |
| ExtensionManagement* extension_management = |
| ExtensionManagementFactory::GetForBrowserContext(profile_); |
| const GURL& update_url = |
| extension_management->GetEffectiveUpdateURL(*extension); |
| if (update_url.is_empty()) |
| return !extension->converted_from_user_script(); |
| return extension_urls::IsWebstoreUpdateUrl(update_url); |
| } |
| |
| void ExtensionUpdater::InstallCRXFile(FetchedCRXFile crx_file) { |
| std::set<int> request_ids; |
| |
| VLOG(2) << "updating " << crx_file.info.extension_id << " with " |
| << crx_file.info.path.value(); |
| |
| // The ExtensionService is now responsible for cleaning up the temp file |
| // at |crx_file.info.path|. |
| CrxInstaller* installer = nullptr; |
| if (service_->UpdateExtension(crx_file.info, crx_file.file_ownership_passed, |
| &installer)) { |
| // If the crx file passes the expectations from the update manifest, this |
| // callback inserts an entry in the extension cache and deletes it, if |
| // required. |
| installer->set_expectations_verified_callback( |
| base::BindOnce(&ExtensionUpdater::PutExtensionInCache, |
| weak_ptr_factory_.GetWeakPtr(), crx_file.info)); |
| |
| for (const int request_id : crx_file.request_ids) { |
| InProgressCheck& request = requests_in_progress_[request_id]; |
| if (request.install_immediately) { |
| installer->set_install_immediately(true); |
| break; |
| } |
| } |
| |
| running_crx_installs_[installer] = std::move(crx_file); |
| |
| // Source parameter ensures that we only see the completion event for an |
| // installer we started. |
| registrar_.Add(this, NOTIFICATION_CRX_INSTALLER_DONE, |
| content::Source<CrxInstaller>(installer)); |
| } else { |
| for (const int request_id : crx_file.request_ids) { |
| InProgressCheck& request = requests_in_progress_[request_id]; |
| request.in_progress_ids.erase(crx_file.info.extension_id); |
| } |
| request_ids.insert(crx_file.request_ids.begin(), |
| crx_file.request_ids.end()); |
| } |
| |
| for (const int request_id : request_ids) |
| NotifyIfFinished(request_id); |
| } |
| |
| void ExtensionUpdater::Observe(int type, |
| const content::NotificationSource& source, |
| const content::NotificationDetails& details) { |
| DCHECK_EQ(NOTIFICATION_CRX_INSTALLER_DONE, type); |
| |
| registrar_.Remove(this, NOTIFICATION_CRX_INSTALLER_DONE, source); |
| |
| // If installing this file didn't succeed, we may need to re-download it. |
| const Extension* extension = content::Details<const Extension>(details).ptr(); |
| |
| CrxInstaller* installer = content::Source<CrxInstaller>(source).ptr(); |
| auto iter = running_crx_installs_.find(installer); |
| DCHECK(iter != running_crx_installs_.end()); |
| FetchedCRXFile& crx_file = iter->second; |
| if (!extension && installer->verification_check_failed() && |
| !crx_file.callback.is_null()) { |
| // If extension downloader asked us to notify it about failed installations, |
| // it will resume a pending download from the manifest data it has already |
| // fetched and call us again with the same request_id's (with either |
| // OnExtensionDownloadFailed or OnExtensionDownloadFinished). For that |
| // reason we don't notify finished requests yet. |
| std::move(crx_file.callback).Run(true); |
| } else { |
| for (const int request_id : crx_file.request_ids) { |
| InProgressCheck& request = requests_in_progress_[request_id]; |
| request.in_progress_ids.erase(crx_file.info.extension_id); |
| NotifyIfFinished(request_id); |
| } |
| if (!crx_file.callback.is_null()) { |
| std::move(crx_file.callback).Run(false); |
| } |
| } |
| |
| running_crx_installs_.erase(iter); |
| } |
| |
| void ExtensionUpdater::NotifyStarted() { |
| content::NotificationService::current()->Notify( |
| NOTIFICATION_EXTENSION_UPDATING_STARTED, |
| content::Source<Profile>(profile_), |
| content::NotificationService::NoDetails()); |
| } |
| |
| void ExtensionUpdater::OnUpdateServiceFinished(int request_id) { |
| DCHECK(base::Contains(requests_in_progress_, request_id)); |
| InProgressCheck& request = requests_in_progress_[request_id]; |
| DCHECK(request.awaiting_update_service); |
| request.awaiting_update_service = false; |
| NotifyIfFinished(request_id); |
| } |
| |
| void ExtensionUpdater::NotifyIfFinished(int request_id) { |
| DCHECK(base::Contains(requests_in_progress_, request_id)); |
| InProgressCheck& request = requests_in_progress_[request_id]; |
| if (!request.in_progress_ids.empty() || request.awaiting_update_service) |
| return; // This request is not done yet. |
| VLOG(2) << "Finished update check " << request_id; |
| if (!request.callback.is_null()) |
| std::move(request.callback).Run(); |
| requests_in_progress_.erase(request_id); |
| } |
| |
| ExtensionUpdater::ScopedSkipScheduledCheckForTest:: |
| ScopedSkipScheduledCheckForTest() { |
| DCHECK(!g_skip_scheduled_checks_for_tests); |
| g_skip_scheduled_checks_for_tests = true; |
| } |
| |
| ExtensionUpdater::ScopedSkipScheduledCheckForTest:: |
| ~ScopedSkipScheduledCheckForTest() { |
| g_skip_scheduled_checks_for_tests = false; |
| } |
| |
| } // namespace extensions |