Home | History | Annotate | Download | only in geolocation
      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", base::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