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