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 |