Index: content/browser/geolocation/network_location_request.cc |
diff --git a/content/browser/geolocation/network_location_request.cc b/content/browser/geolocation/network_location_request.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..18b052691b57dbd034abd4b3b549e41394c3fd84 |
--- /dev/null |
+++ b/content/browser/geolocation/network_location_request.cc |
@@ -0,0 +1,427 @@ |
+// Copyright (c) 2012 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "content/browser/geolocation/network_location_request.h" |
+ |
+#include <stdint.h> |
+ |
+#include <limits> |
+#include <set> |
+#include <string> |
+ |
+#include "base/json/json_reader.h" |
+#include "base/json/json_writer.h" |
+#include "base/metrics/histogram.h" |
+#include "base/metrics/sparse_histogram.h" |
+#include "base/strings/string_number_conversions.h" |
+#include "base/strings/utf_string_conversions.h" |
+#include "base/values.h" |
+#include "content/browser/geolocation/location_arbitrator_impl.h" |
+#include "content/public/common/geoposition.h" |
+#include "google_apis/google_api_keys.h" |
+#include "net/base/escape.h" |
+#include "net/base/load_flags.h" |
+#include "net/url_request/url_fetcher.h" |
+#include "net/url_request/url_request_context_getter.h" |
+#include "net/url_request/url_request_status.h" |
+ |
+namespace content { |
+namespace { |
+ |
+const char kAccessTokenString[] = "accessToken"; |
+const char kLocationString[] = "location"; |
+const char kLatitudeString[] = "lat"; |
+const char kLongitudeString[] = "lng"; |
+const char kAccuracyString[] = "accuracy"; |
+ |
+enum NetworkLocationRequestEvent { |
+ // NOTE: Do not renumber these as that would confuse interpretation of |
+ // previously logged data. When making changes, also update the enum list |
+ // in tools/metrics/histograms/histograms.xml to keep it in sync. |
+ NETWORK_LOCATION_REQUEST_EVENT_REQUEST_START = 0, |
+ NETWORK_LOCATION_REQUEST_EVENT_REQUEST_CANCEL = 1, |
+ NETWORK_LOCATION_REQUEST_EVENT_RESPONSE_SUCCESS = 2, |
+ NETWORK_LOCATION_REQUEST_EVENT_RESPONSE_NOT_OK = 3, |
+ NETWORK_LOCATION_REQUEST_EVENT_RESPONSE_EMPTY = 4, |
+ NETWORK_LOCATION_REQUEST_EVENT_RESPONSE_MALFORMED = 5, |
+ NETWORK_LOCATION_REQUEST_EVENT_RESPONSE_INVALID_FIX = 6, |
+ |
+ // NOTE: Add entries only immediately above this line. |
+ NETWORK_LOCATION_REQUEST_EVENT_COUNT = 7 |
+}; |
+ |
+void RecordUmaEvent(NetworkLocationRequestEvent event) { |
+ UMA_HISTOGRAM_ENUMERATION("Geolocation.NetworkLocationRequest.Event", |
+ event, NETWORK_LOCATION_REQUEST_EVENT_COUNT); |
+} |
+ |
+void RecordUmaResponseCode(int code) { |
+ UMA_HISTOGRAM_SPARSE_SLOWLY("Geolocation.NetworkLocationRequest.ResponseCode", |
+ code); |
+} |
+ |
+void RecordUmaAccessPoints(int count) { |
+ const int min = 0; |
+ const int max = 20; |
+ const int buckets = 21; |
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Geolocation.NetworkLocationRequest.AccessPoints", |
+ count, min, max, buckets); |
+} |
+ |
+// Local functions |
+// Creates the request url to send to the server. |
+GURL FormRequestURL(const GURL& url); |
+ |
+void FormUploadData(const WifiData& wifi_data, |
+ const base::Time& wifi_timestamp, |
+ const base::string16& access_token, |
+ std::string* upload_data); |
+ |
+// Attempts to extract a position from the response. Detects and indicates |
+// various failure cases. |
+void GetLocationFromResponse(bool http_post_result, |
+ int status_code, |
+ const std::string& response_body, |
+ const base::Time& wifi_timestamp, |
+ const GURL& server_url, |
+ Geoposition* position, |
+ base::string16* access_token); |
+ |
+// Parses the server response body. Returns true if parsing was successful. |
+// Sets |*position| to the parsed location if a valid fix was received, |
+// otherwise leaves it unchanged. |
+bool ParseServerResponse(const std::string& response_body, |
+ const base::Time& wifi_timestamp, |
+ Geoposition* position, |
+ base::string16* access_token); |
+void AddWifiData(const WifiData& wifi_data, |
+ int age_milliseconds, |
+ base::DictionaryValue* request); |
+} // namespace |
+ |
+int NetworkLocationRequest::url_fetcher_id_for_tests = 0; |
+ |
+NetworkLocationRequest::NetworkLocationRequest( |
+ const scoped_refptr<net::URLRequestContextGetter>& context, |
+ const GURL& url, |
+ LocationResponseCallback callback) |
+ : url_context_(context), location_response_callback_(callback), url_(url) { |
+} |
+ |
+NetworkLocationRequest::~NetworkLocationRequest() { |
+} |
+ |
+bool NetworkLocationRequest::MakeRequest(const base::string16& access_token, |
+ const WifiData& wifi_data, |
+ const base::Time& wifi_timestamp) { |
+ RecordUmaEvent(NETWORK_LOCATION_REQUEST_EVENT_REQUEST_START); |
+ RecordUmaAccessPoints(wifi_data.access_point_data.size()); |
+ if (url_fetcher_ != NULL) { |
+ DVLOG(1) << "NetworkLocationRequest : Cancelling pending request"; |
+ RecordUmaEvent(NETWORK_LOCATION_REQUEST_EVENT_REQUEST_CANCEL); |
+ url_fetcher_.reset(); |
+ } |
+ wifi_data_ = wifi_data; |
+ wifi_timestamp_ = wifi_timestamp; |
+ |
+ GURL request_url = FormRequestURL(url_); |
+ url_fetcher_ = net::URLFetcher::Create(url_fetcher_id_for_tests, request_url, |
+ net::URLFetcher::POST, this); |
+ url_fetcher_->SetRequestContext(url_context_.get()); |
+ std::string upload_data; |
+ FormUploadData(wifi_data, wifi_timestamp, access_token, &upload_data); |
+ url_fetcher_->SetUploadData("application/json", upload_data); |
+ url_fetcher_->SetLoadFlags( |
+ net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE | |
+ net::LOAD_DO_NOT_SAVE_COOKIES | net::LOAD_DO_NOT_SEND_COOKIES | |
+ net::LOAD_DO_NOT_SEND_AUTH_DATA); |
+ |
+ request_start_time_ = base::TimeTicks::Now(); |
+ url_fetcher_->Start(); |
+ return true; |
+} |
+ |
+void NetworkLocationRequest::OnURLFetchComplete( |
+ const net::URLFetcher* source) { |
+ DCHECK_EQ(url_fetcher_.get(), source); |
+ |
+ net::URLRequestStatus status = source->GetStatus(); |
+ int response_code = source->GetResponseCode(); |
+ RecordUmaResponseCode(response_code); |
+ |
+ Geoposition position; |
+ base::string16 access_token; |
+ std::string data; |
+ source->GetResponseAsString(&data); |
+ GetLocationFromResponse(status.is_success(), response_code, data, |
+ wifi_timestamp_, source->GetURL(), &position, |
+ &access_token); |
+ const bool server_error = |
+ !status.is_success() || (response_code >= 500 && response_code < 600); |
+ url_fetcher_.reset(); |
+ |
+ if (!server_error) { |
+ const base::TimeDelta request_time = |
+ base::TimeTicks::Now() - request_start_time_; |
+ |
+ UMA_HISTOGRAM_CUSTOM_TIMES( |
+ "Net.Wifi.LbsLatency", |
+ request_time, |
+ base::TimeDelta::FromMilliseconds(1), |
+ base::TimeDelta::FromSeconds(10), |
+ 100); |
+ } |
+ |
+ DVLOG(1) << "NetworkLocationRequest::OnURLFetchComplete() : run callback."; |
+ location_response_callback_.Run( |
+ position, server_error, access_token, wifi_data_); |
+} |
+ |
+// Local functions. |
+namespace { |
+ |
+struct AccessPointLess { |
+ bool operator()(const AccessPointData* ap1, |
+ const AccessPointData* ap2) const { |
+ return ap2->radio_signal_strength < ap1->radio_signal_strength; |
+ } |
+}; |
+ |
+GURL FormRequestURL(const GURL& url) { |
+ if (url == LocationArbitratorImpl::DefaultNetworkProviderURL()) { |
+ std::string api_key = google_apis::GetAPIKey(); |
+ if (!api_key.empty()) { |
+ std::string query(url.query()); |
+ if (!query.empty()) |
+ query += "&"; |
+ query += "key=" + net::EscapeQueryParamValue(api_key, true); |
+ GURL::Replacements replacements; |
+ replacements.SetQueryStr(query); |
+ return url.ReplaceComponents(replacements); |
+ } |
+ } |
+ return url; |
+} |
+ |
+void FormUploadData(const WifiData& wifi_data, |
+ const base::Time& wifi_timestamp, |
+ const base::string16& access_token, |
+ std::string* upload_data) { |
+ int age = std::numeric_limits<int32_t>::min(); // Invalid so AddInteger() |
+ // will ignore. |
+ if (!wifi_timestamp.is_null()) { |
+ // Convert absolute timestamps into a relative age. |
+ int64_t delta_ms = (base::Time::Now() - wifi_timestamp).InMilliseconds(); |
+ if (delta_ms >= 0 && delta_ms < std::numeric_limits<int32_t>::max()) |
+ age = static_cast<int>(delta_ms); |
+ } |
+ |
+ base::DictionaryValue request; |
+ AddWifiData(wifi_data, age, &request); |
+ if (!access_token.empty()) |
+ request.SetString(kAccessTokenString, access_token); |
+ base::JSONWriter::Write(request, upload_data); |
+} |
+ |
+void AddString(const std::string& property_name, const std::string& value, |
+ base::DictionaryValue* dict) { |
+ DCHECK(dict); |
+ if (!value.empty()) |
+ dict->SetString(property_name, value); |
+} |
+ |
+void AddInteger(const std::string& property_name, int value, |
+ base::DictionaryValue* dict) { |
+ DCHECK(dict); |
+ if (value != std::numeric_limits<int32_t>::min()) |
+ dict->SetInteger(property_name, value); |
+} |
+ |
+void AddWifiData(const WifiData& wifi_data, |
+ int age_milliseconds, |
+ base::DictionaryValue* request) { |
+ DCHECK(request); |
+ |
+ if (wifi_data.access_point_data.empty()) |
+ return; |
+ |
+ typedef std::multiset<const AccessPointData*, AccessPointLess> AccessPointSet; |
+ AccessPointSet access_points_by_signal_strength; |
+ |
+ for (const auto& ap_data : wifi_data.access_point_data) |
+ access_points_by_signal_strength.insert(&ap_data); |
+ |
+ base::ListValue* wifi_access_point_list = new base::ListValue(); |
+ for (auto* ap_data : access_points_by_signal_strength) { |
+ base::DictionaryValue* wifi_dict = new base::DictionaryValue(); |
+ AddString("macAddress", base::UTF16ToUTF8(ap_data->mac_address), wifi_dict); |
+ AddInteger("signalStrength", ap_data->radio_signal_strength, wifi_dict); |
+ AddInteger("age", age_milliseconds, wifi_dict); |
+ AddInteger("channel", ap_data->channel, wifi_dict); |
+ AddInteger("signalToNoiseRatio", ap_data->signal_to_noise, wifi_dict); |
+ wifi_access_point_list->Append(wifi_dict); |
+ } |
+ request->Set("wifiAccessPoints", wifi_access_point_list); |
+} |
+ |
+void FormatPositionError(const GURL& server_url, |
+ const std::string& message, |
+ Geoposition* position) { |
+ position->error_code = Geoposition::ERROR_CODE_POSITION_UNAVAILABLE; |
+ position->error_message = "Network location provider at '"; |
+ position->error_message += server_url.GetOrigin().spec(); |
+ position->error_message += "' : "; |
+ position->error_message += message; |
+ position->error_message += "."; |
+ VLOG(1) << "NetworkLocationRequest::GetLocationFromResponse() : " |
+ << position->error_message; |
+} |
+ |
+void GetLocationFromResponse(bool http_post_result, |
+ int status_code, |
+ const std::string& response_body, |
+ const base::Time& wifi_timestamp, |
+ const GURL& server_url, |
+ Geoposition* position, |
+ base::string16* access_token) { |
+ DCHECK(position); |
+ DCHECK(access_token); |
+ |
+ // HttpPost can fail for a number of reasons. Most likely this is because |
+ // we're offline, or there was no response. |
+ if (!http_post_result) { |
+ FormatPositionError(server_url, "No response received", position); |
+ RecordUmaEvent(NETWORK_LOCATION_REQUEST_EVENT_RESPONSE_EMPTY); |
+ return; |
+ } |
+ if (status_code != 200) { // HTTP OK. |
+ std::string message = "Returned error code "; |
+ message += base::IntToString(status_code); |
+ FormatPositionError(server_url, message, position); |
+ RecordUmaEvent(NETWORK_LOCATION_REQUEST_EVENT_RESPONSE_NOT_OK); |
+ return; |
+ } |
+ // We use the timestamp from the wifi data that was used to generate |
+ // this position fix. |
+ if (!ParseServerResponse(response_body, wifi_timestamp, position, |
+ access_token)) { |
+ // We failed to parse the repsonse. |
+ FormatPositionError(server_url, "Response was malformed", position); |
+ RecordUmaEvent(NETWORK_LOCATION_REQUEST_EVENT_RESPONSE_MALFORMED); |
+ return; |
+ } |
+ // The response was successfully parsed, but it may not be a valid |
+ // position fix. |
+ if (!position->Validate()) { |
+ FormatPositionError(server_url, |
+ "Did not provide a good position fix", position); |
+ RecordUmaEvent(NETWORK_LOCATION_REQUEST_EVENT_RESPONSE_INVALID_FIX); |
+ return; |
+ } |
+ RecordUmaEvent(NETWORK_LOCATION_REQUEST_EVENT_RESPONSE_SUCCESS); |
+} |
+ |
+// Numeric values without a decimal point have type integer and IsDouble() will |
+// return false. This is convenience function for detecting integer or floating |
+// point numeric values. Note that isIntegral() includes boolean values, which |
+// is not what we want. |
+bool GetAsDouble(const base::DictionaryValue& object, |
+ const std::string& property_name, |
+ double* out) { |
+ DCHECK(out); |
+ const base::Value* value = NULL; |
+ if (!object.Get(property_name, &value)) |
+ return false; |
+ int value_as_int; |
+ DCHECK(value); |
+ if (value->GetAsInteger(&value_as_int)) { |
+ *out = value_as_int; |
+ return true; |
+ } |
+ return value->GetAsDouble(out); |
+} |
+ |
+bool ParseServerResponse(const std::string& response_body, |
+ const base::Time& wifi_timestamp, |
+ Geoposition* position, |
+ base::string16* access_token) { |
+ DCHECK(position); |
+ DCHECK(!position->Validate()); |
+ DCHECK(position->error_code == Geoposition::ERROR_CODE_NONE); |
+ DCHECK(access_token); |
+ DCHECK(!wifi_timestamp.is_null()); |
+ |
+ if (response_body.empty()) { |
+ LOG(WARNING) << "ParseServerResponse() : Response was empty."; |
+ return false; |
+ } |
+ DVLOG(1) << "ParseServerResponse() : Parsing response " << response_body; |
+ |
+ // Parse the response, ignoring comments. |
+ std::string error_msg; |
+ std::unique_ptr<base::Value> response_value = |
+ base::JSONReader::ReadAndReturnError(response_body, base::JSON_PARSE_RFC, |
+ NULL, &error_msg); |
+ if (response_value == NULL) { |
+ LOG(WARNING) << "ParseServerResponse() : JSONReader failed : " |
+ << error_msg; |
+ return false; |
+ } |
+ |
+ if (!response_value->IsType(base::Value::TYPE_DICTIONARY)) { |
+ VLOG(1) << "ParseServerResponse() : Unexpected response type " |
+ << response_value->GetType(); |
+ return false; |
+ } |
+ const base::DictionaryValue* response_object = |
+ static_cast<base::DictionaryValue*>(response_value.get()); |
+ |
+ // Get the access token, if any. |
+ response_object->GetString(kAccessTokenString, access_token); |
+ |
+ // Get the location |
+ const base::Value* location_value = NULL; |
+ if (!response_object->Get(kLocationString, &location_value)) { |
+ VLOG(1) << "ParseServerResponse() : Missing location attribute."; |
+ // GLS returns a response with no location property to represent |
+ // no fix available; return true to indicate successful parse. |
+ return true; |
+ } |
+ DCHECK(location_value); |
+ |
+ if (!location_value->IsType(base::Value::TYPE_DICTIONARY)) { |
+ if (!location_value->IsType(base::Value::TYPE_NULL)) { |
+ VLOG(1) << "ParseServerResponse() : Unexpected location type " |
+ << location_value->GetType(); |
+ // If the network provider was unable to provide a position fix, it should |
+ // return a HTTP 200, with "location" : null. Otherwise it's an error. |
+ return false; |
+ } |
+ return true; // Successfully parsed response containing no fix. |
+ } |
+ const base::DictionaryValue* location_object = |
+ static_cast<const base::DictionaryValue*>(location_value); |
+ |
+ // latitude and longitude fields are always required. |
+ double latitude = 0; |
+ double longitude = 0; |
+ if (!GetAsDouble(*location_object, kLatitudeString, &latitude) || |
+ !GetAsDouble(*location_object, kLongitudeString, &longitude)) { |
+ VLOG(1) << "ParseServerResponse() : location lacks lat and/or long."; |
+ return false; |
+ } |
+ // All error paths covered: now start actually modifying postion. |
+ position->latitude = latitude; |
+ position->longitude = longitude; |
+ position->timestamp = wifi_timestamp; |
+ |
+ // Other fields are optional. |
+ GetAsDouble(*response_object, kAccuracyString, &position->accuracy); |
+ |
+ return true; |
+} |
+ |
+} // namespace |
+ |
+} // namespace content |