普通文本  |  343行  |  11.24 KB

// 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 "base/bind.h"
#include "base/sha1.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.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 kDefaultCheckinInterval = 2 * 24 * 60 * 60;  // seconds = 2 days.
const int64 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 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>::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
const GURL GServicesSettings::DefaultCheckinURL() {
  return GURL(kDefaultCheckinURL);
}

// 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 = StringToLowerASCII(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 && name.find(kDeleteSettingPrefix) == 0) {
      std::string setting_to_delete =
          name.substr(arraysize(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 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 {
  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 {
  // 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 defualt.
  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 {
  // 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 {
  SettingsMap::const_iterator iter = settings_.find(kRegistrationURLKey);
  if (iter == settings_.end() || iter->second.empty())
    return GURL(kDefaultRegistrationURL);
  return GURL(iter->second);
}

}  // namespace gcm