OLD | NEW |
(Empty) | |
| 1 // Copyright 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 "ios/chrome/browser/omaha/omaha_service.h" |
| 6 |
| 7 #include <Foundation/Foundation.h> |
| 8 |
| 9 #include <memory> |
| 10 #include <utility> |
| 11 |
| 12 #include "base/bind.h" |
| 13 #include "base/i18n/time_formatting.h" |
| 14 #include "base/ios/device_util.h" |
| 15 #include "base/logging.h" |
| 16 #include "base/mac/scoped_nsobject.h" |
| 17 #include "base/memory/ptr_util.h" |
| 18 #include "base/metrics/field_trial.h" |
| 19 #include "base/rand_util.h" |
| 20 #include "base/strings/stringprintf.h" |
| 21 #include "base/strings/sys_string_conversions.h" |
| 22 #include "base/strings/utf_string_conversions.h" |
| 23 #include "base/sys_info.h" |
| 24 #include "base/time/time.h" |
| 25 #include "base/values.h" |
| 26 #include "components/metrics/metrics_pref_names.h" |
| 27 #include "components/prefs/pref_service.h" |
| 28 #include "components/version_info/version_info.h" |
| 29 #include "ios/chrome/browser/application_context.h" |
| 30 #include "ios/chrome/browser/arch_util.h" |
| 31 #include "ios/chrome/browser/browser_state/chrome_browser_state_manager.h" |
| 32 #include "ios/chrome/browser/browser_state_metrics/browser_state_metrics.h" |
| 33 #include "ios/chrome/browser/install_time_util.h" |
| 34 #include "ios/chrome/browser/ui/ui_util.h" |
| 35 #include "ios/chrome/browser/upgrade/upgrade_recommended_details.h" |
| 36 #include "ios/chrome/common/channel_info.h" |
| 37 #include "ios/public/provider/chrome/browser/chrome_browser_provider.h" |
| 38 #include "ios/public/provider/chrome/browser/omaha/omaha_service_provider.h" |
| 39 #include "ios/public/provider/chrome/browser/omaha/omaha_xml_writer.h" |
| 40 #include "ios/web/public/web_thread.h" |
| 41 #include "libxml/xmlwriter.h" |
| 42 #include "net/base/backoff_entry.h" |
| 43 #include "net/base/load_flags.h" |
| 44 #include "net/url_request/url_fetcher.h" |
| 45 #include "url/gurl.h" |
| 46 |
| 47 namespace { |
| 48 // Number of hours to wait between successful requests. |
| 49 const int kHoursBetweenRequests = 5; |
| 50 // Minimal time to wait between retry requests. |
| 51 const CFTimeInterval kPostRetryBaseSeconds = 3600; |
| 52 // Maximal time to wait between retry requests. |
| 53 const CFTimeInterval kPostRetryMaxSeconds = 6 * kPostRetryBaseSeconds; |
| 54 |
| 55 // Default last sent application version when none has been sent yet. |
| 56 const char kDefaultLastSentVersion[] = "0.0.0.0"; |
| 57 |
| 58 // Key for saving states in the UserDefaults. |
| 59 NSString* const kNextTriesTimesKey = @"ChromeOmahaServiceNextTries"; |
| 60 NSString* const kCurrentPingKey = @"ChromeOmahaServiceCurrentPing"; |
| 61 NSString* const kNumberTriesKey = @"ChromeOmahaServiceNumberTries"; |
| 62 NSString* const kLastSentVersionKey = @"ChromeOmahaServiceLastSentVersion"; |
| 63 NSString* const kLastSentTimeKey = @"ChromeOmahaServiceLastSentTime"; |
| 64 NSString* const kRetryRequestIdKey = @"ChromeOmahaServiceRetryRequestId"; |
| 65 |
| 66 class XmlWrapper : public OmahaXmlWriter { |
| 67 public: |
| 68 XmlWrapper() |
| 69 : buffer_(xmlBufferCreate()), |
| 70 writer_(xmlNewTextWriterMemory(buffer_, /* compression */ 0)) { |
| 71 DCHECK(buffer_); |
| 72 DCHECK(writer_); |
| 73 } |
| 74 |
| 75 ~XmlWrapper() override { |
| 76 xmlFreeTextWriter(writer_); |
| 77 xmlBufferFree(buffer_); |
| 78 } |
| 79 |
| 80 void StartElement(const char* name) override { |
| 81 DCHECK(name); |
| 82 int result = xmlTextWriterStartElement( |
| 83 writer_, reinterpret_cast<const xmlChar*>(name)); |
| 84 DCHECK_GE(result, 0); |
| 85 } |
| 86 |
| 87 void EndElement() override { |
| 88 int result = xmlTextWriterEndElement(writer_); |
| 89 DCHECK_GE(result, 0); |
| 90 } |
| 91 |
| 92 void WriteAttribute(const char* name, const char* value) override { |
| 93 DCHECK(name); |
| 94 int result = xmlTextWriterWriteAttribute( |
| 95 writer_, reinterpret_cast<const xmlChar*>(name), |
| 96 reinterpret_cast<const xmlChar*>(value)); |
| 97 DCHECK_GE(result, 0); |
| 98 } |
| 99 |
| 100 void Finalize() override { |
| 101 int result = xmlTextWriterEndDocument(writer_); |
| 102 DCHECK_GE(result, 0); |
| 103 } |
| 104 |
| 105 std::string GetContentAsString() override { |
| 106 return std::string(reinterpret_cast<char*>(buffer_->content)); |
| 107 } |
| 108 |
| 109 private: |
| 110 xmlBufferPtr buffer_; |
| 111 xmlTextWriterPtr writer_; |
| 112 |
| 113 DISALLOW_COPY_AND_ASSIGN(XmlWrapper); |
| 114 }; |
| 115 |
| 116 } // namespace |
| 117 |
| 118 #pragma mark - |
| 119 |
| 120 // XML parser for the server response. |
| 121 @interface ResponseParser : NSObject<NSXMLParserDelegate> { |
| 122 BOOL hasError_; |
| 123 BOOL responseIsParsed_; |
| 124 BOOL appIsParsed_; |
| 125 BOOL updateCheckIsParsed_; |
| 126 BOOL urlIsParsed_; |
| 127 BOOL manifestIsParsed_; |
| 128 BOOL pingIsParsed_; |
| 129 BOOL eventIsParsed_; |
| 130 base::scoped_nsobject<NSString> appId_; |
| 131 std::unique_ptr<UpgradeRecommendedDetails> updateInformation_; |
| 132 } |
| 133 |
| 134 // Initialization method. |appId| is the application id one expects to find in |
| 135 // the response message. |
| 136 - (instancetype)initWithAppId:(NSString*)appId; |
| 137 |
| 138 // Returns YES if the message has been correctly parsed. |
| 139 - (BOOL)isCorrect; |
| 140 |
| 141 // If an upgrade is possible, returns the details of the notification to send. |
| 142 // Otherwise, return NULL. |
| 143 - (UpgradeRecommendedDetails*)upgradeRecommendedDetails; |
| 144 |
| 145 @end |
| 146 |
| 147 @implementation ResponseParser |
| 148 |
| 149 - (instancetype)initWithAppId:(NSString*)appId { |
| 150 if (self = [super init]) { |
| 151 appId_.reset([appId retain]); |
| 152 } |
| 153 return self; |
| 154 } |
| 155 |
| 156 - (BOOL)isCorrect { |
| 157 // A response should have either a ping ACK or an event ACK, depending on the |
| 158 // contents of the request. |
| 159 return !hasError_ && (pingIsParsed_ || eventIsParsed_); |
| 160 } |
| 161 |
| 162 - (UpgradeRecommendedDetails*)upgradeRecommendedDetails { |
| 163 return updateInformation_.get(); |
| 164 } |
| 165 |
| 166 // This method is parsing a message with the following type: |
| 167 // <response...> |
| 168 // <daystart.../> |
| 169 // <app...> |
| 170 // <updatecheck status="ok"> |
| 171 // <urls> |
| 172 // <url codebase="???"/> |
| 173 // </urls> |
| 174 // <manifest version="???"> |
| 175 // <packages> |
| 176 // <package hash="0" name="Chrome" required="true" size="0"/> |
| 177 // </packages> |
| 178 // <actions> |
| 179 // <action event="update" run="Chrome"/> |
| 180 // <action event="postinstall"/> |
| 181 // </actions> |
| 182 // </manifest> |
| 183 // </updatecheck> |
| 184 // <ping.../> |
| 185 // </app> |
| 186 // </response> |
| 187 // --- OR --- |
| 188 // <response...> |
| 189 // <daystart.../> |
| 190 // <app...> |
| 191 // <event.../> |
| 192 // </app> |
| 193 // </response> |
| 194 // See http://code.google.com/p/omaha/wiki/ServerProtocol for details. |
| 195 - (void)parser:(NSXMLParser*)parser |
| 196 didStartElement:(NSString*)elementName |
| 197 namespaceURI:(NSString*)namespaceURI |
| 198 qualifiedName:(NSString*)qualifiedName |
| 199 attributes:(NSDictionary*)attributeDict { |
| 200 if (hasError_) |
| 201 return; |
| 202 |
| 203 // Array of uninteresting tags in the Omaha xml response. |
| 204 NSArray* ignoredTagNames = |
| 205 @[ @"action", @"actions", @"daystart", @"package", @"packages", @"urls" ]; |
| 206 if ([ignoredTagNames containsObject:elementName]) |
| 207 return; |
| 208 |
| 209 if (!responseIsParsed_) { |
| 210 if ([elementName isEqualToString:@"response"] && |
| 211 [[attributeDict valueForKey:@"protocol"] isEqualToString:@"3.0"] && |
| 212 [[attributeDict valueForKey:@"server"] isEqualToString:@"prod"]) { |
| 213 responseIsParsed_ = YES; |
| 214 } else { |
| 215 hasError_ = YES; |
| 216 } |
| 217 } else if (!appIsParsed_) { |
| 218 if ([elementName isEqualToString:@"app"] && |
| 219 [[attributeDict valueForKey:@"status"] isEqualToString:@"ok"] && |
| 220 [[attributeDict valueForKey:@"appid"] isEqualToString:appId_]) { |
| 221 appIsParsed_ = YES; |
| 222 } else { |
| 223 hasError_ = YES; |
| 224 } |
| 225 } else if (!eventIsParsed_ && !updateCheckIsParsed_) { |
| 226 if ([elementName isEqualToString:@"updatecheck"]) { |
| 227 updateCheckIsParsed_ = YES; |
| 228 NSString* status = [attributeDict valueForKey:@"status"]; |
| 229 if ([status isEqualToString:@"noupdate"]) { |
| 230 // No update is available on the Market, so we won't get a <url> or |
| 231 // <manifest> tag. |
| 232 urlIsParsed_ = YES; |
| 233 manifestIsParsed_ = YES; |
| 234 } else if ([status isEqualToString:@"ok"]) { |
| 235 updateInformation_ = base::MakeUnique<UpgradeRecommendedDetails>(); |
| 236 } else { |
| 237 hasError_ = YES; |
| 238 } |
| 239 } else if ([elementName isEqualToString:@"event"]) { |
| 240 if ([[attributeDict valueForKey:@"status"] isEqualToString:@"ok"]) { |
| 241 eventIsParsed_ = YES; |
| 242 } else { |
| 243 hasError_ = YES; |
| 244 } |
| 245 } else { |
| 246 hasError_ = YES; |
| 247 } |
| 248 } else if (!urlIsParsed_) { |
| 249 if ([elementName isEqualToString:@"url"] && |
| 250 [[attributeDict valueForKey:@"codebase"] length] > 0) { |
| 251 urlIsParsed_ = YES; |
| 252 DCHECK(updateInformation_); |
| 253 NSString* url = [attributeDict valueForKey:@"codebase"]; |
| 254 if ([[url substringFromIndex:([url length] - 1)] isEqualToString:@"/"]) |
| 255 url = [url substringToIndex:([url length] - 1)]; |
| 256 updateInformation_.get()->upgrade_url = |
| 257 GURL(base::SysNSStringToUTF8(url)); |
| 258 if (!updateInformation_.get()->upgrade_url.is_valid()) |
| 259 hasError_ = YES; |
| 260 } else { |
| 261 hasError_ = YES; |
| 262 } |
| 263 } else if (!manifestIsParsed_) { |
| 264 if ([elementName isEqualToString:@"manifest"] && |
| 265 [attributeDict valueForKey:@"version"]) { |
| 266 manifestIsParsed_ = YES; |
| 267 DCHECK(updateInformation_); |
| 268 updateInformation_.get()->next_version = |
| 269 base::SysNSStringToUTF8([attributeDict valueForKey:@"version"]); |
| 270 } else { |
| 271 hasError_ = YES; |
| 272 } |
| 273 } else if (!pingIsParsed_) { |
| 274 if ([elementName isEqualToString:@"ping"] && |
| 275 [[attributeDict valueForKey:@"status"] isEqualToString:@"ok"]) { |
| 276 pingIsParsed_ = YES; |
| 277 } else { |
| 278 hasError_ = YES; |
| 279 } |
| 280 } else { |
| 281 hasError_ = YES; |
| 282 } |
| 283 } |
| 284 |
| 285 @end |
| 286 |
| 287 // static |
| 288 OmahaService* OmahaService::GetInstance() { |
| 289 return base::Singleton<OmahaService>::get(); |
| 290 } |
| 291 |
| 292 // static |
| 293 void OmahaService::Start(net::URLRequestContextGetter* request_context_getter, |
| 294 const UpgradeRecommendedCallback& callback) { |
| 295 DCHECK(request_context_getter); |
| 296 DCHECK(!callback.is_null()); |
| 297 OmahaService* result = GetInstance(); |
| 298 result->set_upgrade_recommended_callback(callback); |
| 299 // This should only be called once. |
| 300 DCHECK(!result->request_context_getter_); |
| 301 result->request_context_getter_ = request_context_getter; |
| 302 result->locale_lang_ = GetApplicationContext()->GetApplicationLocale(); |
| 303 web::WebThread::PostTask(web::WebThread::IO, FROM_HERE, |
| 304 base::Bind(&OmahaService::SendOrScheduleNextPing, |
| 305 base::Unretained(result))); |
| 306 } |
| 307 |
| 308 OmahaService::OmahaService() |
| 309 : request_context_getter_(NULL), |
| 310 schedule_(true), |
| 311 application_install_date_(0), |
| 312 sending_install_event_(false) { |
| 313 Initialize(); |
| 314 } |
| 315 |
| 316 OmahaService::OmahaService(bool schedule) |
| 317 : request_context_getter_(NULL), |
| 318 schedule_(schedule), |
| 319 application_install_date_(0), |
| 320 sending_install_event_(false) { |
| 321 Initialize(); |
| 322 } |
| 323 |
| 324 OmahaService::~OmahaService() {} |
| 325 |
| 326 void OmahaService::Initialize() { |
| 327 // Initialize the provider at the same time as the rest of the service. |
| 328 ios::GetChromeBrowserProvider()->GetOmahaServiceProvider()->Initialize(); |
| 329 |
| 330 NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; |
| 331 next_tries_time_ = base::Time::FromCFAbsoluteTime( |
| 332 [defaults doubleForKey:kNextTriesTimesKey]); |
| 333 current_ping_time_ = |
| 334 base::Time::FromCFAbsoluteTime([defaults doubleForKey:kCurrentPingKey]); |
| 335 number_of_tries_ = [defaults integerForKey:kNumberTriesKey]; |
| 336 last_sent_time_ = |
| 337 base::Time::FromCFAbsoluteTime([defaults doubleForKey:kLastSentTimeKey]); |
| 338 NSString* lastSentVersion = [defaults stringForKey:kLastSentVersionKey]; |
| 339 if (lastSentVersion) { |
| 340 last_sent_version_ = |
| 341 base::Version(base::SysNSStringToUTF8(lastSentVersion)); |
| 342 } else { |
| 343 last_sent_version_ = base::Version(kDefaultLastSentVersion); |
| 344 } |
| 345 |
| 346 application_install_date_ = |
| 347 GetApplicationContext()->GetLocalState()->GetInt64( |
| 348 metrics::prefs::kInstallDate); |
| 349 DCHECK(application_install_date_); |
| 350 |
| 351 // Whether data should be persisted again to the user preferences. |
| 352 bool persist_again = false; |
| 353 |
| 354 base::Time now = base::Time::Now(); |
| 355 // If |last_sent_time_| is in the future, the clock has been tampered with. |
| 356 // Reset |last_sent_time_| to now. |
| 357 if (last_sent_time_ > now) { |
| 358 last_sent_time_ = now; |
| 359 persist_again = true; |
| 360 } |
| 361 |
| 362 // If the |next_tries_time_| is more than kHoursBetweenRequests hours away, |
| 363 // there is a possibility that the clock has been tampered with. Reschedule |
| 364 // the ping to be the usual interval after the last successful one. |
| 365 if (next_tries_time_ - now > |
| 366 base::TimeDelta::FromHours(kHoursBetweenRequests)) { |
| 367 next_tries_time_ = |
| 368 last_sent_time_ + base::TimeDelta::FromHours(kHoursBetweenRequests); |
| 369 persist_again = true; |
| 370 } |
| 371 |
| 372 // Fire a ping as early as possible if the version changed. |
| 373 base::Version current_version(version_info::GetVersionNumber()); |
| 374 if (last_sent_version_ < current_version) { |
| 375 next_tries_time_ = base::Time::Now() - base::TimeDelta::FromSeconds(1); |
| 376 number_of_tries_ = 0; |
| 377 persist_again = true; |
| 378 } |
| 379 |
| 380 if (persist_again) |
| 381 PersistStates(); |
| 382 } |
| 383 |
| 384 // static |
| 385 void OmahaService::GetDebugInformation( |
| 386 const base::Callback<void(base::DictionaryValue*)> callback) { |
| 387 web::WebThread::PostTask( |
| 388 web::WebThread::IO, FROM_HERE, |
| 389 base::Bind(&OmahaService::GetDebugInformationOnIOThread, |
| 390 base::Unretained(GetInstance()), callback)); |
| 391 } |
| 392 |
| 393 // static |
| 394 base::TimeDelta OmahaService::GetBackOff(uint8_t number_of_tries) { |
| 395 // Configuration for the service exponential backoff |
| 396 static net::BackoffEntry::Policy kBackoffPolicy = { |
| 397 0, // num_errors_to_ignore |
| 398 kPostRetryBaseSeconds * 1000, // initial_delay_ms |
| 399 2.0, // multiply_factor |
| 400 0.1, // jitter_factor |
| 401 kPostRetryMaxSeconds * 1000, // maximum_backoff_ms |
| 402 -1, // entry_lifetime_ms |
| 403 false // always_use_initial_delay |
| 404 }; |
| 405 |
| 406 net::BackoffEntry backoff_entry(&kBackoffPolicy); |
| 407 for (int i = 0; i < number_of_tries; ++i) { |
| 408 backoff_entry.InformOfRequest(false); |
| 409 } |
| 410 |
| 411 return backoff_entry.GetTimeUntilRelease(); |
| 412 } |
| 413 |
| 414 std::string OmahaService::GetPingContent(const std::string& requestId, |
| 415 const std::string& sessionId, |
| 416 const std::string& versionName, |
| 417 const std::string& channelName, |
| 418 const base::Time& installationTime, |
| 419 PingContent pingContent) { |
| 420 OmahaServiceProvider* provider = |
| 421 ios::GetChromeBrowserProvider()->GetOmahaServiceProvider(); |
| 422 |
| 423 XmlWrapper xml_wrapper; |
| 424 xml_wrapper.StartElement("request"); |
| 425 xml_wrapper.WriteAttribute("protocol", "3.0"); |
| 426 xml_wrapper.WriteAttribute("version", "iOS-1.0.0.0"); |
| 427 xml_wrapper.WriteAttribute("ismachine", "1"); |
| 428 xml_wrapper.WriteAttribute("requestid", requestId.c_str()); |
| 429 xml_wrapper.WriteAttribute("sessionid", sessionId.c_str()); |
| 430 provider->AppendExtraAttributes("request", &xml_wrapper); |
| 431 xml_wrapper.WriteAttribute("hardware_class", |
| 432 ios::device_util::GetPlatform().c_str()); |
| 433 // Set up <os platform="ios"... /> |
| 434 xml_wrapper.StartElement("os"); |
| 435 xml_wrapper.WriteAttribute("platform", "ios"); |
| 436 xml_wrapper.WriteAttribute("version", |
| 437 base::SysInfo::OperatingSystemVersion().c_str()); |
| 438 xml_wrapper.WriteAttribute("arch", arch_util::kCurrentArch); |
| 439 xml_wrapper.EndElement(); |
| 440 |
| 441 bool is_first_install = |
| 442 pingContent == INSTALL_EVENT && |
| 443 last_sent_version_ == base::Version(kDefaultLastSentVersion); |
| 444 |
| 445 // Set up <app version="" ...> |
| 446 xml_wrapper.StartElement("app"); |
| 447 if (pingContent == INSTALL_EVENT) { |
| 448 std::string previous_version = |
| 449 is_first_install ? "" : last_sent_version_.GetString(); |
| 450 xml_wrapper.WriteAttribute("version", previous_version.c_str()); |
| 451 xml_wrapper.WriteAttribute("nextversion", versionName.c_str()); |
| 452 } else { |
| 453 xml_wrapper.WriteAttribute("version", versionName.c_str()); |
| 454 xml_wrapper.WriteAttribute("nextversion", ""); |
| 455 } |
| 456 xml_wrapper.WriteAttribute("lang", locale_lang_.c_str()); |
| 457 xml_wrapper.WriteAttribute("brand", provider->GetBrandCode().c_str()); |
| 458 xml_wrapper.WriteAttribute("client", ""); |
| 459 std::string application_id = provider->GetApplicationID(); |
| 460 xml_wrapper.WriteAttribute("appid", application_id.c_str()); |
| 461 std::string install_age; |
| 462 if (is_first_install) { |
| 463 install_age = "-1"; |
| 464 } else if (!installationTime.is_null() && |
| 465 installationTime.ToTimeT() != |
| 466 install_time_util::kUnknownInstallDate) { |
| 467 install_age = base::StringPrintf( |
| 468 "%d", (base::Time::Now() - installationTime).InDays()); |
| 469 } |
| 470 provider->AppendExtraAttributes("app", &xml_wrapper); |
| 471 // If the install date is unknown, send nothing. |
| 472 if (!install_age.empty()) |
| 473 xml_wrapper.WriteAttribute("installage", install_age.c_str()); |
| 474 |
| 475 if (pingContent == INSTALL_EVENT) { |
| 476 // Add an install complete event. |
| 477 xml_wrapper.StartElement("event"); |
| 478 if (is_first_install) { |
| 479 xml_wrapper.WriteAttribute("eventtype", "2"); // install |
| 480 } else { |
| 481 xml_wrapper.WriteAttribute("eventtype", "3"); // update |
| 482 } |
| 483 xml_wrapper.WriteAttribute("eventresult", "1"); // succeeded |
| 484 xml_wrapper.EndElement(); |
| 485 } else { |
| 486 // Set up <updatecheck/> |
| 487 xml_wrapper.StartElement("updatecheck"); |
| 488 xml_wrapper.WriteAttribute("tag", channelName.c_str()); |
| 489 xml_wrapper.EndElement(); |
| 490 |
| 491 // Set up <ping active=1/> |
| 492 xml_wrapper.StartElement("ping"); |
| 493 xml_wrapper.WriteAttribute("active", "1"); |
| 494 xml_wrapper.EndElement(); |
| 495 } |
| 496 |
| 497 // End app. |
| 498 xml_wrapper.EndElement(); |
| 499 // End request. |
| 500 xml_wrapper.EndElement(); |
| 501 |
| 502 xml_wrapper.Finalize(); |
| 503 return xml_wrapper.GetContentAsString(); |
| 504 } |
| 505 |
| 506 std::string OmahaService::GetCurrentPingContent() { |
| 507 base::Version current_version(version_info::GetVersionNumber()); |
| 508 sending_install_event_ = last_sent_version_ < current_version; |
| 509 PingContent ping_content = |
| 510 sending_install_event_ ? INSTALL_EVENT : USAGE_PING; |
| 511 |
| 512 // An install retry ping only makes sense if an install event must be send. |
| 513 DCHECK(sending_install_event_ || !IsNextPingInstallRetry()); |
| 514 std::string request_id = GetNextPingRequestId(ping_content); |
| 515 return GetPingContent(request_id, ios::device_util::GetRandomId(), |
| 516 version_info::GetVersionNumber(), GetChannelString(), |
| 517 base::Time::FromTimeT(application_install_date_), |
| 518 ping_content); |
| 519 } |
| 520 |
| 521 void OmahaService::SendPing() { |
| 522 // Check that no request is in progress. |
| 523 DCHECK(!fetcher_); |
| 524 |
| 525 GURL url(ios::GetChromeBrowserProvider() |
| 526 ->GetOmahaServiceProvider() |
| 527 ->GetUpdateServerURL()); |
| 528 if (!url.is_valid()) { |
| 529 return; |
| 530 } |
| 531 |
| 532 fetcher_ = net::URLFetcher::Create(0, url, net::URLFetcher::POST, this); |
| 533 fetcher_->SetRequestContext(request_context_getter_); |
| 534 fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES | |
| 535 net::LOAD_DO_NOT_SAVE_COOKIES); |
| 536 fetcher_->SetUploadData("text/xml", GetCurrentPingContent()); |
| 537 |
| 538 // If this is not the first try, notify the omaha server. |
| 539 if (number_of_tries_ && IsNextPingInstallRetry()) { |
| 540 fetcher_->SetExtraRequestHeaders(base::StringPrintf( |
| 541 "X-RequestAge: %lld", |
| 542 (base::Time::Now() - current_ping_time_).InSeconds())); |
| 543 } |
| 544 |
| 545 // Update last fail time and number of tries, so that if anything fails |
| 546 // catastrophically, the fail is taken into account. |
| 547 if (number_of_tries_ < 30) |
| 548 ++number_of_tries_; |
| 549 next_tries_time_ = base::Time::Now() + GetBackOff(number_of_tries_); |
| 550 PersistStates(); |
| 551 |
| 552 fetcher_->Start(); |
| 553 } |
| 554 |
| 555 void OmahaService::SendOrScheduleNextPing() { |
| 556 base::Time now = base::Time::Now(); |
| 557 if (next_tries_time_ <= now) { |
| 558 SendPing(); |
| 559 return; |
| 560 } |
| 561 if (schedule_) { |
| 562 timer_.Start(FROM_HERE, next_tries_time_ - now, this, |
| 563 &OmahaService::SendPing); |
| 564 } |
| 565 } |
| 566 |
| 567 void OmahaService::PersistStates() { |
| 568 NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; |
| 569 |
| 570 [defaults setDouble:next_tries_time_.ToCFAbsoluteTime() |
| 571 forKey:kNextTriesTimesKey]; |
| 572 [defaults setDouble:current_ping_time_.ToCFAbsoluteTime() |
| 573 forKey:kCurrentPingKey]; |
| 574 [defaults setDouble:last_sent_time_.ToCFAbsoluteTime() |
| 575 forKey:kLastSentTimeKey]; |
| 576 [defaults setInteger:number_of_tries_ forKey:kNumberTriesKey]; |
| 577 [defaults setObject:base::SysUTF8ToNSString(last_sent_version_.GetString()) |
| 578 forKey:kLastSentVersionKey]; |
| 579 |
| 580 // Save critical state information for usage reporting. |
| 581 [defaults synchronize]; |
| 582 } |
| 583 |
| 584 void OmahaService::OnURLFetchComplete(const net::URLFetcher* fetcher) { |
| 585 DCHECK(fetcher_.get() == fetcher); |
| 586 // Transfer the ownership of fetcher_ to this method. |
| 587 std::unique_ptr<net::URLFetcher> local_fetcher = std::move(fetcher_); |
| 588 |
| 589 if (fetcher->GetResponseCode() != 200) { |
| 590 DLOG(WARNING) << "Error contacting the Omaha server"; |
| 591 SendOrScheduleNextPing(); |
| 592 return; |
| 593 } |
| 594 |
| 595 std::string response; |
| 596 bool result = fetcher->GetResponseAsString(&response); |
| 597 DCHECK(result); |
| 598 NSData* xml = [NSData dataWithBytes:response.data() length:response.length()]; |
| 599 base::scoped_nsobject<NSXMLParser> parser( |
| 600 [[NSXMLParser alloc] initWithData:xml]); |
| 601 const std::string application_id = ios::GetChromeBrowserProvider() |
| 602 ->GetOmahaServiceProvider() |
| 603 ->GetApplicationID(); |
| 604 base::scoped_nsobject<ResponseParser> delegate([[ResponseParser alloc] |
| 605 initWithAppId:base::SysUTF8ToNSString(application_id)]); |
| 606 parser.get().delegate = delegate.get(); |
| 607 |
| 608 if (![parser parse] || ![delegate isCorrect]) { |
| 609 DLOG(ERROR) << "Unable to parse XML response from Omaha server."; |
| 610 SendOrScheduleNextPing(); |
| 611 return; |
| 612 } |
| 613 // Handle success. |
| 614 number_of_tries_ = 0; |
| 615 // Schedule the next request. If requset that just finished was an install |
| 616 // notification, send an active ping immediately. |
| 617 next_tries_time_ = sending_install_event_ |
| 618 ? base::Time::Now() |
| 619 : base::Time::Now() + base::TimeDelta::FromHours( |
| 620 kHoursBetweenRequests); |
| 621 current_ping_time_ = next_tries_time_; |
| 622 last_sent_time_ = base::Time::Now(); |
| 623 last_sent_version_ = base::Version(version_info::GetVersionNumber()); |
| 624 sending_install_event_ = false; |
| 625 ClearInstallRetryRequestId(); |
| 626 PersistStates(); |
| 627 SendOrScheduleNextPing(); |
| 628 |
| 629 // Send notification for updates if needed. |
| 630 UpgradeRecommendedDetails* details = |
| 631 [delegate.get() upgradeRecommendedDetails]; |
| 632 if (details) { |
| 633 web::WebThread::PostTask( |
| 634 web::WebThread::UI, FROM_HERE, |
| 635 base::Bind(upgrade_recommended_callback_, *details)); |
| 636 } |
| 637 } |
| 638 |
| 639 void OmahaService::GetDebugInformationOnIOThread( |
| 640 const base::Callback<void(base::DictionaryValue*)> callback) { |
| 641 auto result = base::MakeUnique<base::DictionaryValue>(); |
| 642 |
| 643 result->SetString("message", GetCurrentPingContent()); |
| 644 result->SetString("last_sent_time", |
| 645 base::TimeFormatShortDateAndTime(last_sent_time_)); |
| 646 result->SetString("next_tries_time", |
| 647 base::TimeFormatShortDateAndTime(next_tries_time_)); |
| 648 result->SetString("current_ping_time", |
| 649 base::TimeFormatShortDateAndTime(current_ping_time_)); |
| 650 result->SetString("last_sent_version", last_sent_version_.GetString()); |
| 651 result->SetString("number_of_tries", |
| 652 base::StringPrintf("%d", number_of_tries_)); |
| 653 result->SetString("timer_running", |
| 654 base::StringPrintf("%d", timer_.IsRunning())); |
| 655 result->SetString( |
| 656 "timer_current_delay", |
| 657 base::StringPrintf("%llds", timer_.GetCurrentDelay().InSeconds())); |
| 658 result->SetString("timer_desired_run_time", |
| 659 base::TimeFormatShortDateAndTime( |
| 660 base::Time::Now() + |
| 661 (timer_.desired_run_time() - base::TimeTicks::Now()))); |
| 662 |
| 663 // Sending the value to the callback. |
| 664 web::WebThread::PostTask(web::WebThread::UI, FROM_HERE, |
| 665 base::Bind(callback, base::Owned(result.release()))); |
| 666 } |
| 667 |
| 668 bool OmahaService::IsNextPingInstallRetry() { |
| 669 return [[NSUserDefaults standardUserDefaults] |
| 670 stringForKey:kRetryRequestIdKey] != nil; |
| 671 } |
| 672 |
| 673 std::string OmahaService::GetNextPingRequestId(PingContent ping_content) { |
| 674 NSString* stored_id = |
| 675 [[NSUserDefaults standardUserDefaults] stringForKey:kRetryRequestIdKey]; |
| 676 if (stored_id) { |
| 677 DCHECK(ping_content == INSTALL_EVENT); |
| 678 return base::SysNSStringToUTF8(stored_id); |
| 679 } else { |
| 680 std::string identifier = ios::device_util::GetRandomId(); |
| 681 if (ping_content == INSTALL_EVENT) |
| 682 OmahaService::SetInstallRetryRequestId(identifier); |
| 683 return identifier; |
| 684 } |
| 685 } |
| 686 |
| 687 void OmahaService::SetInstallRetryRequestId(const std::string& request_id) { |
| 688 NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; |
| 689 [defaults setObject:base::SysUTF8ToNSString(request_id) |
| 690 forKey:kRetryRequestIdKey]; |
| 691 // Save critical state information for usage reporting. |
| 692 [defaults synchronize]; |
| 693 } |
| 694 |
| 695 void OmahaService::ClearInstallRetryRequestId() { |
| 696 NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; |
| 697 [defaults removeObjectForKey:kRetryRequestIdKey]; |
| 698 // Clear critical state information for usage reporting. |
| 699 [defaults synchronize]; |
| 700 } |
| 701 |
| 702 void OmahaService::ClearPersistentStateForTests() { |
| 703 NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; |
| 704 [defaults removeObjectForKey:kNextTriesTimesKey]; |
| 705 [defaults removeObjectForKey:kCurrentPingKey]; |
| 706 [defaults removeObjectForKey:kNumberTriesKey]; |
| 707 [defaults removeObjectForKey:kLastSentVersionKey]; |
| 708 [defaults removeObjectForKey:kLastSentTimeKey]; |
| 709 [defaults removeObjectForKey:kRetryRequestIdKey]; |
| 710 } |
OLD | NEW |