summaryrefslogtreecommitdiffstats
path: root/chrome/browser/geolocation/network_location_request.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/geolocation/network_location_request.cc')
-rw-r--r--chrome/browser/geolocation/network_location_request.cc423
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