• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
26 namespace content {
27 namespace {
28 
29 const char kAccessTokenString[] = "accessToken";
30 const char kLocationString[] = "location";
31 const char kLatitudeString[] = "lat";
32 const char kLongitudeString[] = "lng";
33 const char kAccuracyString[] = "accuracy";
34 
35 enum 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 
RecordUmaEvent(NetworkLocationRequestEvent event)51 void RecordUmaEvent(NetworkLocationRequestEvent event) {
52   UMA_HISTOGRAM_ENUMERATION("Geolocation.NetworkLocationRequest.Event",
53       event, NETWORK_LOCATION_REQUEST_EVENT_COUNT);
54 }
55 
RecordUmaResponseCode(int code)56 void RecordUmaResponseCode(int code) {
57   UMA_HISTOGRAM_SPARSE_SLOWLY("Geolocation.NetworkLocationRequest.ResponseCode",
58       code);
59 }
60 
RecordUmaAccessPoints(int count)61 void 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.
71 GURL FormRequestURL(const GURL& url);
72 
73 void 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.
80 void 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.
91 bool ParseServerResponse(const std::string& response_body,
92                          const base::Time& timestamp,
93                          Geoposition* position,
94                          base::string16* access_token);
95 void AddWifiData(const WifiData& wifi_data,
96                  int age_milliseconds,
97                  base::DictionaryValue* request);
98 }  // namespace
99 
100 int NetworkLocationRequest::url_fetcher_id_for_tests = 0;
101 
NetworkLocationRequest(net::URLRequestContextGetter * context,const GURL & url,LocationResponseCallback callback)102 NetworkLocationRequest::NetworkLocationRequest(
103     net::URLRequestContextGetter* context,
104     const GURL& url,
105     LocationResponseCallback callback)
106         : url_context_(context),
107           callback_(callback),
108           url_(url) {
109 }
110 
~NetworkLocationRequest()111 NetworkLocationRequest::~NetworkLocationRequest() {
112 }
113 
MakeRequest(const base::string16 & access_token,const WifiData & wifi_data,const base::Time & timestamp)114 bool NetworkLocationRequest::MakeRequest(const base::string16& access_token,
115                                          const WifiData& wifi_data,
116                                          const base::Time& timestamp) {
117   RecordUmaEvent(NETWORK_LOCATION_REQUEST_EVENT_REQUEST_START);
118   RecordUmaAccessPoints(wifi_data.access_point_data.size());
119   if (url_fetcher_ != NULL) {
120     DVLOG(1) << "NetworkLocationRequest : Cancelling pending request";
121     RecordUmaEvent(NETWORK_LOCATION_REQUEST_EVENT_REQUEST_CANCEL);
122     url_fetcher_.reset();
123   }
124   wifi_data_ = wifi_data;
125   timestamp_ = timestamp;
126 
127   GURL request_url = FormRequestURL(url_);
128   url_fetcher_.reset(net::URLFetcher::Create(
129       url_fetcher_id_for_tests, request_url, net::URLFetcher::POST, this));
130   url_fetcher_->SetRequestContext(url_context_.get());
131   std::string upload_data;
132   FormUploadData(wifi_data, timestamp, access_token, &upload_data);
133   url_fetcher_->SetUploadData("application/json", upload_data);
134   url_fetcher_->SetLoadFlags(
135       net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE |
136       net::LOAD_DO_NOT_SAVE_COOKIES | net::LOAD_DO_NOT_SEND_COOKIES |
137       net::LOAD_DO_NOT_SEND_AUTH_DATA);
138 
139   start_time_ = base::TimeTicks::Now();
140   url_fetcher_->Start();
141   return true;
142 }
143 
OnURLFetchComplete(const net::URLFetcher * source)144 void NetworkLocationRequest::OnURLFetchComplete(
145     const net::URLFetcher* source) {
146   DCHECK_EQ(url_fetcher_.get(), source);
147 
148   net::URLRequestStatus status = source->GetStatus();
149   int response_code = source->GetResponseCode();
150   RecordUmaResponseCode(response_code);
151 
152   Geoposition position;
153   base::string16 access_token;
154   std::string data;
155   source->GetResponseAsString(&data);
156   GetLocationFromResponse(status.is_success(),
157                           response_code,
158                           data,
159                           timestamp_,
160                           source->GetURL(),
161                           &position,
162                           &access_token);
163   const bool server_error =
164       !status.is_success() || (response_code >= 500 && response_code < 600);
165   url_fetcher_.reset();
166 
167   if (!server_error) {
168     const base::TimeDelta request_time = base::TimeTicks::Now() - start_time_;
169 
170     UMA_HISTOGRAM_CUSTOM_TIMES(
171         "Net.Wifi.LbsLatency",
172         request_time,
173         base::TimeDelta::FromMilliseconds(1),
174         base::TimeDelta::FromSeconds(10),
175         100);
176   }
177 
178   DVLOG(1) << "NetworkLocationRequest::OnURLFetchComplete() : run callback.";
179   callback_.Run(position, server_error, access_token, wifi_data_);
180 }
181 
182 // Local functions.
183 namespace {
184 
185 struct AccessPointLess {
operator ()content::__anon4d3f53ed0211::AccessPointLess186   bool operator()(const AccessPointData* ap1,
187                   const AccessPointData* ap2) const {
188     return ap2->radio_signal_strength < ap1->radio_signal_strength;
189   }
190 };
191 
FormRequestURL(const GURL & url)192 GURL 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 
FormUploadData(const WifiData & wifi_data,const base::Time & timestamp,const base::string16 & access_token,std::string * upload_data)208 void 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 
AddString(const std::string & property_name,const std::string & value,base::DictionaryValue * dict)227 void 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 
AddInteger(const std::string & property_name,int value,base::DictionaryValue * dict)234 void 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 
AddWifiData(const WifiData & wifi_data,int age_milliseconds,base::DictionaryValue * request)241 void 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 
FormatPositionError(const GURL & server_url,const std::string & message,Geoposition * position)275 void 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 
GetLocationFromResponse(bool http_post_result,int status_code,const std::string & response_body,const base::Time & timestamp,const GURL & server_url,Geoposition * position,base::string16 * access_token)288 void 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.
GetAsDouble(const base::DictionaryValue & object,const std::string & property_name,double * out)335 bool 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 
ParseServerResponse(const std::string & response_body,const base::Time & timestamp,Geoposition * position,base::string16 * access_token)351 bool 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, longitude;
413   if (!GetAsDouble(*location_object, kLatitudeString, &latitude) ||
414       !GetAsDouble(*location_object, kLongitudeString, &longitude)) {
415     VLOG(1) << "ParseServerResponse() : location lacks lat and/or long.";
416     return false;
417   }
418   // All error paths covered: now start actually modifying postion.
419   position->latitude = latitude;
420   position->longitude = longitude;
421   position->timestamp = timestamp;
422 
423   // Other fields are optional.
424   GetAsDouble(*response_object, kAccuracyString, &position->accuracy);
425 
426   return true;
427 }
428 
429 }  // namespace
430 
431 }  // namespace content
432