| // Copyright 2014 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 "google_apis/gcm/engine/gservices_settings.h" |
| |
| #include <stdint.h> |
| |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/sha1.h" |
| #include "base/stl_util.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "google_apis/gcm/engine/gservices_switches.h" |
| |
| namespace { |
| // The expected time in seconds between periodic checkins. |
| const char kCheckinIntervalKey[] = "checkin_interval"; |
| // The override URL to the checkin server. |
| const char kCheckinURLKey[] = "checkin_url"; |
| // The MCS machine name to connect to. |
| const char kMCSHostnameKey[] = "gcm_hostname"; |
| // The MCS port to connect to. |
| const char kMCSSecurePortKey[] = "gcm_secure_port"; |
| // The URL to get MCS registration IDs. |
| const char kRegistrationURLKey[] = "gcm_registration_url"; |
| |
| const int64_t kDefaultCheckinInterval = 2 * 24 * 60 * 60; // seconds = 2 days. |
| const int64_t kMinimumCheckinInterval = 12 * 60 * 60; // seconds = 12 hours. |
| const char kDefaultCheckinURL[] = "https://android.clients.google.com/checkin"; |
| const char kDefaultMCSHostname[] = "mtalk.google.com"; |
| const int kDefaultMCSMainSecurePort = 5228; |
| const int kDefaultMCSFallbackSecurePort = 443; |
| const char kDefaultRegistrationURL[] = |
| "https://android.clients.google.com/c2dm/register3"; |
| // Settings that are to be deleted are marked with this prefix in checkin |
| // response. |
| const char kDeleteSettingPrefix[] = "delete_"; |
| // Settings digest starts with verison number followed by '-'. |
| const char kDigestVersionPrefix[] = "1-"; |
| const char kMCSEnpointTemplate[] = "https://%s:%d"; |
| const int kMaxSecurePort = 65535; |
| |
| std::string MakeMCSEndpoint(const std::string& mcs_hostname, int port) { |
| return base::StringPrintf(kMCSEnpointTemplate, mcs_hostname.c_str(), port); |
| } |
| |
| // Default settings can be omitted, as GServicesSettings class provides |
| // reasonable defaults. |
| bool CanBeOmitted(const std::string& settings_name) { |
| return settings_name == kCheckinIntervalKey || |
| settings_name == kCheckinURLKey || |
| settings_name == kMCSHostnameKey || |
| settings_name == kMCSSecurePortKey || |
| settings_name == kRegistrationURLKey; |
| } |
| |
| bool VerifyCheckinInterval( |
| const gcm::GServicesSettings::SettingsMap& settings) { |
| gcm::GServicesSettings::SettingsMap::const_iterator iter = |
| settings.find(kCheckinIntervalKey); |
| if (iter == settings.end()) |
| return CanBeOmitted(kCheckinIntervalKey); |
| |
| int64_t checkin_interval = kMinimumCheckinInterval; |
| if (!base::StringToInt64(iter->second, &checkin_interval)) { |
| DVLOG(1) << "Failed to parse checkin interval: " << iter->second; |
| return false; |
| } |
| if (checkin_interval == std::numeric_limits<int64_t>::max()) { |
| DVLOG(1) << "Checkin interval is too big: " << checkin_interval; |
| return false; |
| } |
| if (checkin_interval < kMinimumCheckinInterval) { |
| DVLOG(1) << "Checkin interval: " << checkin_interval |
| << " is less than allowed minimum: " << kMinimumCheckinInterval; |
| } |
| |
| return true; |
| } |
| |
| bool VerifyMCSEndpoint(const gcm::GServicesSettings::SettingsMap& settings) { |
| std::string mcs_hostname; |
| gcm::GServicesSettings::SettingsMap::const_iterator iter = |
| settings.find(kMCSHostnameKey); |
| if (iter == settings.end()) { |
| // Because endpoint has 2 parts (hostname and port) we are defaulting and |
| // moving on with verification. |
| if (CanBeOmitted(kMCSHostnameKey)) |
| mcs_hostname = kDefaultMCSHostname; |
| else |
| return false; |
| } else if (iter->second.empty()) { |
| DVLOG(1) << "Empty MCS hostname provided."; |
| return false; |
| } else { |
| mcs_hostname = iter->second; |
| } |
| |
| int mcs_secure_port = 0; |
| iter = settings.find(kMCSSecurePortKey); |
| if (iter == settings.end()) { |
| // Simlarly we might have to default the port, when only hostname is |
| // provided. |
| if (CanBeOmitted(kMCSSecurePortKey)) |
| mcs_secure_port = kDefaultMCSMainSecurePort; |
| else |
| return false; |
| } else if (!base::StringToInt(iter->second, &mcs_secure_port)) { |
| DVLOG(1) << "Failed to parse MCS secure port: " << iter->second; |
| return false; |
| } |
| |
| if (mcs_secure_port < 0 || mcs_secure_port > kMaxSecurePort) { |
| DVLOG(1) << "Incorrect port value: " << mcs_secure_port; |
| return false; |
| } |
| |
| GURL mcs_main_endpoint(MakeMCSEndpoint(mcs_hostname, mcs_secure_port)); |
| if (!mcs_main_endpoint.is_valid()) { |
| DVLOG(1) << "Invalid main MCS endpoint: " |
| << mcs_main_endpoint.possibly_invalid_spec(); |
| return false; |
| } |
| GURL mcs_fallback_endpoint( |
| MakeMCSEndpoint(mcs_hostname, kDefaultMCSFallbackSecurePort)); |
| if (!mcs_fallback_endpoint.is_valid()) { |
| DVLOG(1) << "Invalid fallback MCS endpoint: " |
| << mcs_fallback_endpoint.possibly_invalid_spec(); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool VerifyCheckinURL(const gcm::GServicesSettings::SettingsMap& settings) { |
| gcm::GServicesSettings::SettingsMap::const_iterator iter = |
| settings.find(kCheckinURLKey); |
| if (iter == settings.end()) |
| return CanBeOmitted(kCheckinURLKey); |
| |
| GURL checkin_url(iter->second); |
| if (!checkin_url.is_valid()) { |
| DVLOG(1) << "Invalid checkin URL provided: " << iter->second; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool VerifyRegistrationURL( |
| const gcm::GServicesSettings::SettingsMap& settings) { |
| gcm::GServicesSettings::SettingsMap::const_iterator iter = |
| settings.find(kRegistrationURLKey); |
| if (iter == settings.end()) |
| return CanBeOmitted(kRegistrationURLKey); |
| |
| GURL registration_url(iter->second); |
| if (!registration_url.is_valid()) { |
| DVLOG(1) << "Invalid registration URL provided: " << iter->second; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool VerifySettings(const gcm::GServicesSettings::SettingsMap& settings) { |
| return VerifyCheckinInterval(settings) && VerifyMCSEndpoint(settings) && |
| VerifyCheckinURL(settings) && VerifyRegistrationURL(settings); |
| } |
| |
| } // namespace |
| |
| namespace gcm { |
| |
| // static |
| const base::TimeDelta GServicesSettings::MinimumCheckinInterval() { |
| return base::TimeDelta::FromSeconds(kMinimumCheckinInterval); |
| } |
| |
| // static |
| std::string GServicesSettings::CalculateDigest(const SettingsMap& settings) { |
| unsigned char hash[base::kSHA1Length]; |
| std::string data; |
| for (SettingsMap::const_iterator iter = settings.begin(); |
| iter != settings.end(); |
| ++iter) { |
| data += iter->first; |
| data += '\0'; |
| data += iter->second; |
| data += '\0'; |
| } |
| base::SHA1HashBytes( |
| reinterpret_cast<const unsigned char*>(&data[0]), data.size(), hash); |
| std::string digest = |
| kDigestVersionPrefix + base::HexEncode(hash, base::kSHA1Length); |
| digest = base::ToLowerASCII(digest); |
| return digest; |
| } |
| |
| GServicesSettings::GServicesSettings() : weak_ptr_factory_(this) { |
| digest_ = CalculateDigest(settings_); |
| } |
| |
| GServicesSettings::~GServicesSettings() { |
| } |
| |
| bool GServicesSettings::UpdateFromCheckinResponse( |
| const checkin_proto::AndroidCheckinResponse& checkin_response) { |
| if (!checkin_response.has_settings_diff()) { |
| DVLOG(1) << "Field settings_diff not set in response."; |
| return false; |
| } |
| |
| bool settings_diff = checkin_response.settings_diff(); |
| SettingsMap new_settings; |
| // Only reuse the existing settings, if we are given a settings difference. |
| if (settings_diff) |
| new_settings = settings_map(); |
| |
| for (int i = 0; i < checkin_response.setting_size(); ++i) { |
| std::string name = checkin_response.setting(i).name(); |
| if (name.empty()) { |
| DVLOG(1) << "Setting name is empty"; |
| return false; |
| } |
| |
| if (settings_diff && base::StartsWith(name, kDeleteSettingPrefix, |
| base::CompareCase::SENSITIVE)) { |
| std::string setting_to_delete = |
| name.substr(base::size(kDeleteSettingPrefix) - 1); |
| new_settings.erase(setting_to_delete); |
| DVLOG(1) << "Setting deleted: " << setting_to_delete; |
| } else { |
| std::string value = checkin_response.setting(i).value(); |
| new_settings[name] = value; |
| DVLOG(1) << "New setting: '" << name << "' : '" << value << "'"; |
| } |
| } |
| |
| if (!VerifySettings(new_settings)) |
| return false; |
| |
| settings_.swap(new_settings); |
| digest_ = CalculateDigest(settings_); |
| return true; |
| } |
| |
| void GServicesSettings::UpdateFromLoadResult( |
| const GCMStore::LoadResult& load_result) { |
| // No need to try to update settings when load_result is empty. |
| if (load_result.gservices_settings.empty()) |
| return; |
| if (!VerifySettings(load_result.gservices_settings)) |
| return; |
| std::string digest = CalculateDigest(load_result.gservices_settings); |
| if (digest != load_result.gservices_digest) { |
| DVLOG(1) << "G-services settings digest mismatch. " |
| << "Expected digest: " << load_result.gservices_digest |
| << ". Calculated digest is: " << digest; |
| return; |
| } |
| |
| settings_ = load_result.gservices_settings; |
| digest_ = load_result.gservices_digest; |
| } |
| |
| base::TimeDelta GServicesSettings::GetCheckinInterval() const { |
| int64_t checkin_interval = kMinimumCheckinInterval; |
| SettingsMap::const_iterator iter = settings_.find(kCheckinIntervalKey); |
| if (iter == settings_.end() || |
| !base::StringToInt64(iter->second, &checkin_interval)) { |
| checkin_interval = kDefaultCheckinInterval; |
| } |
| |
| if (checkin_interval < kMinimumCheckinInterval) |
| checkin_interval = kMinimumCheckinInterval; |
| |
| return base::TimeDelta::FromSeconds(checkin_interval); |
| } |
| |
| GURL GServicesSettings::GetCheckinURL() const { |
| base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); |
| if (command_line->HasSwitch(switches::kGCMCheckinURL)) |
| return GURL(command_line->GetSwitchValueASCII(switches::kGCMCheckinURL)); |
| |
| SettingsMap::const_iterator iter = settings_.find(kCheckinURLKey); |
| if (iter == settings_.end() || iter->second.empty()) |
| return GURL(kDefaultCheckinURL); |
| return GURL(iter->second); |
| } |
| |
| GURL GServicesSettings::GetMCSMainEndpoint() const { |
| base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); |
| if (command_line->HasSwitch(switches::kGCMMCSEndpoint)) |
| return GURL(command_line->GetSwitchValueASCII(switches::kGCMMCSEndpoint)); |
| |
| // Get alternative hostname or use default. |
| std::string mcs_hostname; |
| SettingsMap::const_iterator iter = settings_.find(kMCSHostnameKey); |
| if (iter != settings_.end() && !iter->second.empty()) |
| mcs_hostname = iter->second; |
| else |
| mcs_hostname = kDefaultMCSHostname; |
| |
| // Get alternative secure port or use default. |
| int mcs_secure_port = 0; |
| iter = settings_.find(kMCSSecurePortKey); |
| if (iter == settings_.end() || iter->second.empty() || |
| !base::StringToInt(iter->second, &mcs_secure_port)) { |
| mcs_secure_port = kDefaultMCSMainSecurePort; |
| } |
| |
| // If constructed address makes sense use it. |
| GURL mcs_endpoint(MakeMCSEndpoint(mcs_hostname, mcs_secure_port)); |
| if (mcs_endpoint.is_valid()) |
| return mcs_endpoint; |
| |
| // Otherwise use default settings. |
| return GURL(MakeMCSEndpoint(kDefaultMCSHostname, kDefaultMCSMainSecurePort)); |
| } |
| |
| GURL GServicesSettings::GetMCSFallbackEndpoint() const { |
| base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); |
| if (command_line->HasSwitch(switches::kGCMMCSEndpoint)) |
| return GURL(); // No fallback endpoint when using command line override. |
| |
| // Get alternative hostname or use default. |
| std::string mcs_hostname; |
| SettingsMap::const_iterator iter = settings_.find(kMCSHostnameKey); |
| if (iter != settings_.end() && !iter->second.empty()) |
| mcs_hostname = iter->second; |
| else |
| mcs_hostname = kDefaultMCSHostname; |
| |
| // If constructed address makes sense use it. |
| GURL mcs_endpoint( |
| MakeMCSEndpoint(mcs_hostname, kDefaultMCSFallbackSecurePort)); |
| if (mcs_endpoint.is_valid()) |
| return mcs_endpoint; |
| |
| return GURL( |
| MakeMCSEndpoint(kDefaultMCSHostname, kDefaultMCSFallbackSecurePort)); |
| } |
| |
| GURL GServicesSettings::GetRegistrationURL() const { |
| base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); |
| if (command_line->HasSwitch(switches::kGCMRegistrationURL)) { |
| return GURL( |
| command_line->GetSwitchValueASCII(switches::kGCMRegistrationURL)); |
| } |
| |
| SettingsMap::const_iterator iter = settings_.find(kRegistrationURLKey); |
| if (iter == settings_.end() || iter->second.empty()) |
| return GURL(kDefaultRegistrationURL); |
| return GURL(iter->second); |
| } |
| |
| } // namespace gcm |