1effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch// Copyright 2014 The Chromium Authors. All rights reserved.
2effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch// Use of this source code is governed by a BSD-style license that can be
3effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch// found in the LICENSE file.
4effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
5effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch#include "chrome/browser/chromeos/timezone/timezone_request.h"
6effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
7effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch#include <string>
8effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
9effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch#include "base/json/json_reader.h"
10effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch#include "base/metrics/histogram.h"
11effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch#include "base/metrics/sparse_histogram.h"
12effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch#include "base/strings/string_number_conversions.h"
13effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch#include "base/strings/stringprintf.h"
14effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch#include "base/time/time.h"
15effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch#include "base/values.h"
160529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch#include "chrome/browser/chromeos/geolocation/geoposition.h"
17effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch#include "google_apis/google_api_keys.h"
18effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch#include "net/base/escape.h"
19effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch#include "net/base/load_flags.h"
20effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch#include "net/http/http_status_code.h"
21effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch#include "net/url_request/url_fetcher.h"
22effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch#include "net/url_request/url_request_context_getter.h"
23effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch#include "net/url_request/url_request_status.h"
24effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
25effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochnamespace chromeos {
26effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
27effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochnamespace {
28effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
29effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochconst char kDefaultTimezoneProviderUrl[] =
30effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    "https://maps.googleapis.com/maps/api/timezone/json?";
31effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
32effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochconst char kKeyString[] = "key";
33effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch// Language parameter is unsupported for now.
34effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch// const char kLanguageString[] = "language";
35effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochconst char kLocationString[] = "location";
36effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochconst char kSensorString[] = "sensor";
37effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochconst char kTimestampString[] = "timestamp";
38effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
39effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochconst char kDstOffsetString[] = "dstOffset";
40effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochconst char kRawOffsetString[] = "rawOffset";
41effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochconst char kTimeZoneIdString[] = "timeZoneId";
42effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochconst char kTimeZoneNameString[] = "timeZoneName";
43effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochconst char kStatusString[] = "status";
44effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochconst char kErrorMessageString[] = "error_message";
45effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
46effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch// Sleep between timezone request retry on HTTP error.
47effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochconst unsigned int kResolveTimeZoneRetrySleepOnServerErrorSeconds = 5;
48effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
49effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch// Sleep between timezone request retry on bad server response.
50effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochconst unsigned int kResolveTimeZoneRetrySleepBadResponseSeconds = 10;
51effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
52effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochstruct StatusString2Enum {
53effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  const char* string;
54effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  TimeZoneResponseData::Status value;
55effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch};
56effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
57effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochconst StatusString2Enum statusString2Enum[] = {
58effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    {"OK", TimeZoneResponseData::OK},
59effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    {"INVALID_REQUEST", TimeZoneResponseData::INVALID_REQUEST},
60effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    {"OVER_QUERY_LIMIT", TimeZoneResponseData::OVER_QUERY_LIMIT},
61effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    {"REQUEST_DENIED", TimeZoneResponseData::REQUEST_DENIED},
62effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    {"UNKNOWN_ERROR", TimeZoneResponseData::UNKNOWN_ERROR},
63effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    {"ZERO_RESULTS", TimeZoneResponseData::ZERO_RESULTS}, };
64effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
65effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochenum TimeZoneRequestEvent {
66effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  // NOTE: Do not renumber these as that would confuse interpretation of
67effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  // previously logged data. When making changes, also update the enum list
68effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  // in tools/metrics/histograms/histograms.xml to keep it in sync.
69effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  TIMEZONE_REQUEST_EVENT_REQUEST_START = 0,
70effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  TIMEZONE_REQUEST_EVENT_RESPONSE_SUCCESS = 1,
71effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  TIMEZONE_REQUEST_EVENT_RESPONSE_NOT_OK = 2,
72effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  TIMEZONE_REQUEST_EVENT_RESPONSE_EMPTY = 3,
73effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  TIMEZONE_REQUEST_EVENT_RESPONSE_MALFORMED = 4,
74effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
75effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  // NOTE: Add entries only immediately above this line.
76effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  TIMEZONE_REQUEST_EVENT_COUNT = 5
77effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch};
78effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
79effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochenum TimeZoneRequestResult {
80effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  // NOTE: Do not renumber these as that would confuse interpretation of
81effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  // previously logged data. When making changes, also update the enum list
82effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  // in tools/metrics/histograms/histograms.xml to keep it in sync.
83effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  TIMEZONE_REQUEST_RESULT_SUCCESS = 0,
84effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  TIMEZONE_REQUEST_RESULT_FAILURE = 1,
85effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  TIMEZONE_REQUEST_RESULT_SERVER_ERROR = 2,
86effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  TIMEZONE_REQUEST_RESULT_CANCELLED = 3,
87effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
88effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  // NOTE: Add entries only immediately above this line.
89effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  TIMEZONE_REQUEST_RESULT_COUNT = 4
90effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch};
91effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
92effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch// Too many requests (more than 1) mean there is a problem in implementation.
93effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochvoid RecordUmaEvent(TimeZoneRequestEvent event) {
94effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  UMA_HISTOGRAM_ENUMERATION(
95effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      "TimeZone.TimeZoneRequest.Event", event, TIMEZONE_REQUEST_EVENT_COUNT);
96effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch}
97effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
98effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochvoid RecordUmaResponseCode(int code) {
99effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  UMA_HISTOGRAM_SPARSE_SLOWLY("TimeZone.TimeZoneRequest.ResponseCode", code);
100effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch}
101effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
102effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch// Slow timezone resolve leads to bad user experience.
103effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochvoid RecordUmaResponseTime(base::TimeDelta elapsed, bool success) {
104effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  if (success) {
105effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    UMA_HISTOGRAM_TIMES("TimeZone.TimeZoneRequest.ResponseSuccessTime",
106effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch                        elapsed);
107effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  } else {
108effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    UMA_HISTOGRAM_TIMES("TimeZone.TimeZoneRequest.ResponseFailureTime",
109effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch                        elapsed);
110effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  }
111effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch}
112effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
113effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochvoid RecordUmaResult(TimeZoneRequestResult result, unsigned retries) {
114effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  UMA_HISTOGRAM_ENUMERATION(
115effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      "TimeZone.TimeZoneRequest.Result", result, TIMEZONE_REQUEST_RESULT_COUNT);
116effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  UMA_HISTOGRAM_SPARSE_SLOWLY("TimeZone.TimeZoneRequest.Retries", retries);
117effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch}
118effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
119effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch// Creates the request url to send to the server.
120effb81e5f8246d0db0270817048dc992db66e9fbBen MurdochGURL TimeZoneRequestURL(const GURL& url,
1210529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch                        const Geoposition& geoposition,
122effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch                        bool sensor) {
123effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  std::string query(url.query());
124effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  query += base::StringPrintf(
125effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      "%s=%f,%f", kLocationString, geoposition.latitude, geoposition.longitude);
126effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  if (url == DefaultTimezoneProviderURL()) {
127effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    std::string api_key = google_apis::GetAPIKey();
128effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    if (!api_key.empty()) {
129effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      query += "&";
130effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      query += kKeyString;
131effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      query += "=";
132effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      query += net::EscapeQueryParamValue(api_key, true);
133effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    }
134effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  }
135effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  if (!geoposition.timestamp.is_null()) {
136effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    query += base::StringPrintf(
137effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch        "&%s=%ld", kTimestampString, geoposition.timestamp.ToTimeT());
138effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  }
139effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  query += "&";
140effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  query += kSensorString;
141effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  query += "=";
142effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  query += (sensor ? "true" : "false");
143effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
144effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  GURL::Replacements replacements;
145effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  replacements.SetQueryStr(query);
146effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  return url.ReplaceComponents(replacements);
147effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch}
148effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
149effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochvoid PrintTimeZoneError(const GURL& server_url,
150effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch                        const std::string& message,
151effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch                        TimeZoneResponseData* timezone) {
152effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  timezone->status = TimeZoneResponseData::REQUEST_ERROR;
153effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  timezone->error_message =
154effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      base::StringPrintf("TimeZone provider at '%s' : %s.",
155effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch                         server_url.GetOrigin().spec().c_str(),
156effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch                         message.c_str());
157effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  LOG(WARNING) << "TimeZoneRequest::GetTimeZoneFromResponse() : "
158effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch               << timezone->error_message;
159effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch}
160effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
161effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch// Parses the server response body. Returns true if parsing was successful.
162effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch// Sets |*timezone| to the parsed TimeZone if a valid timezone was received,
163effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch// otherwise leaves it unchanged.
164effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochbool ParseServerResponse(const GURL& server_url,
165effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch                         const std::string& response_body,
166effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch                         TimeZoneResponseData* timezone) {
167effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  DCHECK(timezone);
168effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
169effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  if (response_body.empty()) {
170effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    PrintTimeZoneError(server_url, "Server returned empty response", timezone);
171effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    RecordUmaEvent(TIMEZONE_REQUEST_EVENT_RESPONSE_EMPTY);
172effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    return false;
173effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  }
174effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  VLOG(1) << "TimeZoneRequest::ParseServerResponse() : Parsing response "
175effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch          << response_body;
176effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
177effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  // Parse the response, ignoring comments.
178effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  std::string error_msg;
179effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  scoped_ptr<base::Value> response_value(base::JSONReader::ReadAndReturnError(
180effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      response_body, base::JSON_PARSE_RFC, NULL, &error_msg));
181effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  if (response_value == NULL) {
182effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    PrintTimeZoneError(server_url, "JSONReader failed: " + error_msg, timezone);
183effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    RecordUmaEvent(TIMEZONE_REQUEST_EVENT_RESPONSE_MALFORMED);
184effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    return false;
185effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  }
186effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
187effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  const base::DictionaryValue* response_object = NULL;
188effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  if (!response_value->GetAsDictionary(&response_object)) {
189effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    PrintTimeZoneError(server_url,
190effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch                       "Unexpected response type : " +
191effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch                           base::StringPrintf("%u", response_value->GetType()),
192effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch                       timezone);
193effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    RecordUmaEvent(TIMEZONE_REQUEST_EVENT_RESPONSE_MALFORMED);
194effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    return false;
195effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  }
196effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
197effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  std::string status;
198effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
199effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  if (!response_object->GetStringWithoutPathExpansion(kStatusString, &status)) {
200effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    PrintTimeZoneError(server_url, "Missing status attribute.", timezone);
201effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    RecordUmaEvent(TIMEZONE_REQUEST_EVENT_RESPONSE_MALFORMED);
202effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    return false;
203effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  }
204effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
205effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  bool found = false;
206effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  for (size_t i = 0; i < arraysize(statusString2Enum); ++i) {
207effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    if (status != statusString2Enum[i].string)
208effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      continue;
209effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
210effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    timezone->status = statusString2Enum[i].value;
211effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    found = true;
212effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    break;
213effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  }
214effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
215effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  if (!found) {
216effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    PrintTimeZoneError(
217effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch        server_url, "Bad status attribute value: '" + status + "'", timezone);
218effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    RecordUmaEvent(TIMEZONE_REQUEST_EVENT_RESPONSE_MALFORMED);
219effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    return false;
220effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  }
221effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
222effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  const bool status_ok = (timezone->status == TimeZoneResponseData::OK);
223effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
224effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  if (!response_object->GetDoubleWithoutPathExpansion(kDstOffsetString,
225effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch                                                      &timezone->dstOffset) &&
226effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      status_ok) {
227effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    PrintTimeZoneError(server_url, "Missing dstOffset attribute.", timezone);
228effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    RecordUmaEvent(TIMEZONE_REQUEST_EVENT_RESPONSE_MALFORMED);
229effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    return false;
230effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  }
231effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
232effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  if (!response_object->GetDoubleWithoutPathExpansion(kRawOffsetString,
233effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch                                                      &timezone->rawOffset) &&
234effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      status_ok) {
235effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    PrintTimeZoneError(server_url, "Missing rawOffset attribute.", timezone);
236effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    RecordUmaEvent(TIMEZONE_REQUEST_EVENT_RESPONSE_MALFORMED);
237effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    return false;
238effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  }
239effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
240effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  if (!response_object->GetStringWithoutPathExpansion(kTimeZoneIdString,
241effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch                                                      &timezone->timeZoneId) &&
242effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      status_ok) {
243effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    PrintTimeZoneError(server_url, "Missing timeZoneId attribute.", timezone);
244effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    RecordUmaEvent(TIMEZONE_REQUEST_EVENT_RESPONSE_MALFORMED);
245effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    return false;
246effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  }
247effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
248effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  if (!response_object->GetStringWithoutPathExpansion(
249effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch          kTimeZoneNameString, &timezone->timeZoneName) &&
250effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      status_ok) {
251effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    PrintTimeZoneError(server_url, "Missing timeZoneName attribute.", timezone);
252effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    RecordUmaEvent(TIMEZONE_REQUEST_EVENT_RESPONSE_MALFORMED);
253effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    return false;
254effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  }
255effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
256effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  // "error_message" field is optional. Ignore result.
257effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  response_object->GetStringWithoutPathExpansion(kErrorMessageString,
258effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch                                                 &timezone->error_message);
259effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
260effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  return true;
261effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch}
262effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
263effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch// Attempts to extract a position from the response. Detects and indicates
264effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch// various failure cases.
265effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochscoped_ptr<TimeZoneResponseData> GetTimeZoneFromResponse(
266effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    bool http_success,
267effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    int status_code,
268effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    const std::string& response_body,
269effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    const GURL& server_url) {
270effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  scoped_ptr<TimeZoneResponseData> timezone(new TimeZoneResponseData);
271effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
272effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  // HttpPost can fail for a number of reasons. Most likely this is because
273effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  // we're offline, or there was no response.
274effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  if (!http_success) {
275effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    PrintTimeZoneError(server_url, "No response received", timezone.get());
276effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    RecordUmaEvent(TIMEZONE_REQUEST_EVENT_RESPONSE_EMPTY);
277effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    return timezone.Pass();
278effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  }
279effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  if (status_code != net::HTTP_OK) {
280effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    std::string message = "Returned error code ";
281effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    message += base::IntToString(status_code);
282effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    PrintTimeZoneError(server_url, message, timezone.get());
283effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    RecordUmaEvent(TIMEZONE_REQUEST_EVENT_RESPONSE_NOT_OK);
284effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    return timezone.Pass();
285effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  }
286effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
287effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  if (!ParseServerResponse(server_url, response_body, timezone.get()))
288effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    return timezone.Pass();
289effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
290effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  RecordUmaEvent(TIMEZONE_REQUEST_EVENT_RESPONSE_SUCCESS);
291effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  return timezone.Pass();
292effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch}
293effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
294effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch}  // namespace
295effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
296effb81e5f8246d0db0270817048dc992db66e9fbBen MurdochTimeZoneResponseData::TimeZoneResponseData()
297effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    : dstOffset(0), rawOffset(0), status(ZERO_RESULTS) {
298effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch}
299effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
300effb81e5f8246d0db0270817048dc992db66e9fbBen MurdochGURL DefaultTimezoneProviderURL() {
301effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  return GURL(kDefaultTimezoneProviderUrl);
302effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch}
303effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
304effb81e5f8246d0db0270817048dc992db66e9fbBen MurdochTimeZoneRequest::TimeZoneRequest(
305effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    net::URLRequestContextGetter* url_context_getter,
306effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    const GURL& service_url,
3070529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    const Geoposition& geoposition,
308effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    bool sensor,
309effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    base::TimeDelta retry_timeout)
310effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    : url_context_getter_(url_context_getter),
311effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      service_url_(service_url),
312effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      geoposition_(geoposition),
313effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      sensor_(sensor),
314effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      retry_timeout_abs_(base::Time::Now() + retry_timeout),
315cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      retry_sleep_on_server_error_(base::TimeDelta::FromSeconds(
316cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          kResolveTimeZoneRetrySleepOnServerErrorSeconds)),
317cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      retry_sleep_on_bad_response_(base::TimeDelta::FromSeconds(
318cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)          kResolveTimeZoneRetrySleepBadResponseSeconds)),
319effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      retries_(0) {
320effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch}
321effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
322effb81e5f8246d0db0270817048dc992db66e9fbBen MurdochTimeZoneRequest::~TimeZoneRequest() {
323effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  DCHECK(thread_checker_.CalledOnValidThread());
324effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
325effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  // If callback is not empty, request is cancelled.
326effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  if (!callback_.is_null()) {
327effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    RecordUmaResponseTime(base::Time::Now() - request_started_at_, false);
328effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    RecordUmaResult(TIMEZONE_REQUEST_RESULT_CANCELLED, retries_);
329effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  }
330effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch}
331effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
332effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochvoid TimeZoneRequest::StartRequest() {
333effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  DCHECK(thread_checker_.CalledOnValidThread());
334effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  RecordUmaEvent(TIMEZONE_REQUEST_EVENT_REQUEST_START);
335effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  request_started_at_ = base::Time::Now();
336effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  ++retries_;
337effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
338effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  url_fetcher_.reset(
339effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      net::URLFetcher::Create(request_url_, net::URLFetcher::GET, this));
340effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  url_fetcher_->SetRequestContext(url_context_getter_);
341effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  url_fetcher_->SetLoadFlags(net::LOAD_BYPASS_CACHE |
342effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch                             net::LOAD_DISABLE_CACHE |
343effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch                             net::LOAD_DO_NOT_SAVE_COOKIES |
344effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch                             net::LOAD_DO_NOT_SEND_COOKIES |
345effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch                             net::LOAD_DO_NOT_SEND_AUTH_DATA);
346effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  url_fetcher_->Start();
347effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch}
348effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
349effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochvoid TimeZoneRequest::MakeRequest(TimeZoneResponseCallback callback) {
350effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  callback_ = callback;
351effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  request_url_ =
352effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      TimeZoneRequestURL(service_url_, geoposition_, false /* sensor */);
353effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  StartRequest();
354effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch}
355effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
356effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochvoid TimeZoneRequest::Retry(bool server_error) {
357cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  const base::TimeDelta delay(server_error ? retry_sleep_on_server_error_
358cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)                                           : retry_sleep_on_bad_response_);
359effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  timezone_request_scheduled_.Start(
360effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      FROM_HERE, delay, this, &TimeZoneRequest::StartRequest);
361effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch}
362effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
363effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochvoid TimeZoneRequest::OnURLFetchComplete(const net::URLFetcher* source) {
364effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  DCHECK_EQ(url_fetcher_.get(), source);
365effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
366effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  net::URLRequestStatus status = source->GetStatus();
367effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  int response_code = source->GetResponseCode();
368effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  RecordUmaResponseCode(response_code);
369effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
370effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  std::string data;
371effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  source->GetResponseAsString(&data);
372effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  scoped_ptr<TimeZoneResponseData> timezone = GetTimeZoneFromResponse(
373effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      status.is_success(), response_code, data, source->GetURL());
374effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  const bool server_error =
375effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      !status.is_success() || (response_code >= 500 && response_code < 600);
376effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  url_fetcher_.reset();
377effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
378effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  DVLOG(1) << "TimeZoneRequest::OnURLFetchComplete(): timezone={"
379effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch           << timezone->ToStringForDebug() << "}";
380effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
381effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  const base::Time now = base::Time::Now();
382effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  const bool retry_timeout = (now >= retry_timeout_abs_);
383effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
384effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  const bool success = (timezone->status == TimeZoneResponseData::OK);
385effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  if (!success && !retry_timeout) {
386effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    Retry(server_error);
387effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    return;
388effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  }
389effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  RecordUmaResponseTime(base::Time::Now() - request_started_at_, success);
390effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
391effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  const TimeZoneRequestResult result =
392effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      (server_error ? TIMEZONE_REQUEST_RESULT_SERVER_ERROR
393effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch                    : (success ? TIMEZONE_REQUEST_RESULT_SUCCESS
394effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch                               : TIMEZONE_REQUEST_RESULT_FAILURE));
395effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  RecordUmaResult(result, retries_);
396effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
397effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  TimeZoneResponseCallback callback = callback_;
398effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
399effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  // Empty callback is used to identify "completed or not yet started request".
400effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  callback_.Reset();
401effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
402effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  // callback.Run() usually destroys TimeZoneRequest, because this is the way
403effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  // callback is implemented in TimeZoneProvider.
404effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  callback.Run(timezone.Pass(), server_error);
405effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  // "this" is already destroyed here.
406effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch}
407effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
408effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochstd::string TimeZoneResponseData::ToStringForDebug() const {
409effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  static const char* const status2string[] = {
410effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      "OK",
411effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      "INVALID_REQUEST",
412effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      "OVER_QUERY_LIMIT",
413effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      "REQUEST_DENIED",
414effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      "UNKNOWN_ERROR",
415effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      "ZERO_RESULTS",
416effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      "REQUEST_ERROR"
417effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  };
418effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
419effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch  return base::StringPrintf(
420effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      "dstOffset=%f, rawOffset=%f, timeZoneId='%s', timeZoneName='%s', "
421effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      "error_message='%s', status=%u (%s)",
422effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      dstOffset,
423effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      rawOffset,
424effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      timeZoneId.c_str(),
425effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      timeZoneName.c_str(),
426effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      error_message.c_str(),
427effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      (unsigned)status,
428effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch      (status < arraysize(status2string) ? status2string[status] : "unknown"));
429cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)}
430effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch
431effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch}  // namespace chromeos
432