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 "content/browser/geolocation/network_location_request.h"
6
7#include <set>
8#include <string>
9
10#include "base/json/json_reader.h"
11#include "base/json/json_writer.h"
12#include "base/metrics/histogram.h"
13#include "base/metrics/sparse_histogram.h"
14#include "base/strings/string_number_conversions.h"
15#include "base/strings/utf_string_conversions.h"
16#include "base/values.h"
17#include "content/browser/geolocation/location_arbitrator_impl.h"
18#include "content/public/common/geoposition.h"
19#include "google_apis/google_api_keys.h"
20#include "net/base/escape.h"
21#include "net/base/load_flags.h"
22#include "net/url_request/url_fetcher.h"
23#include "net/url_request/url_request_context_getter.h"
24#include "net/url_request/url_request_status.h"
25
26namespace content {
27namespace {
28
29const char kAccessTokenString[] = "accessToken";
30const char kLocationString[] = "location";
31const char kLatitudeString[] = "lat";
32const char kLongitudeString[] = "lng";
33const char kAccuracyString[] = "accuracy";
34
35enum NetworkLocationRequestEvent {
36  // NOTE: Do not renumber these as that would confuse interpretation of
37  // previously logged data. When making changes, also update the enum list
38  // in tools/metrics/histograms/histograms.xml to keep it in sync.
39  NETWORK_LOCATION_REQUEST_EVENT_REQUEST_START = 0,
40  NETWORK_LOCATION_REQUEST_EVENT_REQUEST_CANCEL = 1,
41  NETWORK_LOCATION_REQUEST_EVENT_RESPONSE_SUCCESS = 2,
42  NETWORK_LOCATION_REQUEST_EVENT_RESPONSE_NOT_OK = 3,
43  NETWORK_LOCATION_REQUEST_EVENT_RESPONSE_EMPTY = 4,
44  NETWORK_LOCATION_REQUEST_EVENT_RESPONSE_MALFORMED = 5,
45  NETWORK_LOCATION_REQUEST_EVENT_RESPONSE_INVALID_FIX = 6,
46
47  // NOTE: Add entries only immediately above this line.
48  NETWORK_LOCATION_REQUEST_EVENT_COUNT = 7
49};
50
51void RecordUmaEvent(NetworkLocationRequestEvent event) {
52  UMA_HISTOGRAM_ENUMERATION("Geolocation.NetworkLocationRequest.Event",
53      event, NETWORK_LOCATION_REQUEST_EVENT_COUNT);
54}
55
56void RecordUmaResponseCode(int code) {
57  UMA_HISTOGRAM_SPARSE_SLOWLY("Geolocation.NetworkLocationRequest.ResponseCode",
58      code);
59}
60
61void RecordUmaAccessPoints(int count) {
62  const int min = 0;
63  const int max = 20;
64  const int buckets = 21;
65  UMA_HISTOGRAM_CUSTOM_COUNTS("Geolocation.NetworkLocationRequest.AccessPoints",
66      count, min, max, buckets);
67}
68
69// Local functions
70// Creates the request url to send to the server.
71GURL FormRequestURL(const GURL& url);
72
73void FormUploadData(const WifiData& wifi_data,
74                    const base::Time& timestamp,
75                    const base::string16& access_token,
76                    std::string* upload_data);
77
78// Attempts to extract a position from the response. Detects and indicates
79// various failure cases.
80void GetLocationFromResponse(bool http_post_result,
81                             int status_code,
82                             const std::string& response_body,
83                             const base::Time& timestamp,
84                             const GURL& server_url,
85                             Geoposition* position,
86                             base::string16* access_token);
87
88// Parses the server response body. Returns true if parsing was successful.
89// Sets |*position| to the parsed location if a valid fix was received,
90// otherwise leaves it unchanged.
91bool ParseServerResponse(const std::string& response_body,
92                         const base::Time& timestamp,
93                         Geoposition* position,
94                         base::string16* access_token);
95void AddWifiData(const WifiData& wifi_data,
96                 int age_milliseconds,
97                 base::DictionaryValue* request);
98}  // namespace
99
100int NetworkLocationRequest::url_fetcher_id_for_tests = 0;
101
102NetworkLocationRequest::NetworkLocationRequest(
103    net::URLRequestContextGetter* context,
104    const GURL& url,
105    LocationResponseCallback callback)
106    : url_context_(context), location_response_callback_(callback), url_(url) {
107}
108
109NetworkLocationRequest::~NetworkLocationRequest() {
110}
111
112bool NetworkLocationRequest::MakeRequest(const base::string16& access_token,
113                                         const WifiData& wifi_data,
114                                         const base::Time& timestamp) {
115  RecordUmaEvent(NETWORK_LOCATION_REQUEST_EVENT_REQUEST_START);
116  RecordUmaAccessPoints(wifi_data.access_point_data.size());
117  if (url_fetcher_ != NULL) {
118    DVLOG(1) << "NetworkLocationRequest : Cancelling pending request";
119    RecordUmaEvent(NETWORK_LOCATION_REQUEST_EVENT_REQUEST_CANCEL);
120    url_fetcher_.reset();
121  }
122  wifi_data_ = wifi_data;
123  wifi_data_timestamp_ = timestamp;
124
125  GURL request_url = FormRequestURL(url_);
126  url_fetcher_.reset(net::URLFetcher::Create(
127      url_fetcher_id_for_tests, request_url, net::URLFetcher::POST, this));
128  url_fetcher_->SetRequestContext(url_context_.get());
129  std::string upload_data;
130  FormUploadData(wifi_data, timestamp, access_token, &upload_data);
131  url_fetcher_->SetUploadData("application/json", upload_data);
132  url_fetcher_->SetLoadFlags(
133      net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE |
134      net::LOAD_DO_NOT_SAVE_COOKIES | net::LOAD_DO_NOT_SEND_COOKIES |
135      net::LOAD_DO_NOT_SEND_AUTH_DATA);
136
137  request_start_time_ = base::TimeTicks::Now();
138  url_fetcher_->Start();
139  return true;
140}
141
142void NetworkLocationRequest::OnURLFetchComplete(
143    const net::URLFetcher* source) {
144  DCHECK_EQ(url_fetcher_.get(), source);
145
146  net::URLRequestStatus status = source->GetStatus();
147  int response_code = source->GetResponseCode();
148  RecordUmaResponseCode(response_code);
149
150  Geoposition position;
151  base::string16 access_token;
152  std::string data;
153  source->GetResponseAsString(&data);
154  GetLocationFromResponse(status.is_success(),
155                          response_code,
156                          data,
157                          wifi_data_timestamp_,
158                          source->GetURL(),
159                          &position,
160                          &access_token);
161  const bool server_error =
162      !status.is_success() || (response_code >= 500 && response_code < 600);
163  url_fetcher_.reset();
164
165  if (!server_error) {
166    const base::TimeDelta request_time =
167        base::TimeTicks::Now() - request_start_time_;
168
169    UMA_HISTOGRAM_CUSTOM_TIMES(
170        "Net.Wifi.LbsLatency",
171        request_time,
172        base::TimeDelta::FromMilliseconds(1),
173        base::TimeDelta::FromSeconds(10),
174        100);
175  }
176
177  DVLOG(1) << "NetworkLocationRequest::OnURLFetchComplete() : run callback.";
178  location_response_callback_.Run(
179      position, server_error, access_token, wifi_data_);
180}
181
182// Local functions.
183namespace {
184
185struct AccessPointLess {
186  bool operator()(const AccessPointData* ap1,
187                  const AccessPointData* ap2) const {
188    return ap2->radio_signal_strength < ap1->radio_signal_strength;
189  }
190};
191
192GURL FormRequestURL(const GURL& url) {
193  if (url == LocationArbitratorImpl::DefaultNetworkProviderURL()) {
194    std::string api_key = google_apis::GetAPIKey();
195    if (!api_key.empty()) {
196      std::string query(url.query());
197      if (!query.empty())
198        query += "&";
199      query += "key=" + net::EscapeQueryParamValue(api_key, true);
200      GURL::Replacements replacements;
201      replacements.SetQueryStr(query);
202      return url.ReplaceComponents(replacements);
203    }
204  }
205  return url;
206}
207
208void FormUploadData(const WifiData& wifi_data,
209                    const base::Time& timestamp,
210                    const base::string16& access_token,
211                    std::string* upload_data) {
212  int age = kint32min;  // Invalid so AddInteger() will ignore.
213  if (!timestamp.is_null()) {
214    // Convert absolute timestamps into a relative age.
215    int64 delta_ms = (base::Time::Now() - timestamp).InMilliseconds();
216    if (delta_ms >= 0 && delta_ms < kint32max)
217      age = static_cast<int>(delta_ms);
218  }
219
220  base::DictionaryValue request;
221  AddWifiData(wifi_data, age, &request);
222  if (!access_token.empty())
223    request.SetString(kAccessTokenString, access_token);
224  base::JSONWriter::Write(&request, upload_data);
225}
226
227void AddString(const std::string& property_name, const std::string& value,
228               base::DictionaryValue* dict) {
229  DCHECK(dict);
230  if (!value.empty())
231    dict->SetString(property_name, value);
232}
233
234void AddInteger(const std::string& property_name, int value,
235                base::DictionaryValue* dict) {
236  DCHECK(dict);
237  if (value != kint32min)
238    dict->SetInteger(property_name, value);
239}
240
241void AddWifiData(const WifiData& wifi_data,
242                 int age_milliseconds,
243                 base::DictionaryValue* request) {
244  DCHECK(request);
245
246  if (wifi_data.access_point_data.empty())
247    return;
248
249  typedef std::multiset<const AccessPointData*, AccessPointLess> AccessPointSet;
250  AccessPointSet access_points_by_signal_strength;
251
252  for (WifiData::AccessPointDataSet::const_iterator iter =
253       wifi_data.access_point_data.begin();
254       iter != wifi_data.access_point_data.end();
255       ++iter) {
256    access_points_by_signal_strength.insert(&(*iter));
257  }
258
259  base::ListValue* wifi_access_point_list = new base::ListValue();
260  for (AccessPointSet::iterator iter =
261      access_points_by_signal_strength.begin();
262      iter != access_points_by_signal_strength.end();
263      ++iter) {
264    base::DictionaryValue* wifi_dict = new base::DictionaryValue();
265    AddString("macAddress", base::UTF16ToUTF8((*iter)->mac_address), wifi_dict);
266    AddInteger("signalStrength", (*iter)->radio_signal_strength, wifi_dict);
267    AddInteger("age", age_milliseconds, wifi_dict);
268    AddInteger("channel", (*iter)->channel, wifi_dict);
269    AddInteger("signalToNoiseRatio", (*iter)->signal_to_noise, wifi_dict);
270    wifi_access_point_list->Append(wifi_dict);
271  }
272  request->Set("wifiAccessPoints", wifi_access_point_list);
273}
274
275void FormatPositionError(const GURL& server_url,
276                         const std::string& message,
277                         Geoposition* position) {
278    position->error_code = Geoposition::ERROR_CODE_POSITION_UNAVAILABLE;
279    position->error_message = "Network location provider at '";
280    position->error_message += server_url.GetOrigin().spec();
281    position->error_message += "' : ";
282    position->error_message += message;
283    position->error_message += ".";
284    VLOG(1) << "NetworkLocationRequest::GetLocationFromResponse() : "
285            << position->error_message;
286}
287
288void GetLocationFromResponse(bool http_post_result,
289                             int status_code,
290                             const std::string& response_body,
291                             const base::Time& timestamp,
292                             const GURL& server_url,
293                             Geoposition* position,
294                             base::string16* access_token) {
295  DCHECK(position);
296  DCHECK(access_token);
297
298  // HttpPost can fail for a number of reasons. Most likely this is because
299  // we're offline, or there was no response.
300  if (!http_post_result) {
301    FormatPositionError(server_url, "No response received", position);
302    RecordUmaEvent(NETWORK_LOCATION_REQUEST_EVENT_RESPONSE_EMPTY);
303    return;
304  }
305  if (status_code != 200) {  // HTTP OK.
306    std::string message = "Returned error code ";
307    message += base::IntToString(status_code);
308    FormatPositionError(server_url, message, position);
309    RecordUmaEvent(NETWORK_LOCATION_REQUEST_EVENT_RESPONSE_NOT_OK);
310    return;
311  }
312  // We use the timestamp from the wifi data that was used to generate
313  // this position fix.
314  if (!ParseServerResponse(response_body, timestamp, position, access_token)) {
315    // We failed to parse the repsonse.
316    FormatPositionError(server_url, "Response was malformed", position);
317    RecordUmaEvent(NETWORK_LOCATION_REQUEST_EVENT_RESPONSE_MALFORMED);
318    return;
319  }
320  // The response was successfully parsed, but it may not be a valid
321  // position fix.
322  if (!position->Validate()) {
323    FormatPositionError(server_url,
324                        "Did not provide a good position fix", position);
325    RecordUmaEvent(NETWORK_LOCATION_REQUEST_EVENT_RESPONSE_INVALID_FIX);
326    return;
327  }
328  RecordUmaEvent(NETWORK_LOCATION_REQUEST_EVENT_RESPONSE_SUCCESS);
329}
330
331// Numeric values without a decimal point have type integer and IsDouble() will
332// return false. This is convenience function for detecting integer or floating
333// point numeric values. Note that isIntegral() includes boolean values, which
334// is not what we want.
335bool GetAsDouble(const base::DictionaryValue& object,
336                 const std::string& property_name,
337                 double* out) {
338  DCHECK(out);
339  const base::Value* value = NULL;
340  if (!object.Get(property_name, &value))
341    return false;
342  int value_as_int;
343  DCHECK(value);
344  if (value->GetAsInteger(&value_as_int)) {
345    *out = value_as_int;
346    return true;
347  }
348  return value->GetAsDouble(out);
349}
350
351bool ParseServerResponse(const std::string& response_body,
352                         const base::Time& timestamp,
353                         Geoposition* position,
354                         base::string16* access_token) {
355  DCHECK(position);
356  DCHECK(!position->Validate());
357  DCHECK(position->error_code == Geoposition::ERROR_CODE_NONE);
358  DCHECK(access_token);
359  DCHECK(!timestamp.is_null());
360
361  if (response_body.empty()) {
362    LOG(WARNING) << "ParseServerResponse() : Response was empty.";
363    return false;
364  }
365  DVLOG(1) << "ParseServerResponse() : Parsing response " << response_body;
366
367  // Parse the response, ignoring comments.
368  std::string error_msg;
369  scoped_ptr<base::Value> response_value(base::JSONReader::ReadAndReturnError(
370      response_body, base::JSON_PARSE_RFC, NULL, &error_msg));
371  if (response_value == NULL) {
372    LOG(WARNING) << "ParseServerResponse() : JSONReader failed : "
373                 << error_msg;
374    return false;
375  }
376
377  if (!response_value->IsType(base::Value::TYPE_DICTIONARY)) {
378    VLOG(1) << "ParseServerResponse() : Unexpected response type "
379            << response_value->GetType();
380    return false;
381  }
382  const base::DictionaryValue* response_object =
383      static_cast<base::DictionaryValue*>(response_value.get());
384
385  // Get the access token, if any.
386  response_object->GetString(kAccessTokenString, access_token);
387
388  // Get the location
389  const base::Value* location_value = NULL;
390  if (!response_object->Get(kLocationString, &location_value)) {
391    VLOG(1) << "ParseServerResponse() : Missing location attribute.";
392    // GLS returns a response with no location property to represent
393    // no fix available; return true to indicate successful parse.
394    return true;
395  }
396  DCHECK(location_value);
397
398  if (!location_value->IsType(base::Value::TYPE_DICTIONARY)) {
399    if (!location_value->IsType(base::Value::TYPE_NULL)) {
400      VLOG(1) << "ParseServerResponse() : Unexpected location type "
401              << location_value->GetType();
402      // If the network provider was unable to provide a position fix, it should
403      // return a HTTP 200, with "location" : null. Otherwise it's an error.
404      return false;
405    }
406    return true;  // Successfully parsed response containing no fix.
407  }
408  const base::DictionaryValue* location_object =
409      static_cast<const base::DictionaryValue*>(location_value);
410
411  // latitude and longitude fields are always required.
412  double latitude = 0;
413  double longitude = 0;
414  if (!GetAsDouble(*location_object, kLatitudeString, &latitude) ||
415      !GetAsDouble(*location_object, kLongitudeString, &longitude)) {
416    VLOG(1) << "ParseServerResponse() : location lacks lat and/or long.";
417    return false;
418  }
419  // All error paths covered: now start actually modifying postion.
420  position->latitude = latitude;
421  position->longitude = longitude;
422  position->timestamp = timestamp;
423
424  // Other fields are optional.
425  GetAsDouble(*response_object, kAccuracyString, &position->accuracy);
426
427  return true;
428}
429
430}  // namespace
431
432}  // namespace content
433