network_location_request.cc 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  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. #include "services/device/geolocation/network_location_request.h"
  5. #include <stdint.h>
  6. #include <limits>
  7. #include <set>
  8. #include <string>
  9. #include <utility>
  10. #include "base/bind.h"
  11. #include "base/json/json_reader.h"
  12. #include "base/json/json_writer.h"
  13. #include "base/memory/ptr_util.h"
  14. #include "base/metrics/histogram.h"
  15. #include "base/metrics/histogram_functions.h"
  16. #include "base/metrics/histogram_macros.h"
  17. #include "base/strings/escape.h"
  18. #include "base/strings/string_number_conversions.h"
  19. #include "base/strings/utf_string_conversions.h"
  20. #include "base/values.h"
  21. #include "net/base/load_flags.h"
  22. #include "net/base/net_errors.h"
  23. #include "net/traffic_annotation/network_traffic_annotation.h"
  24. #include "services/device/geolocation/location_arbitrator.h"
  25. #include "services/device/public/cpp/geolocation/geoposition.h"
  26. #include "services/network/public/cpp/resource_request.h"
  27. #include "services/network/public/cpp/shared_url_loader_factory.h"
  28. #include "services/network/public/cpp/simple_url_loader.h"
  29. #include "services/network/public/mojom/url_response_head.mojom.h"
  30. namespace device {
  31. namespace {
  32. const char kNetworkLocationBaseUrl[] =
  33. "https://www.googleapis.com/geolocation/v1/geolocate";
  34. const char kLocationString[] = "location";
  35. const char kLatitudeString[] = "lat";
  36. const char kLongitudeString[] = "lng";
  37. const char kAccuracyString[] = "accuracy";
  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. // NOTE: Add entries only immediately above this line.
  50. NETWORK_LOCATION_REQUEST_EVENT_COUNT = 7
  51. };
  52. void RecordUmaEvent(NetworkLocationRequestEvent event) {
  53. UMA_HISTOGRAM_ENUMERATION("Geolocation.NetworkLocationRequest.Event", event,
  54. NETWORK_LOCATION_REQUEST_EVENT_COUNT);
  55. }
  56. void RecordUmaResponseCode(int code) {
  57. base::UmaHistogramSparse("Geolocation.NetworkLocationRequest.ResponseCode",
  58. code);
  59. }
  60. void RecordUmaNetError(int net_error) {
  61. base::UmaHistogramSparse("Geolocation.NetworkLocationRequest.NetError",
  62. -net_error);
  63. }
  64. void RecordUmaAccessPoints(int count) {
  65. const int min = 1;
  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. // Local functions
  72. // Returns a URL for a request to the Google Maps geolocation API. If the
  73. // specified |api_key| is not empty, it is escaped and included as a query
  74. // string parameter.
  75. GURL FormRequestURL(const std::string& api_key);
  76. void FormUploadData(const WifiData& wifi_data,
  77. const base::Time& wifi_timestamp,
  78. std::string* upload_data);
  79. // Attempts to extract a position from the response. Detects and indicates
  80. // various failure cases.
  81. void GetLocationFromResponse(int net_error,
  82. int status_code,
  83. std::unique_ptr<std::string> response_body,
  84. const base::Time& wifi_timestamp,
  85. const GURL& server_url,
  86. mojom::Geoposition* position);
  87. // Parses the server response body. Returns true if parsing was successful.
  88. // Sets |*position| to the parsed location if a valid fix was received,
  89. // otherwise leaves it unchanged.
  90. bool ParseServerResponse(const std::string& response_body,
  91. const base::Time& wifi_timestamp,
  92. mojom::Geoposition* position);
  93. void AddWifiData(const WifiData& wifi_data,
  94. int age_milliseconds,
  95. base::DictionaryValue* request);
  96. } // namespace
  97. NetworkLocationRequest::NetworkLocationRequest(
  98. scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
  99. const std::string& api_key,
  100. LocationResponseCallback callback)
  101. : url_loader_factory_(std::move(url_loader_factory)),
  102. api_key_(api_key),
  103. location_response_callback_(std::move(callback)) {}
  104. NetworkLocationRequest::~NetworkLocationRequest() = default;
  105. bool NetworkLocationRequest::MakeRequest(
  106. const WifiData& wifi_data,
  107. const base::Time& wifi_timestamp,
  108. const net::PartialNetworkTrafficAnnotationTag& partial_traffic_annotation) {
  109. RecordUmaEvent(NETWORK_LOCATION_REQUEST_EVENT_REQUEST_START);
  110. RecordUmaAccessPoints(wifi_data.access_point_data.size());
  111. if (url_loader_) {
  112. DVLOG(1) << "NetworkLocationRequest : Cancelling pending request";
  113. RecordUmaEvent(NETWORK_LOCATION_REQUEST_EVENT_REQUEST_CANCEL);
  114. url_loader_.reset();
  115. }
  116. wifi_data_ = wifi_data;
  117. wifi_timestamp_ = wifi_timestamp;
  118. net::NetworkTrafficAnnotationTag traffic_annotation =
  119. net::CompleteNetworkTrafficAnnotation("network_location_request",
  120. partial_traffic_annotation,
  121. R"(
  122. semantics {
  123. description:
  124. "Obtains geo position based on current IP address and local "
  125. "network information including Wi-Fi access points (even if you’re "
  126. "not using them)."
  127. trigger:
  128. "Location requests are sent when the page requests them or new "
  129. "IP address is available."
  130. data: "Wi-Fi data, IP address."
  131. destination: GOOGLE_OWNED_SERVICE
  132. }
  133. policy {
  134. cookies_allowed: NO
  135. })");
  136. auto resource_request = std::make_unique<network::ResourceRequest>();
  137. resource_request->method = "POST";
  138. resource_request->url = FormRequestURL(api_key_);
  139. DCHECK(resource_request->url.is_valid());
  140. resource_request->load_flags =
  141. net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE;
  142. resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit;
  143. url_loader_ = network::SimpleURLLoader::Create(std::move(resource_request),
  144. traffic_annotation);
  145. url_loader_->SetAllowHttpErrorResults(true);
  146. std::string upload_data;
  147. FormUploadData(wifi_data, wifi_timestamp, &upload_data);
  148. url_loader_->AttachStringForUpload(upload_data, "application/json");
  149. url_loader_->DownloadToString(
  150. url_loader_factory_.get(),
  151. base::BindOnce(&NetworkLocationRequest::OnRequestComplete,
  152. base::Unretained(this)),
  153. 1024 * 1024 /* 1 MiB */);
  154. return true;
  155. }
  156. void NetworkLocationRequest::OnRequestComplete(
  157. std::unique_ptr<std::string> data) {
  158. int net_error = url_loader_->NetError();
  159. int response_code = 0;
  160. if (url_loader_->ResponseInfo())
  161. response_code = url_loader_->ResponseInfo()->headers->response_code();
  162. RecordUmaResponseCode(response_code);
  163. mojom::Geoposition position;
  164. GetLocationFromResponse(net_error, response_code, std::move(data),
  165. wifi_timestamp_, url_loader_->GetFinalURL(),
  166. &position);
  167. bool server_error =
  168. net_error != net::OK || (response_code >= 500 && response_code < 600);
  169. url_loader_.reset();
  170. DVLOG(1) << "NetworkLocationRequest::OnURLFetchComplete() : run callback.";
  171. location_response_callback_.Run(position, server_error, wifi_data_);
  172. }
  173. // Local functions.
  174. namespace {
  175. struct AccessPointLess {
  176. bool operator()(const AccessPointData* ap1,
  177. const AccessPointData* ap2) const {
  178. return ap2->radio_signal_strength < ap1->radio_signal_strength;
  179. }
  180. };
  181. GURL FormRequestURL(const std::string& api_key) {
  182. GURL url(kNetworkLocationBaseUrl);
  183. if (!api_key.empty()) {
  184. std::string query(url.query());
  185. if (!query.empty())
  186. query += "&";
  187. query += "key=" + base::EscapeQueryParamValue(api_key, true);
  188. GURL::Replacements replacements;
  189. replacements.SetQueryStr(query);
  190. return url.ReplaceComponents(replacements);
  191. }
  192. return url;
  193. }
  194. void FormUploadData(const WifiData& wifi_data,
  195. const base::Time& wifi_timestamp,
  196. std::string* upload_data) {
  197. int age = std::numeric_limits<int32_t>::min(); // Invalid so AddInteger()
  198. // will ignore.
  199. if (!wifi_timestamp.is_null()) {
  200. // Convert absolute timestamps into a relative age.
  201. int64_t delta_ms = (base::Time::Now() - wifi_timestamp).InMilliseconds();
  202. if (delta_ms >= 0 && delta_ms < std::numeric_limits<int32_t>::max())
  203. age = static_cast<int>(delta_ms);
  204. }
  205. base::DictionaryValue request;
  206. AddWifiData(wifi_data, age, &request);
  207. base::JSONWriter::Write(request, upload_data);
  208. }
  209. void AddString(const std::string& property_name,
  210. const std::string& value,
  211. base::Value::Dict& dict) {
  212. if (!value.empty())
  213. dict.Set(property_name, value);
  214. }
  215. void AddInteger(const std::string& property_name,
  216. int value,
  217. base::Value::Dict& dict) {
  218. if (value != std::numeric_limits<int32_t>::min())
  219. dict.Set(property_name, value);
  220. }
  221. void AddWifiData(const WifiData& wifi_data,
  222. int age_milliseconds,
  223. base::DictionaryValue* request) {
  224. DCHECK(request);
  225. if (wifi_data.access_point_data.empty())
  226. return;
  227. typedef std::multiset<const AccessPointData*, AccessPointLess> AccessPointSet;
  228. AccessPointSet access_points_by_signal_strength;
  229. for (const auto& ap_data : wifi_data.access_point_data)
  230. access_points_by_signal_strength.insert(&ap_data);
  231. base::Value::List wifi_access_point_list;
  232. for (auto* ap_data : access_points_by_signal_strength) {
  233. base::Value::Dict wifi_dict;
  234. auto macAddress = base::UTF16ToUTF8(ap_data->mac_address);
  235. if (macAddress.empty())
  236. continue;
  237. AddString("macAddress", macAddress, wifi_dict);
  238. AddInteger("signalStrength", ap_data->radio_signal_strength, wifi_dict);
  239. AddInteger("age", age_milliseconds, wifi_dict);
  240. AddInteger("channel", ap_data->channel, wifi_dict);
  241. AddInteger("signalToNoiseRatio", ap_data->signal_to_noise, wifi_dict);
  242. wifi_access_point_list.Append(std::move(wifi_dict));
  243. }
  244. if (!wifi_access_point_list.empty())
  245. request->GetDict().Set("wifiAccessPoints",
  246. base::Value(std::move(wifi_access_point_list)));
  247. }
  248. void FormatPositionError(const GURL& server_url,
  249. const std::string& error_message,
  250. const std::string& error_technical,
  251. mojom::Geoposition* position) {
  252. position->error_code = mojom::Geoposition::ErrorCode::POSITION_UNAVAILABLE;
  253. position->error_message = error_message;
  254. VLOG(1) << "NetworkLocationRequest::GetLocationFromResponse() : "
  255. << position->error_message;
  256. if (!error_technical.empty()) {
  257. position->error_technical = "Network location provider at '";
  258. position->error_technical += server_url.DeprecatedGetOriginAsURL().spec();
  259. position->error_technical += "' : ";
  260. position->error_technical += error_technical;
  261. position->error_technical += ".";
  262. VLOG(1) << "NetworkLocationRequest::GetLocationFromResponse() : "
  263. << position->error_technical;
  264. }
  265. }
  266. void GetLocationFromResponse(int net_error,
  267. int status_code,
  268. std::unique_ptr<std::string> response_body,
  269. const base::Time& wifi_timestamp,
  270. const GURL& server_url,
  271. mojom::Geoposition* position) {
  272. DCHECK(position);
  273. // HttpPost can fail for a number of reasons. Most likely this is because
  274. // we're offline, or there was no response.
  275. if (net_error != net::OK) {
  276. FormatPositionError(server_url,
  277. "Network error. Check "
  278. "DevTools console for more information.",
  279. net::ErrorToShortString(net_error), position);
  280. RecordUmaEvent(NETWORK_LOCATION_REQUEST_EVENT_RESPONSE_EMPTY);
  281. RecordUmaNetError(net_error);
  282. return;
  283. }
  284. if (status_code != 200) { // HTTP OK.
  285. std::string message = "Returned error code ";
  286. message += base::NumberToString(status_code);
  287. FormatPositionError(server_url,
  288. "Failed to query location from network service. Check "
  289. "the DevTools console for more information.",
  290. message, position);
  291. RecordUmaEvent(NETWORK_LOCATION_REQUEST_EVENT_RESPONSE_NOT_OK);
  292. return;
  293. }
  294. // We use the timestamp from the wifi data that was used to generate
  295. // this position fix.
  296. DCHECK(response_body);
  297. if (!ParseServerResponse(*response_body, wifi_timestamp, position)) {
  298. // We failed to parse the response.
  299. FormatPositionError(server_url, "Response was malformed", "", position);
  300. RecordUmaEvent(NETWORK_LOCATION_REQUEST_EVENT_RESPONSE_MALFORMED);
  301. return;
  302. }
  303. // The response was successfully parsed, but it may not be a valid
  304. // position fix.
  305. if (!ValidateGeoposition(*position)) {
  306. FormatPositionError(server_url, "Did not provide a good position fix", "",
  307. position);
  308. RecordUmaEvent(NETWORK_LOCATION_REQUEST_EVENT_RESPONSE_INVALID_FIX);
  309. return;
  310. }
  311. RecordUmaEvent(NETWORK_LOCATION_REQUEST_EVENT_RESPONSE_SUCCESS);
  312. }
  313. // Numeric values without a decimal point have type integer and IsDouble() will
  314. // return false. This is convenience function for detecting integer or floating
  315. // point numeric values. Note that isIntegral() includes boolean values, which
  316. // is not what we want.
  317. bool GetAsDouble(const base::DictionaryValue& object,
  318. const std::string& property_name,
  319. double* out) {
  320. DCHECK(out);
  321. const base::Value* value = NULL;
  322. if (!object.Get(property_name, &value))
  323. return false;
  324. DCHECK(value);
  325. if (value->is_int()) {
  326. *out = value->GetInt();
  327. return true;
  328. }
  329. if (value->is_double()) {
  330. *out = value->GetDouble();
  331. return true;
  332. }
  333. return false;
  334. }
  335. bool ParseServerResponse(const std::string& response_body,
  336. const base::Time& wifi_timestamp,
  337. mojom::Geoposition* position) {
  338. DCHECK(position);
  339. DCHECK(!ValidateGeoposition(*position));
  340. DCHECK(position->error_code == mojom::Geoposition::ErrorCode::NONE);
  341. DCHECK(!wifi_timestamp.is_null());
  342. if (response_body.empty()) {
  343. LOG(WARNING) << "ParseServerResponse() : Response was empty.";
  344. return false;
  345. }
  346. DVLOG(1) << "ParseServerResponse() : Parsing response " << response_body;
  347. // Parse the response, ignoring comments.
  348. auto response_result =
  349. base::JSONReader::ReadAndReturnValueWithError(response_body);
  350. if (!response_result.has_value()) {
  351. LOG(WARNING) << "ParseServerResponse() : JSONReader failed : "
  352. << response_result.error().message;
  353. return false;
  354. }
  355. base::Value response_value = std::move(*response_result);
  356. if (!response_value.is_dict()) {
  357. VLOG(1) << "ParseServerResponse() : Unexpected response type "
  358. << response_value.type();
  359. return false;
  360. }
  361. const base::DictionaryValue* response_object =
  362. static_cast<base::DictionaryValue*>(&response_value);
  363. // Get the location
  364. const base::Value* location_value = NULL;
  365. if (!response_object->Get(kLocationString, &location_value)) {
  366. VLOG(1) << "ParseServerResponse() : Missing location attribute.";
  367. // GLS returns a response with no location property to represent
  368. // no fix available; return true to indicate successful parse.
  369. return true;
  370. }
  371. DCHECK(location_value);
  372. if (!location_value->is_dict()) {
  373. if (!location_value->is_none()) {
  374. VLOG(1) << "ParseServerResponse() : Unexpected location type "
  375. << location_value->type();
  376. // If the network provider was unable to provide a position fix, it should
  377. // return a HTTP 200, with "location" : null. Otherwise it's an error.
  378. return false;
  379. }
  380. return true; // Successfully parsed response containing no fix.
  381. }
  382. const base::DictionaryValue* location_object =
  383. static_cast<const base::DictionaryValue*>(location_value);
  384. // latitude and longitude fields are always required.
  385. double latitude = 0;
  386. double longitude = 0;
  387. if (!GetAsDouble(*location_object, kLatitudeString, &latitude) ||
  388. !GetAsDouble(*location_object, kLongitudeString, &longitude)) {
  389. VLOG(1) << "ParseServerResponse() : location lacks lat and/or long.";
  390. return false;
  391. }
  392. // All error paths covered: now start actually modifying postion.
  393. position->latitude = latitude;
  394. position->longitude = longitude;
  395. position->timestamp = wifi_timestamp;
  396. // Other fields are optional.
  397. GetAsDouble(*response_object, kAccuracyString, &position->accuracy);
  398. return true;
  399. }
  400. } // namespace
  401. } // namespace device