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/upgrade_detector_impl.h" 6 7#include <string> 8 9#include "base/bind.h" 10#include "base/build_time.h" 11#include "base/command_line.h" 12#include "base/files/file_path.h" 13#include "base/memory/scoped_ptr.h" 14#include "base/memory/singleton.h" 15#include "base/path_service.h" 16#include "base/process/launch.h" 17#include "base/strings/string_number_conversions.h" 18#include "base/strings/string_util.h" 19#include "base/strings/utf_string_conversions.h" 20#include "base/time/time.h" 21#include "base/version.h" 22#include "chrome/browser/browser_process.h" 23#include "chrome/browser/google/google_util.h" 24#include "chrome/common/chrome_switches.h" 25#include "chrome/common/chrome_version_info.h" 26#include "content/public/browser/browser_thread.h" 27#include "ui/base/resource/resource_bundle.h" 28 29#if defined(OS_WIN) 30#include "chrome/installer/util/browser_distribution.h" 31#include "chrome/installer/util/google_update_settings.h" 32#include "chrome/installer/util/helper.h" 33#include "chrome/installer/util/install_util.h" 34#elif defined(OS_MACOSX) 35#include "chrome/browser/mac/keystone_glue.h" 36#elif defined(OS_POSIX) 37#include "base/process/launch.h" 38#endif 39 40using content::BrowserThread; 41 42namespace { 43 44// How long (in milliseconds) to wait (each cycle) before checking whether 45// Chrome's been upgraded behind our back. 46const int kCheckForUpgradeMs = 2 * 60 * 60 * 1000; // 2 hours. 47 48// How long to wait (each cycle) before checking which severity level we should 49// be at. Once we reach the highest severity, the timer will stop. 50const int kNotifyCycleTimeMs = 20 * 60 * 1000; // 20 minutes. 51 52// Same as kNotifyCycleTimeMs but only used during testing. 53const int kNotifyCycleTimeForTestingMs = 500; // Half a second. 54 55// The number of days after which we identify a build/install as outdated. 56const uint64 kOutdatedBuildAgeInDays = 12 * 7; 57 58std::string CmdLineInterval() { 59 const CommandLine& cmd_line = *CommandLine::ForCurrentProcess(); 60 return cmd_line.GetSwitchValueASCII(switches::kCheckForUpdateIntervalSec); 61} 62 63bool IsTesting() { 64 const CommandLine& cmd_line = *CommandLine::ForCurrentProcess(); 65 return cmd_line.HasSwitch(switches::kSimulateUpgrade) || 66 cmd_line.HasSwitch(switches::kCheckForUpdateIntervalSec) || 67 cmd_line.HasSwitch(switches::kSimulateCriticalUpdate) || 68 cmd_line.HasSwitch(switches::kSimulateOutdated); 69} 70 71// How often to check for an upgrade. 72int GetCheckForUpgradeEveryMs() { 73 // Check for a value passed via the command line. 74 int interval_ms; 75 std::string interval = CmdLineInterval(); 76 if (!interval.empty() && base::StringToInt(interval, &interval_ms)) 77 return interval_ms * 1000; // Command line value is in seconds. 78 79 return kCheckForUpgradeMs; 80} 81 82bool IsUnstableChannel() { 83 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 84 chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel(); 85 return channel == chrome::VersionInfo::CHANNEL_DEV || 86 channel == chrome::VersionInfo::CHANNEL_CANARY; 87} 88 89// This task identifies whether we are running an unstable version. And then 90// it unconditionally calls back the provided task. 91void CheckForUnstableChannel(const base::Closure& callback_task, 92 bool* is_unstable_channel) { 93 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 94 *is_unstable_channel = IsUnstableChannel(); 95 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, callback_task); 96} 97 98#if defined(OS_WIN) 99bool IsSystemInstall() { 100 // Get the version of the currently *installed* instance of Chrome, 101 // which might be newer than the *running* instance if we have been 102 // upgraded in the background. 103 base::FilePath exe_path; 104 if (!PathService::Get(base::DIR_EXE, &exe_path)) { 105 NOTREACHED() << "Failed to find executable path"; 106 return false; 107 } 108 109 return !InstallUtil::IsPerUserInstall(exe_path.value().c_str()); 110} 111 112// This task checks the update policy and calls back the task only if automatic 113// updates are allowed. It also identifies whether we are running an unstable 114// channel. 115void DetectUpdatability(const base::Closure& callback_task, 116 bool* is_unstable_channel) { 117 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 118 119 base::string16 app_guid = installer::GetAppGuidForUpdates(IsSystemInstall()); 120 DCHECK(!app_guid.empty()); 121 if (GoogleUpdateSettings::AUTOMATIC_UPDATES == 122 GoogleUpdateSettings::GetAppUpdatePolicy(app_guid, NULL)) { 123 CheckForUnstableChannel(callback_task, is_unstable_channel); 124 } 125} 126#endif // defined(OS_WIN) 127 128} // namespace 129 130UpgradeDetectorImpl::UpgradeDetectorImpl() 131 : weak_factory_(this), 132 is_unstable_channel_(false), 133 build_date_(base::GetBuildTime()) { 134 CommandLine command_line(*CommandLine::ForCurrentProcess()); 135 // The different command line switches that affect testing can't be used 136 // simultaneously, if they do, here's the precedence order, based on the order 137 // of the if statements below: 138 // - kDisableBackgroundNetworking prevents any of the other command line 139 // switch from being taken into account. 140 // - kSimulateUpgrade supersedes critical or outdated upgrade switches. 141 // - kSimulateCriticalUpdate has precedence over kSimulateOutdated. 142 // - kSimulateOutdated can work on its own, or with a specified date. 143 if (command_line.HasSwitch(switches::kDisableBackgroundNetworking)) 144 return; 145 if (command_line.HasSwitch(switches::kSimulateUpgrade)) { 146 UpgradeDetected(UPGRADE_AVAILABLE_REGULAR); 147 return; 148 } 149 if (command_line.HasSwitch(switches::kSimulateCriticalUpdate)) { 150 UpgradeDetected(UPGRADE_AVAILABLE_CRITICAL); 151 return; 152 } 153 if (command_line.HasSwitch(switches::kSimulateOutdated)) { 154 // The outdated simulation can work without a value, which means outdated 155 // now, or with a value that must be a well formed date/time string that 156 // overrides the build date. 157 // Also note that to test with a given time/date, until the network time 158 // tracking moves off of the VariationsService, the "variations-server-url" 159 // command line switch must also be specified for the service to be 160 // available on non GOOGLE_CHROME_BUILD. 161 std::string build_date = command_line.GetSwitchValueASCII( 162 switches::kSimulateOutdated); 163 base::Time maybe_build_time; 164 bool result = base::Time::FromString(build_date.c_str(), &maybe_build_time); 165 if (result && !maybe_build_time.is_null()) { 166 // We got a valid build date simulation so use it and check for upgrades. 167 build_date_ = maybe_build_time; 168 StartTimerForUpgradeCheck(); 169 } else { 170 // Without a valid date, we simulate that we are already outdated... 171 UpgradeDetected(UPGRADE_NEEDED_OUTDATED_INSTALL); 172 } 173 return; 174 } 175 176 // Windows: only enable upgrade notifications for official builds. 177 // Mac: only enable them if the updater (Keystone) is present. 178 // Linux (and other POSIX): always enable regardless of branding. 179 base::Closure start_upgrade_check_timer_task = 180 base::Bind(&UpgradeDetectorImpl::StartTimerForUpgradeCheck, 181 weak_factory_.GetWeakPtr()); 182#if defined(OS_WIN) && defined(GOOGLE_CHROME_BUILD) 183 // On Windows, there might be a policy preventing updates, so validate 184 // updatability, and then call StartTimerForUpgradeCheck appropriately. 185 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, 186 base::Bind(&DetectUpdatability, 187 start_upgrade_check_timer_task, 188 &is_unstable_channel_)); 189 return; 190#elif defined(OS_WIN) && !defined(GOOGLE_CHROME_BUILD) 191 return; // Chromium has no upgrade channel. 192#elif defined(OS_MACOSX) 193 if (!keystone_glue::KeystoneEnabled()) 194 return; // Keystone updater not enabled. 195#elif !defined(OS_POSIX) 196 return; 197#endif 198 199 // Check whether the build is an unstable channel before starting the timer. 200 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, 201 base::Bind(&CheckForUnstableChannel, 202 start_upgrade_check_timer_task, 203 &is_unstable_channel_)); 204 205 // Start tracking network time updates. 206 network_time_tracker_.Start(); 207} 208 209UpgradeDetectorImpl::~UpgradeDetectorImpl() { 210} 211 212// Static 213// This task checks the currently running version of Chrome against the 214// installed version. If the installed version is newer, it calls back 215// UpgradeDetectorImpl::UpgradeDetected using a weak pointer so that it can 216// be interrupted from the UI thread. 217void UpgradeDetectorImpl::DetectUpgradeTask( 218 base::WeakPtr<UpgradeDetectorImpl> upgrade_detector) { 219 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 220 221 Version installed_version; 222 Version critical_update; 223 224#if defined(OS_WIN) 225 // Get the version of the currently *installed* instance of Chrome, 226 // which might be newer than the *running* instance if we have been 227 // upgraded in the background. 228 bool system_install = IsSystemInstall(); 229 230 // TODO(tommi): Check if using the default distribution is always the right 231 // thing to do. 232 BrowserDistribution* dist = BrowserDistribution::GetDistribution(); 233 InstallUtil::GetChromeVersion(dist, system_install, &installed_version); 234 235 if (installed_version.IsValid()) { 236 InstallUtil::GetCriticalUpdateVersion(dist, system_install, 237 &critical_update); 238 } 239#elif defined(OS_MACOSX) 240 installed_version = 241 Version(UTF16ToASCII(keystone_glue::CurrentlyInstalledVersion())); 242#elif defined(OS_POSIX) 243 // POSIX but not Mac OS X: Linux, etc. 244 CommandLine command_line(*CommandLine::ForCurrentProcess()); 245 command_line.AppendSwitch(switches::kProductVersion); 246 std::string reply; 247 if (!base::GetAppOutput(command_line, &reply)) { 248 DLOG(ERROR) << "Failed to get current file version"; 249 return; 250 } 251 252 installed_version = Version(reply); 253#endif 254 255 // Get the version of the currently *running* instance of Chrome. 256 chrome::VersionInfo version_info; 257 if (!version_info.is_valid()) { 258 NOTREACHED() << "Failed to get current file version"; 259 return; 260 } 261 Version running_version(version_info.Version()); 262 if (!running_version.IsValid()) { 263 NOTREACHED(); 264 return; 265 } 266 267 // |installed_version| may be NULL when the user downgrades on Linux (by 268 // switching from dev to beta channel, for example). The user needs a 269 // restart in this case as well. See http://crbug.com/46547 270 if (!installed_version.IsValid() || 271 (installed_version.CompareTo(running_version) > 0)) { 272 // If a more recent version is available, it might be that we are lacking 273 // a critical update, such as a zero-day fix. 274 UpgradeAvailable upgrade_available = UPGRADE_AVAILABLE_REGULAR; 275 if (critical_update.IsValid() && 276 critical_update.CompareTo(running_version) > 0) { 277 upgrade_available = UPGRADE_AVAILABLE_CRITICAL; 278 } 279 280 // Fire off the upgrade detected task. 281 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 282 base::Bind(&UpgradeDetectorImpl::UpgradeDetected, 283 upgrade_detector, 284 upgrade_available)); 285 } 286} 287 288void UpgradeDetectorImpl::StartTimerForUpgradeCheck() { 289 detect_upgrade_timer_.Start(FROM_HERE, 290 base::TimeDelta::FromMilliseconds(GetCheckForUpgradeEveryMs()), 291 this, &UpgradeDetectorImpl::CheckForUpgrade); 292} 293 294void UpgradeDetectorImpl::CheckForUpgrade() { 295 // Interrupt any (unlikely) unfinished execution of DetectUpgradeTask, or at 296 // least prevent the callback from being executed, because we will potentially 297 // call it from within DetectOutdatedInstall() or will post 298 // DetectUpgradeTask again below anyway. 299 weak_factory_.InvalidateWeakPtrs(); 300 301 // No need to look for upgrades if the install is outdated. 302 if (DetectOutdatedInstall()) 303 return; 304 305 // We use FILE as the thread to run the upgrade detection code on all 306 // platforms. For Linux, this is because we don't want to block the UI thread 307 // while launching a background process and reading its output; on the Mac and 308 // on Windows checking for an upgrade requires reading a file. 309 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, 310 base::Bind(&UpgradeDetectorImpl::DetectUpgradeTask, 311 weak_factory_.GetWeakPtr())); 312} 313 314bool UpgradeDetectorImpl::DetectOutdatedInstall() { 315 // Only enable the outdated install check if we are running the trial for it, 316 // unless we are simulating an outdated isntall. 317 static bool simulate_outdated = CommandLine::ForCurrentProcess()->HasSwitch( 318 switches::kSimulateOutdated); 319 if (!simulate_outdated) { 320 // Also don't show the bubble if we have a brand code that is NOT organic. 321 std::string brand; 322 if (google_util::GetBrand(&brand) && !google_util::IsOrganic(brand)) 323 return false; 324 } 325 326 base::Time network_time; 327 base::TimeDelta uncertainty; 328 if (!network_time_tracker_.GetNetworkTime(base::TimeTicks::Now(), 329 &network_time, 330 &uncertainty)) { 331 return false; 332 } 333 334 if (network_time.is_null() || build_date_.is_null() || 335 build_date_ > network_time) { 336 NOTREACHED(); 337 return false; 338 } 339 340 if (network_time - build_date_ > 341 base::TimeDelta::FromDays(kOutdatedBuildAgeInDays)) { 342 UpgradeDetected(UPGRADE_NEEDED_OUTDATED_INSTALL); 343 return true; 344 } 345 // If we simlated an outdated install with a date, we don't want to keep 346 // checking for version upgrades, which happens on non-official builds. 347 return simulate_outdated; 348} 349 350void UpgradeDetectorImpl::UpgradeDetected(UpgradeAvailable upgrade_available) { 351 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 352 upgrade_available_ = upgrade_available; 353 354 // Stop the recurring timer (that is checking for changes). 355 detect_upgrade_timer_.Stop(); 356 357 NotifyUpgradeDetected(); 358 359 // Start the repeating timer for notifying the user after a certain period. 360 // The called function will eventually figure out that enough time has passed 361 // and stop the timer. 362 int cycle_time = IsTesting() ? 363 kNotifyCycleTimeForTestingMs : kNotifyCycleTimeMs; 364 upgrade_notification_timer_.Start(FROM_HERE, 365 base::TimeDelta::FromMilliseconds(cycle_time), 366 this, &UpgradeDetectorImpl::NotifyOnUpgrade); 367} 368 369void UpgradeDetectorImpl::NotifyOnUpgrade() { 370 base::TimeDelta delta = base::Time::Now() - upgrade_detected_time(); 371 372 // We'll make testing more convenient by switching to seconds of waiting 373 // instead of days between flipping severity. 374 bool is_testing = IsTesting(); 375 int64 time_passed = is_testing ? delta.InSeconds() : delta.InHours(); 376 377 bool is_critical_or_outdated = upgrade_available_ > UPGRADE_AVAILABLE_REGULAR; 378 if (is_unstable_channel_) { 379 // There's only one threat level for unstable channels like dev and 380 // canary, and it hits after one hour. During testing, it hits after one 381 // second. 382 const int kUnstableThreshold = 1; 383 384 if (is_critical_or_outdated) 385 set_upgrade_notification_stage(UPGRADE_ANNOYANCE_CRITICAL); 386 else if (time_passed >= kUnstableThreshold) { 387 set_upgrade_notification_stage(UPGRADE_ANNOYANCE_LOW); 388 389 // That's as high as it goes. 390 upgrade_notification_timer_.Stop(); 391 } else { 392 return; // Not ready to recommend upgrade. 393 } 394 } else { 395 const int kMultiplier = is_testing ? 10 : 24; 396 // 14 days when not testing, otherwise 14 seconds. 397 const int kSevereThreshold = 14 * kMultiplier; 398 const int kHighThreshold = 7 * kMultiplier; 399 const int kElevatedThreshold = 4 * kMultiplier; 400 const int kLowThreshold = 2 * kMultiplier; 401 402 // These if statements must be sorted (highest interval first). 403 if (time_passed >= kSevereThreshold || is_critical_or_outdated) { 404 set_upgrade_notification_stage( 405 is_critical_or_outdated ? UPGRADE_ANNOYANCE_CRITICAL : 406 UPGRADE_ANNOYANCE_SEVERE); 407 408 // We can't get any higher, baby. 409 upgrade_notification_timer_.Stop(); 410 } else if (time_passed >= kHighThreshold) { 411 set_upgrade_notification_stage(UPGRADE_ANNOYANCE_HIGH); 412 } else if (time_passed >= kElevatedThreshold) { 413 set_upgrade_notification_stage(UPGRADE_ANNOYANCE_ELEVATED); 414 } else if (time_passed >= kLowThreshold) { 415 set_upgrade_notification_stage(UPGRADE_ANNOYANCE_LOW); 416 } else { 417 return; // Not ready to recommend upgrade. 418 } 419 } 420 421 NotifyUpgradeRecommended(); 422} 423 424// static 425UpgradeDetectorImpl* UpgradeDetectorImpl::GetInstance() { 426 return Singleton<UpgradeDetectorImpl>::get(); 427} 428 429// static 430UpgradeDetector* UpgradeDetector::GetInstance() { 431 return UpgradeDetectorImpl::GetInstance(); 432} 433