• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2014 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 "google_apis/gcm/engine/gservices_settings.h"
6 
7 #include "base/bind.h"
8 #include "base/sha1.h"
9 #include "base/strings/string_number_conversions.h"
10 #include "base/strings/string_util.h"
11 #include "base/strings/stringprintf.h"
12 
13 namespace {
14 // The expected time in seconds between periodic checkins.
15 const char kCheckinIntervalKey[] = "checkin_interval";
16 // The override URL to the checkin server.
17 const char kCheckinURLKey[] = "checkin_url";
18 // The MCS machine name to connect to.
19 const char kMCSHostnameKey[] = "gcm_hostname";
20 // The MCS port to connect to.
21 const char kMCSSecurePortKey[] = "gcm_secure_port";
22 // The URL to get MCS registration IDs.
23 const char kRegistrationURLKey[] = "gcm_registration_url";
24 
25 const int64 kDefaultCheckinInterval = 2 * 24 * 60 * 60;  // seconds = 2 days.
26 const int64 kMinimumCheckinInterval = 12 * 60 * 60;      // seconds = 12 hours.
27 const char kDefaultCheckinURL[] = "https://android.clients.google.com/checkin";
28 const char kDefaultMCSHostname[] = "mtalk.google.com";
29 const int kDefaultMCSMainSecurePort = 5228;
30 const int kDefaultMCSFallbackSecurePort = 443;
31 const char kDefaultRegistrationURL[] =
32     "https://android.clients.google.com/c2dm/register3";
33 // Settings that are to be deleted are marked with this prefix in checkin
34 // response.
35 const char kDeleteSettingPrefix[] = "delete_";
36 // Settings digest starts with verison number followed by '-'.
37 const char kDigestVersionPrefix[] = "1-";
38 const char kMCSEnpointTemplate[] = "https://%s:%d";
39 const int kMaxSecurePort = 65535;
40 
MakeMCSEndpoint(const std::string & mcs_hostname,int port)41 std::string MakeMCSEndpoint(const std::string& mcs_hostname, int port) {
42   return base::StringPrintf(kMCSEnpointTemplate, mcs_hostname.c_str(), port);
43 }
44 
45 // Default settings can be omitted, as GServicesSettings class provides
46 // reasonable defaults.
CanBeOmitted(const std::string & settings_name)47 bool CanBeOmitted(const std::string& settings_name) {
48   return settings_name == kCheckinIntervalKey ||
49          settings_name == kCheckinURLKey ||
50          settings_name == kMCSHostnameKey ||
51          settings_name == kMCSSecurePortKey ||
52          settings_name == kRegistrationURLKey;
53 }
54 
VerifyCheckinInterval(const gcm::GServicesSettings::SettingsMap & settings)55 bool VerifyCheckinInterval(
56     const gcm::GServicesSettings::SettingsMap& settings) {
57   gcm::GServicesSettings::SettingsMap::const_iterator iter =
58       settings.find(kCheckinIntervalKey);
59   if (iter == settings.end())
60     return CanBeOmitted(kCheckinIntervalKey);
61 
62   int64 checkin_interval = kMinimumCheckinInterval;
63   if (!base::StringToInt64(iter->second, &checkin_interval)) {
64     DVLOG(1) << "Failed to parse checkin interval: " << iter->second;
65     return false;
66   }
67   if (checkin_interval == std::numeric_limits<int64>::max()) {
68     DVLOG(1) << "Checkin interval is too big: " << checkin_interval;
69     return false;
70   }
71   if (checkin_interval < kMinimumCheckinInterval) {
72     DVLOG(1) << "Checkin interval: " << checkin_interval
73              << " is less than allowed minimum: " << kMinimumCheckinInterval;
74   }
75 
76   return true;
77 }
78 
VerifyMCSEndpoint(const gcm::GServicesSettings::SettingsMap & settings)79 bool VerifyMCSEndpoint(const gcm::GServicesSettings::SettingsMap& settings) {
80   std::string mcs_hostname;
81   gcm::GServicesSettings::SettingsMap::const_iterator iter =
82       settings.find(kMCSHostnameKey);
83   if (iter == settings.end()) {
84     // Because endpoint has 2 parts (hostname and port) we are defaulting and
85     // moving on with verification.
86     if (CanBeOmitted(kMCSHostnameKey))
87       mcs_hostname = kDefaultMCSHostname;
88     else
89       return false;
90   } else if (iter->second.empty()) {
91     DVLOG(1) << "Empty MCS hostname provided.";
92     return false;
93   } else {
94     mcs_hostname = iter->second;
95   }
96 
97   int mcs_secure_port = 0;
98   iter = settings.find(kMCSSecurePortKey);
99   if (iter == settings.end()) {
100     // Simlarly we might have to default the port, when only hostname is
101     // provided.
102     if (CanBeOmitted(kMCSSecurePortKey))
103       mcs_secure_port = kDefaultMCSMainSecurePort;
104     else
105       return false;
106   } else if (!base::StringToInt(iter->second, &mcs_secure_port)) {
107     DVLOG(1) << "Failed to parse MCS secure port: " << iter->second;
108     return false;
109   }
110 
111   if (mcs_secure_port < 0 || mcs_secure_port > kMaxSecurePort) {
112     DVLOG(1) << "Incorrect port value: " << mcs_secure_port;
113     return false;
114   }
115 
116   GURL mcs_main_endpoint(MakeMCSEndpoint(mcs_hostname, mcs_secure_port));
117   if (!mcs_main_endpoint.is_valid()) {
118     DVLOG(1) << "Invalid main MCS endpoint: "
119              << mcs_main_endpoint.possibly_invalid_spec();
120     return false;
121   }
122   GURL mcs_fallback_endpoint(
123       MakeMCSEndpoint(mcs_hostname, kDefaultMCSFallbackSecurePort));
124   if (!mcs_fallback_endpoint.is_valid()) {
125     DVLOG(1) << "Invalid fallback MCS endpoint: "
126              << mcs_fallback_endpoint.possibly_invalid_spec();
127     return false;
128   }
129 
130   return true;
131 }
132 
VerifyCheckinURL(const gcm::GServicesSettings::SettingsMap & settings)133 bool VerifyCheckinURL(const gcm::GServicesSettings::SettingsMap& settings) {
134   gcm::GServicesSettings::SettingsMap::const_iterator iter =
135       settings.find(kCheckinURLKey);
136   if (iter == settings.end())
137     return CanBeOmitted(kCheckinURLKey);
138 
139   GURL checkin_url(iter->second);
140   if (!checkin_url.is_valid()) {
141     DVLOG(1) << "Invalid checkin URL provided: " << iter->second;
142     return false;
143   }
144 
145   return true;
146 }
147 
VerifyRegistrationURL(const gcm::GServicesSettings::SettingsMap & settings)148 bool VerifyRegistrationURL(
149     const gcm::GServicesSettings::SettingsMap& settings) {
150   gcm::GServicesSettings::SettingsMap::const_iterator iter =
151       settings.find(kRegistrationURLKey);
152   if (iter == settings.end())
153     return CanBeOmitted(kRegistrationURLKey);
154 
155   GURL registration_url(iter->second);
156   if (!registration_url.is_valid()) {
157     DVLOG(1) << "Invalid registration URL provided: " << iter->second;
158     return false;
159   }
160 
161   return true;
162 }
163 
VerifySettings(const gcm::GServicesSettings::SettingsMap & settings)164 bool VerifySettings(const gcm::GServicesSettings::SettingsMap& settings) {
165   return VerifyCheckinInterval(settings) && VerifyMCSEndpoint(settings) &&
166          VerifyCheckinURL(settings) && VerifyRegistrationURL(settings);
167 }
168 
169 }  // namespace
170 
171 namespace gcm {
172 
173 // static
MinimumCheckinInterval()174 const base::TimeDelta GServicesSettings::MinimumCheckinInterval() {
175   return base::TimeDelta::FromSeconds(kMinimumCheckinInterval);
176 }
177 
178 // static
DefaultCheckinURL()179 const GURL GServicesSettings::DefaultCheckinURL() {
180   return GURL(kDefaultCheckinURL);
181 }
182 
183 // static
CalculateDigest(const SettingsMap & settings)184 std::string GServicesSettings::CalculateDigest(const SettingsMap& settings) {
185   unsigned char hash[base::kSHA1Length];
186   std::string data;
187   for (SettingsMap::const_iterator iter = settings.begin();
188        iter != settings.end();
189        ++iter) {
190     data += iter->first;
191     data += '\0';
192     data += iter->second;
193     data += '\0';
194   }
195   base::SHA1HashBytes(
196       reinterpret_cast<const unsigned char*>(&data[0]), data.size(), hash);
197   std::string digest =
198       kDigestVersionPrefix + base::HexEncode(hash, base::kSHA1Length);
199   digest = base::StringToLowerASCII(digest);
200   return digest;
201 }
202 
GServicesSettings()203 GServicesSettings::GServicesSettings() : weak_ptr_factory_(this) {
204   digest_ = CalculateDigest(settings_);
205 }
206 
~GServicesSettings()207 GServicesSettings::~GServicesSettings() {
208 }
209 
UpdateFromCheckinResponse(const checkin_proto::AndroidCheckinResponse & checkin_response)210 bool GServicesSettings::UpdateFromCheckinResponse(
211     const checkin_proto::AndroidCheckinResponse& checkin_response) {
212   if (!checkin_response.has_settings_diff()) {
213     DVLOG(1) << "Field settings_diff not set in response.";
214     return false;
215   }
216 
217   bool settings_diff = checkin_response.settings_diff();
218   SettingsMap new_settings;
219   // Only reuse the existing settings, if we are given a settings difference.
220   if (settings_diff)
221     new_settings = settings_map();
222 
223   for (int i = 0; i < checkin_response.setting_size(); ++i) {
224     std::string name = checkin_response.setting(i).name();
225     if (name.empty()) {
226       DVLOG(1) << "Setting name is empty";
227       return false;
228     }
229 
230     if (settings_diff && name.find(kDeleteSettingPrefix) == 0) {
231       std::string setting_to_delete =
232           name.substr(arraysize(kDeleteSettingPrefix) - 1);
233       new_settings.erase(setting_to_delete);
234       DVLOG(1) << "Setting deleted: " << setting_to_delete;
235     } else {
236       std::string value = checkin_response.setting(i).value();
237       new_settings[name] = value;
238       DVLOG(1) << "New setting: '" << name << "' : '" << value << "'";
239     }
240   }
241 
242   if (!VerifySettings(new_settings))
243     return false;
244 
245   settings_.swap(new_settings);
246   digest_ = CalculateDigest(settings_);
247   return true;
248 }
249 
UpdateFromLoadResult(const GCMStore::LoadResult & load_result)250 void GServicesSettings::UpdateFromLoadResult(
251     const GCMStore::LoadResult& load_result) {
252   // No need to try to update settings when load_result is empty.
253   if (load_result.gservices_settings.empty())
254     return;
255   if (!VerifySettings(load_result.gservices_settings))
256     return;
257   std::string digest = CalculateDigest(load_result.gservices_settings);
258   if (digest != load_result.gservices_digest) {
259     DVLOG(1) << "G-services settings digest mismatch. "
260              << "Expected digest: " << load_result.gservices_digest
261              << ". Calculated digest is: " << digest;
262     return;
263   }
264 
265   settings_ = load_result.gservices_settings;
266   digest_ = load_result.gservices_digest;
267 }
268 
GetCheckinInterval() const269 base::TimeDelta GServicesSettings::GetCheckinInterval() const {
270   int64 checkin_interval = kMinimumCheckinInterval;
271   SettingsMap::const_iterator iter = settings_.find(kCheckinIntervalKey);
272   if (iter == settings_.end() ||
273       !base::StringToInt64(iter->second, &checkin_interval)) {
274     checkin_interval = kDefaultCheckinInterval;
275   }
276 
277   if (checkin_interval < kMinimumCheckinInterval)
278     checkin_interval = kMinimumCheckinInterval;
279 
280   return base::TimeDelta::FromSeconds(checkin_interval);
281 }
282 
GetCheckinURL() const283 GURL GServicesSettings::GetCheckinURL() const {
284   SettingsMap::const_iterator iter = settings_.find(kCheckinURLKey);
285   if (iter == settings_.end() || iter->second.empty())
286     return GURL(kDefaultCheckinURL);
287   return GURL(iter->second);
288 }
289 
GetMCSMainEndpoint() const290 GURL GServicesSettings::GetMCSMainEndpoint() const {
291   // Get alternative hostname or use default.
292   std::string mcs_hostname;
293   SettingsMap::const_iterator iter = settings_.find(kMCSHostnameKey);
294   if (iter != settings_.end() && !iter->second.empty())
295     mcs_hostname = iter->second;
296   else
297     mcs_hostname = kDefaultMCSHostname;
298 
299   // Get alternative secure port or use defualt.
300   int mcs_secure_port = 0;
301   iter = settings_.find(kMCSSecurePortKey);
302   if (iter == settings_.end() || iter->second.empty() ||
303       !base::StringToInt(iter->second, &mcs_secure_port)) {
304     mcs_secure_port = kDefaultMCSMainSecurePort;
305   }
306 
307   // If constructed address makes sense use it.
308   GURL mcs_endpoint(MakeMCSEndpoint(mcs_hostname, mcs_secure_port));
309   if (mcs_endpoint.is_valid())
310     return mcs_endpoint;
311 
312   // Otherwise use default settings.
313   return GURL(MakeMCSEndpoint(kDefaultMCSHostname, kDefaultMCSMainSecurePort));
314 }
315 
GetMCSFallbackEndpoint() const316 GURL GServicesSettings::GetMCSFallbackEndpoint() const {
317   // Get alternative hostname or use default.
318   std::string mcs_hostname;
319   SettingsMap::const_iterator iter = settings_.find(kMCSHostnameKey);
320   if (iter != settings_.end() && !iter->second.empty())
321     mcs_hostname = iter->second;
322   else
323     mcs_hostname = kDefaultMCSHostname;
324 
325   // If constructed address makes sense use it.
326   GURL mcs_endpoint(
327       MakeMCSEndpoint(mcs_hostname, kDefaultMCSFallbackSecurePort));
328   if (mcs_endpoint.is_valid())
329     return mcs_endpoint;
330 
331   return GURL(
332       MakeMCSEndpoint(kDefaultMCSHostname, kDefaultMCSFallbackSecurePort));
333 }
334 
GetRegistrationURL() const335 GURL GServicesSettings::GetRegistrationURL() const {
336   SettingsMap::const_iterator iter = settings_.find(kRegistrationURLKey);
337   if (iter == settings_.end() || iter->second.empty())
338     return GURL(kDefaultRegistrationURL);
339   return GURL(iter->second);
340 }
341 
342 }  // namespace gcm
343