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 51 void RecordUmaEvent(NetworkLocationRequestEvent event) { 52 UMA_HISTOGRAM_ENUMERATION("Geolocation.NetworkLocationRequest.Event", 53 event, NETWORK_LOCATION_REQUEST_EVENT_COUNT); 54 } 55 56 void RecordUmaResponseCode(int code) { 57 UMA_HISTOGRAM_SPARSE_SLOWLY("Geolocation.NetworkLocationRequest.ResponseCode", 58 code); 59 } 60 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 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 111 NetworkLocationRequest::~NetworkLocationRequest() { 112 } 113 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 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 { 186 bool operator()(const AccessPointData* ap1, 187 const AccessPointData* ap2) const { 188 return ap2->radio_signal_strength < ap1->radio_signal_strength; 189 } 190 }; 191 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 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 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 != kint32min) 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 (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", 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 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 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. 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 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