Add support for Omaha cohorts to the component updater.
BUG=638633,639569
Review-Url: https://codereview.chromium.org/2252093002
Cr-Commit-Position: refs/heads/master@{#413809}
diff --git a/components/update_client/persisted_data.cc b/components/update_client/persisted_data.cc
index 673ba1e6..4f2c34a 100644
--- a/components/update_client/persisted_data.cc
+++ b/components/update_client/persisted_data.cc
@@ -28,33 +28,62 @@
DCHECK(thread_checker_.CalledOnValidThread());
}
-int PersistedData::GetDateLastRollCall(const std::string& id) const {
+int PersistedData::GetInt(const std::string& id,
+ const std::string& key,
+ int fallback) const {
DCHECK(thread_checker_.CalledOnValidThread());
- if (!pref_service_)
- return kDateLastRollCallUnknown;
- int dlrc = kDateLastRollCallUnknown;
- const base::DictionaryValue* dict =
- pref_service_->GetDictionary(kPersistedDataPreference);
// We assume ids do not contain '.' characters.
DCHECK_EQ(std::string::npos, id.find('.'));
- if (!dict->GetInteger(base::StringPrintf("apps.%s.dlrc", id.c_str()), &dlrc))
- return kDateLastRollCallUnknown;
- return dlrc;
+ if (!pref_service_)
+ return fallback;
+ const base::DictionaryValue* dict =
+ pref_service_->GetDictionary(kPersistedDataPreference);
+ if (!dict)
+ return fallback;
+ int result = 0;
+ return dict->GetInteger(
+ base::StringPrintf("apps.%s.%s", id.c_str(), key.c_str()), &result)
+ ? result
+ : fallback;
+}
+
+std::string PersistedData::GetString(const std::string& id,
+ const std::string& key) const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ // We assume ids do not contain '.' characters.
+ DCHECK_EQ(std::string::npos, id.find('.'));
+ if (!pref_service_)
+ return std::string();
+ const base::DictionaryValue* dict =
+ pref_service_->GetDictionary(kPersistedDataPreference);
+ if (!dict)
+ return std::string();
+ std::string result;
+ return dict->GetString(
+ base::StringPrintf("apps.%s.%s", id.c_str(), key.c_str()), &result)
+ ? result
+ : std::string();
+}
+
+int PersistedData::GetDateLastRollCall(const std::string& id) const {
+ return GetInt(id, "dlrc", kDateLastRollCallUnknown);
}
std::string PersistedData::GetPingFreshness(const std::string& id) const {
- DCHECK(thread_checker_.CalledOnValidThread());
- if (!pref_service_)
- return std::string();
- std::string freshness;
- const base::DictionaryValue* dict =
- pref_service_->GetDictionary(kPersistedDataPreference);
- // We assume ids do not contain '.' characters.
- DCHECK_EQ(std::string::npos, id.find('.'));
- if (!dict->GetString(base::StringPrintf("apps.%s.pf", id.c_str()),
- &freshness))
- return std::string();
- return base::StringPrintf("{%s}", freshness.c_str());
+ std::string result = GetString(id, "pf");
+ return !result.empty() ? base::StringPrintf("{%s}", result.c_str()) : result;
+}
+
+std::string PersistedData::GetCohort(const std::string& id) const {
+ return GetString(id, "cohort");
+}
+
+std::string PersistedData::GetCohortName(const std::string& id) const {
+ return GetString(id, "cohortname");
+}
+
+std::string PersistedData::GetCohortHint(const std::string& id) const {
+ return GetString(id, "cohorthint");
}
void PersistedData::SetDateLastRollCall(const std::vector<std::string>& ids,
@@ -72,6 +101,32 @@
}
}
+void PersistedData::SetString(const std::string& id,
+ const std::string& key,
+ const std::string& value) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (!pref_service_)
+ return;
+ DictionaryPrefUpdate update(pref_service_, kPersistedDataPreference);
+ update->SetString(base::StringPrintf("apps.%s.%s", id.c_str(), key.c_str()),
+ value);
+}
+
+void PersistedData::SetCohort(const std::string& id,
+ const std::string& cohort) {
+ SetString(id, "cohort", cohort);
+}
+
+void PersistedData::SetCohortName(const std::string& id,
+ const std::string& cohort_name) {
+ SetString(id, "cohortname", cohort_name);
+}
+
+void PersistedData::SetCohortHint(const std::string& id,
+ const std::string& cohort_hint) {
+ SetString(id, "cohorthint", cohort_hint);
+}
+
void PersistedData::RegisterPrefs(PrefRegistrySimple* registry) {
registry->RegisterDictionaryPref(kPersistedDataPreference);
}
diff --git a/components/update_client/persisted_data.h b/components/update_client/persisted_data.h
index 6b24e57..06d4d0d2 100644
--- a/components/update_client/persisted_data.h
+++ b/components/update_client/persisted_data.h
@@ -52,7 +52,27 @@
// This is called only via update_client's RegisterUpdateClientPreferences.
static void RegisterPrefs(PrefRegistrySimple* registry);
+ // These functions return cohort data for the specified |id|. "Cohort"
+ // indicates the membership of the client in any release channels components
+ // have set up in a machine-readable format, while "CohortName" does so in a
+ // human-readable form. "CohortHint" indicates the client's channel selection
+ // preference.
+ std::string GetCohort(const std::string& id) const;
+ std::string GetCohortHint(const std::string& id) const;
+ std::string GetCohortName(const std::string& id) const;
+
+ // These functions set cohort data for the specified |id|.
+ void SetCohort(const std::string& id, const std::string& cohort);
+ void SetCohortHint(const std::string& id, const std::string& cohort_hint);
+ void SetCohortName(const std::string& id, const std::string& cohort_name);
+
private:
+ int GetInt(const std::string& id, const std::string& key, int fallback) const;
+ std::string GetString(const std::string& id, const std::string& key) const;
+ void SetString(const std::string& id,
+ const std::string& key,
+ const std::string& value);
+
base::ThreadChecker thread_checker_;
PrefService* pref_service_;
diff --git a/components/update_client/persisted_data_unittest.cc b/components/update_client/persisted_data_unittest.cc
index dfe242c2..cc5f875 100644
--- a/components/update_client/persisted_data_unittest.cc
+++ b/components/update_client/persisted_data_unittest.cc
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include <memory>
#include <string>
#include <vector>
@@ -50,4 +51,41 @@
EXPECT_EQ(-2, metadata->GetDateLastRollCall("someotherappid"));
}
+TEST(PersistedDataTest, SimpleCohort) {
+ std::unique_ptr<TestingPrefServiceSimple> pref(
+ new TestingPrefServiceSimple());
+ PersistedData::RegisterPrefs(pref->registry());
+ std::unique_ptr<PersistedData> metadata(new PersistedData(pref.get()));
+ EXPECT_EQ("", metadata->GetCohort("someappid"));
+ EXPECT_EQ("", metadata->GetCohort("someotherappid"));
+ EXPECT_EQ("", metadata->GetCohortHint("someappid"));
+ EXPECT_EQ("", metadata->GetCohortHint("someotherappid"));
+ EXPECT_EQ("", metadata->GetCohortName("someappid"));
+ EXPECT_EQ("", metadata->GetCohortName("someotherappid"));
+ metadata->SetCohort("someappid", "c1");
+ metadata->SetCohort("someotherappid", "c2");
+ metadata->SetCohortHint("someappid", "ch1");
+ metadata->SetCohortHint("someotherappid", "ch2");
+ metadata->SetCohortName("someappid", "cn1");
+ metadata->SetCohortName("someotherappid", "cn2");
+ EXPECT_EQ("c1", metadata->GetCohort("someappid"));
+ EXPECT_EQ("c2", metadata->GetCohort("someotherappid"));
+ EXPECT_EQ("ch1", metadata->GetCohortHint("someappid"));
+ EXPECT_EQ("ch2", metadata->GetCohortHint("someotherappid"));
+ EXPECT_EQ("cn1", metadata->GetCohortName("someappid"));
+ EXPECT_EQ("cn2", metadata->GetCohortName("someotherappid"));
+ metadata->SetCohort("someappid", "oc1");
+ metadata->SetCohort("someotherappid", "oc2");
+ metadata->SetCohortHint("someappid", "och1");
+ metadata->SetCohortHint("someotherappid", "och2");
+ metadata->SetCohortName("someappid", "ocn1");
+ metadata->SetCohortName("someotherappid", "ocn2");
+ EXPECT_EQ("oc1", metadata->GetCohort("someappid"));
+ EXPECT_EQ("oc2", metadata->GetCohort("someotherappid"));
+ EXPECT_EQ("och1", metadata->GetCohortHint("someappid"));
+ EXPECT_EQ("och2", metadata->GetCohortHint("someotherappid"));
+ EXPECT_EQ("ocn1", metadata->GetCohortName("someappid"));
+ EXPECT_EQ("ocn2", metadata->GetCohortName("someotherappid"));
+}
+
} // namespace update_client
diff --git a/components/update_client/update_checker.cc b/components/update_client/update_checker.cc
index a5ec266..c1cf4d7 100644
--- a/components/update_client/update_checker.cc
+++ b/components/update_client/update_checker.cc
@@ -91,6 +91,15 @@
base::StringAppendF(&app, " %s=\"%s\"", attr.first.c_str(),
attr.second.c_str());
}
+ const std::string cohort = metadata->GetCohort(item->id);
+ const std::string cohort_name = metadata->GetCohortName(item->id);
+ const std::string cohort_hint = metadata->GetCohortHint(item->id);
+ if (!cohort.empty())
+ base::StringAppendF(&app, " cohort=\"%s\"", cohort.c_str());
+ if (!cohort_name.empty())
+ base::StringAppendF(&app, " cohortname=\"%s\"", cohort_name.c_str());
+ if (!cohort_hint.empty())
+ base::StringAppendF(&app, " cohorthint=\"%s\"", cohort_hint.c_str());
base::StringAppendF(&app, ">");
base::StringAppendF(&app, "<updatecheck");
@@ -203,6 +212,17 @@
int daynum = update_response.results().daystart_elapsed_days;
if (daynum != UpdateResponse::kNoDaystart)
metadata_->SetDateLastRollCall(*ids_checked, daynum);
+ for (const auto& result : update_response.results().list) {
+ auto entry = result.cohort_attrs.find(UpdateResponse::Result::kCohort);
+ if (entry != result.cohort_attrs.end())
+ metadata_->SetCohort(result.extension_id, entry->second);
+ entry = result.cohort_attrs.find(UpdateResponse::Result::kCohortName);
+ if (entry != result.cohort_attrs.end())
+ metadata_->SetCohortName(result.extension_id, entry->second);
+ entry = result.cohort_attrs.find(UpdateResponse::Result::kCohortHint);
+ if (entry != result.cohort_attrs.end())
+ metadata_->SetCohortHint(result.extension_id, entry->second);
+ }
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::Bind(update_check_callback_, error,
update_response.results(), retry_after_sec));
diff --git a/components/update_client/update_response.cc b/components/update_client/update_response.cc
index 453f6a2..41670d3 100644
--- a/components/update_client/update_response.cc
+++ b/components/update_client/update_response.cc
@@ -9,6 +9,7 @@
#include <algorithm>
#include <memory>
+#include "base/memory/ptr_util.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
@@ -20,6 +21,9 @@
namespace update_client {
static const char* kExpectedResponseProtocol = "3.0";
+const char UpdateResponse::Result::kCohort[] = "cohort";
+const char UpdateResponse::Result::kCohortHint[] = "cohorthint";
+const char UpdateResponse::Result::kCohortName[] = "cohortname";
UpdateResponse::UpdateResponse() {
}
@@ -32,8 +36,7 @@
UpdateResponse::Results::~Results() {
}
-UpdateResponse::Result::Result() {
-}
+UpdateResponse::Result::Result() {}
UpdateResponse::Result::Result(const Result& other) = default;
UpdateResponse::Result::~Result() {
}
@@ -93,6 +96,21 @@
return std::string();
}
+// Returns the value of a named attribute, or nullptr .
+static std::unique_ptr<std::string> GetAttributePtr(
+ xmlNode* node,
+ const char* attribute_name) {
+ const xmlChar* name = reinterpret_cast<const xmlChar*>(attribute_name);
+ for (xmlAttr* attr = node->properties; attr != NULL; attr = attr->next) {
+ if (!xmlStrcmp(attr->name, name) && attr->children &&
+ attr->children->content) {
+ return base::MakeUnique<std::string>(
+ reinterpret_cast<const char*>(attr->children->content));
+ }
+ }
+ return nullptr;
+}
+
// This is used for the xml parser to report errors. This assumes the context
// is a pointer to a std::string where the error message should be appended.
static void XmlErrorFunc(void* context, const char* message, ...) {
@@ -268,6 +286,17 @@
bool ParseAppTag(xmlNode* app,
UpdateResponse::Result* result,
std::string* error) {
+ // Read cohort information.
+ auto cohort = GetAttributePtr(app, "cohort");
+ static const char* attrs[] = {UpdateResponse::Result::kCohort,
+ UpdateResponse::Result::kCohortHint,
+ UpdateResponse::Result::kCohortName};
+ for (const auto& attr : attrs) {
+ auto value = GetAttributePtr(app, attr);
+ if (value)
+ result->cohort_attrs.insert({attr, *value});
+ }
+
// Read the crx id.
result->extension_id = GetAttribute(app, "appid");
if (result->extension_id.empty()) {
diff --git a/components/update_client/update_response.h b/components/update_client/update_response.h
index 6c0ea9bd..01b49aaa 100644
--- a/components/update_client/update_response.h
+++ b/components/update_client/update_response.h
@@ -5,6 +5,8 @@
#ifndef COMPONENTS_UPDATE_CLIENT_UPDATE_RESPONSE_H_
#define COMPONENTS_UPDATE_CLIENT_UPDATE_RESPONSE_H_
+#include <map>
+#include <memory>
#include <string>
#include <vector>
@@ -99,6 +101,15 @@
std::vector<GURL> crx_diffurls;
Manifest manifest;
+
+ // The server has instructed the client to set its [key] to [value] for each
+ // key-value pair in this string.
+ std::map<std::string, std::string> cohort_attrs;
+
+ // The following are the only allowed keys in |cohort_attrs|.
+ static const char kCohort[];
+ static const char kCohortHint[];
+ static const char kCohortName[];
};
static const int kNoDaystart = -1;
diff --git a/components/update_client/update_response_unittest.cc b/components/update_client/update_response_unittest.cc
index 20f08b07..b4160240 100644
--- a/components/update_client/update_response_unittest.cc
+++ b/components/update_client/update_response_unittest.cc
@@ -220,6 +220,27 @@
" </app>"
"</response>";
+// Includes two <app> tags, both of which set the cohort.
+const char* kTwoAppsSetCohort =
+ "<?xml version='1.0' encoding='UTF-8'?>"
+ "<response protocol='3.0'>"
+ " <app appid='aaaaaaaa' cohort='1:2q3/'>"
+ " <updatecheck status='noupdate'/>"
+ " </app>"
+ " <app appid='bbbbbbbb' cohort='1:[email protected]' cohortname='cname'>"
+ " <updatecheck status='ok'>"
+ " <urls>"
+ " <url codebase='http://example.com/'/>"
+ " </urls>"
+ " <manifest version='1.2.3.4' prodversionmin='2.0.143.0'>"
+ " <packages>"
+ " <package name='extension_1_2_3_4.crx'/>"
+ " </packages>"
+ " </manifest>"
+ " </updatecheck>"
+ " </app>"
+ "</response>";
+
TEST(ComponentUpdaterUpdateResponseTest, TestParser) {
UpdateResponse parser;
@@ -312,6 +333,30 @@
EXPECT_EQ(1u, parser.results().list.size());
firstResult = &parser.results().list[0];
EXPECT_EQ(firstResult->extension_id, "bbbbbbbb");
+
+ // Parse xml with two apps setting the cohort info.
+ EXPECT_TRUE(parser.Parse(kTwoAppsSetCohort));
+ EXPECT_TRUE(parser.errors().empty());
+ EXPECT_EQ(2u, parser.results().list.size());
+ firstResult = &parser.results().list[0];
+ EXPECT_EQ(firstResult->extension_id, "aaaaaaaa");
+ EXPECT_NE(firstResult->cohort_attrs.find("cohort"),
+ firstResult->cohort_attrs.end());
+ EXPECT_EQ(firstResult->cohort_attrs.find("cohort")->second, "1:2q3/");
+ EXPECT_EQ(firstResult->cohort_attrs.find("cohortname"),
+ firstResult->cohort_attrs.end());
+ EXPECT_EQ(firstResult->cohort_attrs.find("cohorthint"),
+ firstResult->cohort_attrs.end());
+ const UpdateResponse::Result* secondResult = &parser.results().list[1];
+ EXPECT_EQ(secondResult->extension_id, "bbbbbbbb");
+ EXPECT_NE(secondResult->cohort_attrs.find("cohort"),
+ secondResult->cohort_attrs.end());
+ EXPECT_EQ(secondResult->cohort_attrs.find("cohort")->second, "1:[email protected]");
+ EXPECT_NE(secondResult->cohort_attrs.find("cohortname"),
+ secondResult->cohort_attrs.end());
+ EXPECT_EQ(secondResult->cohort_attrs.find("cohortname")->second, "cname");
+ EXPECT_EQ(secondResult->cohort_attrs.find("cohorthint"),
+ secondResult->cohort_attrs.end());
}
} // namespace update_client