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