diff options
Diffstat (limited to 'chrome/browser/geolocation/network_location_request.cc')
-rw-r--r-- | chrome/browser/geolocation/network_location_request.cc | 423 |
1 files changed, 423 insertions, 0 deletions
diff --git a/chrome/browser/geolocation/network_location_request.cc b/chrome/browser/geolocation/network_location_request.cc new file mode 100644 index 0000000..7eae212 --- /dev/null +++ b/chrome/browser/geolocation/network_location_request.cc @@ -0,0 +1,423 @@ +// Copyright (c) 2010 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 "chrome/browser/geolocation/network_location_request.h" + +#include "base/json/json_reader.h" +#include "base/json/json_writer.h" +#include "base/string_util.h" +#include "base/values.h" +#include "chrome/common/geoposition.h" +#include "chrome/common/net/url_request_context_getter.h" +#include "net/base/load_flags.h" +#include "net/url_request/url_request_status.h" + +namespace { +const char kMimeApplicationJson[] = "application/json"; + +// See http://code.google.com/apis/gears/geolocation_network_protocol.html +const char kGeoLocationNetworkProtocolVersion[] = "1.1.0"; + +const wchar_t kAccessTokenString[] = L"access_token"; +const wchar_t kLocationString[] = L"location"; +const wchar_t kLatitudeString[] = L"latitude"; +const wchar_t kLongitudeString[] = L"longitude"; +const wchar_t kAltitudeString[] = L"altitude"; +const wchar_t kAccuracyString[] = L"accuracy"; +const wchar_t kAltitudeAccuracyString[] = L"altitude_accuracy"; + +// Local functions +// Creates the request payload to send to the server. +void FormRequestBody(const std::string& host_name, + const string16& access_token, + const RadioData& radio_data, + const WifiData& wifi_data, + const base::Time& timestamp, + std::string* data); +// Parsers the server response. +void GetLocationFromResponse(bool http_post_result, + int status_code, + const std::string& response_body, + const base::Time& timestamp, + const GURL& server_url, + Geoposition* position, + string16* access_token); + +const char* RadioTypeToString(RadioType type); +// Adds a string if it's valid to the JSON object. +void AddString(const std::wstring& property_name, + const string16& value, + DictionaryValue* object); +// Adds an integer if it's valid to the JSON object. +void AddInteger(const std::wstring& property_name, + int value, + DictionaryValue* object); +// 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 (i.e. IsInitialized() == false). +bool ParseServerResponse(const std::string& response_body, + const base::Time& timestamp, + Geoposition* position, + string16* access_token); +void AddRadioData(const RadioData& radio_data, + int age_milliseconds, + DictionaryValue* body_object); +void AddWifiData(const WifiData& wifi_data, + int age_milliseconds, + DictionaryValue* body_object); +} // namespace + +int NetworkLocationRequest::url_fetcher_id_for_tests = 0; + +NetworkLocationRequest::NetworkLocationRequest(URLRequestContextGetter* context, + const GURL& url, + ListenerInterface* listener) + : url_context_(context), listener_(listener), + url_(url) { + DCHECK(listener); +} + +NetworkLocationRequest::~NetworkLocationRequest() { +} + +bool NetworkLocationRequest::MakeRequest(const std::string& host_name, + const string16& access_token, + const RadioData& radio_data, + const WifiData& wifi_data, + const base::Time& timestamp) { + if (url_fetcher_ != NULL) { + DLOG(INFO) << "NetworkLocationRequest : Cancelling pending request"; + url_fetcher_.reset(); + } + radio_data_ = radio_data; + wifi_data_ = wifi_data; + timestamp_ = timestamp; + std::string post_body; + FormRequestBody(host_name, access_token, radio_data_, wifi_data_, + timestamp_, &post_body); + + url_fetcher_.reset(URLFetcher::Create( + url_fetcher_id_for_tests, url_, URLFetcher::POST, this)); + url_fetcher_->set_upload_data(kMimeApplicationJson, post_body); + url_fetcher_->set_request_context(url_context_); + url_fetcher_->set_load_flags( + 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); + url_fetcher_->Start(); + return true; +} + +void NetworkLocationRequest::OnURLFetchComplete(const URLFetcher* source, + const GURL& url, + const URLRequestStatus& status, + int response_code, + const ResponseCookies& cookies, + const std::string& data) { + DCHECK_EQ(url_fetcher_.get(), source); + DCHECK(url_.possibly_invalid_spec() == url.possibly_invalid_spec()); + + Geoposition position; + string16 access_token; + GetLocationFromResponse(status.is_success(), response_code, data, + timestamp_, url, &position, &access_token); + const bool server_error = + !status.is_success() || (response_code >= 500 && response_code < 600); + url_fetcher_.reset(); + + DCHECK(listener_); + DLOG(INFO) << "NetworkLocationRequest::Run() : " + "Calling listener with position.\n"; + listener_->LocationResponseAvailable(position, server_error, access_token, + radio_data_, wifi_data_); +} + +// Local functions. +namespace { + +void FormRequestBody(const std::string& host_name, + const string16& access_token, + const RadioData& radio_data, + const WifiData& wifi_data, + const base::Time& timestamp, + std::string* data) { + DCHECK(data); + + DictionaryValue body_object; + // Version and host are required. + COMPILE_ASSERT(sizeof(kGeoLocationNetworkProtocolVersion) > 1, + must_include_valid_version); + DCHECK(!host_name.empty()); + body_object.SetString(L"version", kGeoLocationNetworkProtocolVersion); + body_object.SetString(L"host", host_name); + + AddString(L"access_token", access_token, &body_object); + + body_object.SetBoolean(L"request_address", false); + + int age = kint32min; // Invalid so AddInteger() will ignore. + if (!timestamp.is_null()) { + // Convert absolute timestamps into a relative age. + int64 delta_ms = (base::Time::Now() - timestamp).InMilliseconds(); + if (delta_ms >= 0 && delta_ms < kint32max) + age = static_cast<int>(delta_ms); + } + AddRadioData(radio_data, age, &body_object); + AddWifiData(wifi_data, age, &body_object); + + base::JSONWriter::Write(&body_object, false, data); + DLOG(INFO) << "NetworkLocationRequest::FormRequestBody(): Formed body " + << *data << ".\n"; +} + +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.possibly_invalid_spec(); + position->error_message += "' : "; + position->error_message += message; + position->error_message += "."; + LOG(INFO) << "NetworkLocationRequest::GetLocationFromResponse() : " + << position->error_message; +} + +void GetLocationFromResponse(bool http_post_result, + int status_code, + const std::string& response_body, + const base::Time& timestamp, + const GURL& server_url, + Geoposition* position, + 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); + return; + } + if (status_code != 200) { // HTTP OK. + std::string message = "Returned error code "; + message += IntToString(status_code); + FormatPositionError(server_url, message, position); + return; + } + // We use the timestamp from the device data that was used to generate + // this position fix. + if (!ParseServerResponse(response_body, timestamp, position, access_token)) { + // We failed to parse the repsonse. + FormatPositionError(server_url, "Response was malformed", position); + return; + } + // The response was successfully parsed, but it may not be a valid + // position fix. + if (!position->IsValidFix()) { + FormatPositionError(server_url, + "Did not provide a good position fix", position); + return; + } +} + +const char* RadioTypeToString(RadioType type) { + switch (type) { + case RADIO_TYPE_UNKNOWN: + break; + case RADIO_TYPE_GSM: + return "gsm"; + case RADIO_TYPE_CDMA: + return "cdma"; + case RADIO_TYPE_WCDMA: + return "wcdma"; + default: + LOG(DFATAL) << "Bad RadioType"; + } + return "unknown"; +} + +void AddString(const std::wstring& property_name, + const string16& value, + DictionaryValue* object) { + DCHECK(object); + if (!value.empty()) { + object->SetStringFromUTF16(property_name, value); + } +} + +void AddInteger(const std::wstring& property_name, + int value, + DictionaryValue* object) { + DCHECK(object); + if (kint32min != value) { + object->SetInteger(property_name, value); + } +} + +// 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 DictionaryValue& object, + const std::wstring& property_name, + double* out) { + DCHECK(out); + 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->GetAsReal(out); +} + +bool ParseServerResponse(const std::string& response_body, + const base::Time& timestamp, + Geoposition* position, + string16* access_token) { + DCHECK(position); + DCHECK(!position->IsInitialized()); + DCHECK(access_token); + DCHECK(!timestamp.is_null()); + + if (response_body.empty()) { + LOG(WARNING) << "ParseServerResponse() : Response was empty.\n"; + return false; + } + DLOG(INFO) << "ParseServerResponse() : Parsing response " + << response_body << ".\n"; + + // Parse the response, ignoring comments. + std::string error_msg; + scoped_ptr<Value> response_value(base::JSONReader::ReadAndReturnError( + response_body, false, NULL, &error_msg)); + if (response_value == NULL) { + LOG(WARNING) << "ParseServerResponse() : JSONReader failed : " + << error_msg << ".\n"; + return false; + } + + if (!response_value->IsType(Value::TYPE_DICTIONARY)) { + LOG(INFO) << "ParseServerResponse() : Unexpected resopnse type " + << response_value->GetType() << ".\n"; + return false; + } + const DictionaryValue* response_object = + static_cast<DictionaryValue*>(response_value.get()); + + // Get the access token, if any. + response_object->GetStringAsUTF16(kAccessTokenString, access_token); + + // Get the location + Value* location_value = NULL; + if (!response_object->Get(kLocationString, &location_value)) { + LOG(INFO) << "ParseServerResponse() : Missing location attribute.\n"; + // 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(Value::TYPE_DICTIONARY)) { + if (!location_value->IsType(Value::TYPE_NULL)) { + LOG(INFO) << "ParseServerResponse() : Unexpected location type" + << location_value->GetType() << ".\n"; + // 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. + } + DictionaryValue* location_object = + static_cast<DictionaryValue*>(location_value); + + // latitude and longitude fields are always required. + double latitude, longitude; + if (!GetAsDouble(*location_object, kLatitudeString, &latitude) || + !GetAsDouble(*location_object, kLongitudeString, &longitude)) { + LOG(INFO) << "ParseServerResponse() : location lacks lat and/or long.\n"; + return false; + } + // All error paths covered: now start actually modifying postion. + position->latitude = latitude; + position->longitude = longitude; + position->timestamp = timestamp; + + // Other fields are optional. + GetAsDouble(*location_object, kAccuracyString, &position->accuracy); + GetAsDouble(*location_object, kAltitudeString, &position->altitude); + GetAsDouble(*location_object, kAltitudeAccuracyString, + &position->altitude_accuracy); + + return true; +} + +void AddRadioData(const RadioData& radio_data, + int age_milliseconds, + DictionaryValue* body_object) { + DCHECK(body_object); + + AddInteger(L"home_mobile_country_code", radio_data.home_mobile_country_code, + body_object); + AddInteger(L"home_mobile_network_code", radio_data.home_mobile_network_code, + body_object); + AddString(L"radio_type", + ASCIIToUTF16(RadioTypeToString(radio_data.radio_type)), + body_object); + AddString(L"carrier", radio_data.carrier, body_object); + + const int num_cell_towers = static_cast<int>(radio_data.cell_data.size()); + if (num_cell_towers == 0) { + return; + } + ListValue* cell_towers = new ListValue; + for (int i = 0; i < num_cell_towers; ++i) { + DictionaryValue* cell_tower = new DictionaryValue; + AddInteger(L"cell_id", radio_data.cell_data[i].cell_id, cell_tower); + AddInteger(L"location_area_code", + radio_data.cell_data[i].location_area_code, cell_tower); + AddInteger(L"mobile_country_code", + radio_data.cell_data[i].mobile_country_code, cell_tower); + AddInteger(L"mobile_network_code", + radio_data.cell_data[i].mobile_network_code, cell_tower); + AddInteger(L"age", age_milliseconds, cell_tower); + AddInteger(L"signal_strength", + radio_data.cell_data[i].radio_signal_strength, cell_tower); + AddInteger(L"timing_advance", radio_data.cell_data[i].timing_advance, + cell_tower); + cell_towers->Append(cell_tower); + } + body_object->Set(L"cell_towers", cell_towers); +} + +void AddWifiData(const WifiData& wifi_data, + int age_milliseconds, + DictionaryValue* body_object) { + DCHECK(body_object); + + if (wifi_data.access_point_data.empty()) { + return; + } + + ListValue* wifi_towers = new ListValue; + for (WifiData::AccessPointDataSet::const_iterator iter = + wifi_data.access_point_data.begin(); + iter != wifi_data.access_point_data.end(); + iter++) { + DictionaryValue* wifi_tower = new DictionaryValue; + AddString(L"mac_address", iter->mac_address, wifi_tower); + AddInteger(L"signal_strength", iter->radio_signal_strength, wifi_tower); + AddInteger(L"age", age_milliseconds, wifi_tower); + AddInteger(L"channel", iter->channel, wifi_tower); + AddInteger(L"signal_to_noise", iter->signal_to_noise, wifi_tower); + AddString(L"ssid", iter->ssid, wifi_tower); + wifi_towers->Append(wifi_tower); + } + body_object->Set(L"wifi_towers", wifi_towers); +} +} // namespace |