| 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 |