1 //
2 // Copyright (C) 2019 The Android Open Source Project
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 // http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 //
16
17 #include "update_engine/cros/omaha_request_builder_xml.h"
18
19 #include <string>
20 #include <utility>
21 #include <vector>
22
23 #include <base/guid.h>
24 #include <base/strings/stringprintf.h>
25 #include <gtest/gtest.h>
26
27 #include "update_engine/cros/fake_system_state.h"
28
29 using std::pair;
30 using std::string;
31 using std::vector;
32 using testing::_;
33 using testing::DoAll;
34 using testing::Return;
35 using testing::SetArgPointee;
36
37 namespace chromeos_update_engine {
38
39 namespace {
40 // Helper to find key and extract value from the given string |xml|, instead
41 // of using a full parser. The attribute key will be followed by "=\"" as xml
42 // attribute values must be within double quotes (not single quotes).
FindAttributeKeyValueInXml(const string & xml,const string & key,const size_t val_size)43 static string FindAttributeKeyValueInXml(const string& xml,
44 const string& key,
45 const size_t val_size) {
46 string key_with_quotes = key + "=\"";
47 const size_t val_start_pos = xml.find(key);
48 if (val_start_pos == string::npos)
49 return "";
50 return xml.substr(val_start_pos + key_with_quotes.size(), val_size);
51 }
52 // Helper to find the count of substring in a string.
CountSubstringInString(const string & str,const string & substr)53 static size_t CountSubstringInString(const string& str, const string& substr) {
54 size_t count = 0, pos = 0;
55 while ((pos = str.find(substr, pos ? pos + 1 : 0)) != string::npos)
56 ++count;
57 return count;
58 }
59 } // namespace
60
61 class OmahaRequestBuilderXmlTest : public ::testing::Test {
62 protected:
SetUp()63 void SetUp() override {
64 FakeSystemState::CreateInstance();
65 FakeSystemState::Get()->set_request_params(¶ms_);
66 }
TearDown()67 void TearDown() override {}
68
69 static constexpr size_t kGuidSize = 36;
70
71 OmahaRequestParams params_;
72 };
73
TEST_F(OmahaRequestBuilderXmlTest,XmlEncodeTest)74 TEST_F(OmahaRequestBuilderXmlTest, XmlEncodeTest) {
75 string output;
76 vector<pair<string, string>> xml_encode_pairs = {
77 {"ab", "ab"},
78 {"a<b", "a<b"},
79 {"<&>\"\'\\", "<&>"'\\"},
80 {"<&>", "&lt;&amp;&gt;"}};
81 for (const auto& xml_encode_pair : xml_encode_pairs) {
82 const auto& before_encoding = xml_encode_pair.first;
83 const auto& after_encoding = xml_encode_pair.second;
84 EXPECT_TRUE(XmlEncode(before_encoding, &output));
85 EXPECT_EQ(after_encoding, output);
86 }
87 // Check that unterminated UTF-8 strings are handled properly.
88 EXPECT_FALSE(XmlEncode("\xc2", &output));
89 // Fail with invalid ASCII-7 chars.
90 EXPECT_FALSE(XmlEncode("This is an 'n' with a tilde: \xc3\xb1", &output));
91 }
92
TEST_F(OmahaRequestBuilderXmlTest,XmlEncodeWithDefaultTest)93 TEST_F(OmahaRequestBuilderXmlTest, XmlEncodeWithDefaultTest) {
94 EXPECT_EQ("", XmlEncodeWithDefault(""));
95 EXPECT_EQ("<&>", XmlEncodeWithDefault("<&>", "something else"));
96 EXPECT_EQ("<not escaped>", XmlEncodeWithDefault("\xc2", "<not escaped>"));
97 }
98
TEST_F(OmahaRequestBuilderXmlTest,PlatformGetAppTest)99 TEST_F(OmahaRequestBuilderXmlTest, PlatformGetAppTest) {
100 params_.set_device_requisition("device requisition");
101 OmahaRequestBuilderXml omaha_request{nullptr,
102 false,
103 false,
104 0,
105 0,
106 0,
107 ""};
108 OmahaAppData dlc_app_data = {.id = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
109 .version = "",
110 .skip_update = false,
111 .is_dlc = false};
112
113 // Verify that the attributes that shouldn't be missing for Platform AppID are
114 // in fact present in the <app ...></app>.
115 const string app = omaha_request.GetApp(dlc_app_data);
116 EXPECT_NE(string::npos, app.find("lang="));
117 EXPECT_NE(string::npos, app.find("requisition="));
118 }
119
TEST_F(OmahaRequestBuilderXmlTest,DlcGetAppTest)120 TEST_F(OmahaRequestBuilderXmlTest, DlcGetAppTest) {
121 params_.set_device_requisition("device requisition");
122 OmahaRequestBuilderXml omaha_request{nullptr,
123 false,
124 false,
125 0,
126 0,
127 0,
128 ""};
129 OmahaAppData dlc_app_data = {
130 .id = "_dlc_id", .version = "", .skip_update = false, .is_dlc = true};
131
132 // Verify that the attributes that should be missing for DLC AppIDs are in
133 // fact not present in the <app ...></app>.
134 const string app = omaha_request.GetApp(dlc_app_data);
135 EXPECT_EQ(string::npos, app.find("lang="));
136 EXPECT_EQ(string::npos, app.find("requisition="));
137 }
138
TEST_F(OmahaRequestBuilderXmlTest,GetRequestXmlRequestIdTest)139 TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlRequestIdTest) {
140 OmahaRequestBuilderXml omaha_request{nullptr,
141 false,
142 false,
143 0,
144 0,
145 0,
146 ""};
147 const string request_xml = omaha_request.GetRequest();
148 const string key = "requestid";
149 const string request_id =
150 FindAttributeKeyValueInXml(request_xml, key, kGuidSize);
151 // A valid |request_id| is either a GUID version 4 or empty string.
152 if (!request_id.empty())
153 EXPECT_TRUE(base::IsValidGUID(request_id));
154 }
155
TEST_F(OmahaRequestBuilderXmlTest,GetRequestXmlSessionIdTest)156 TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlSessionIdTest) {
157 const string gen_session_id = base::GenerateGUID();
158 OmahaRequestBuilderXml omaha_request{nullptr,
159 false,
160 false,
161 0,
162 0,
163 0,
164 gen_session_id};
165 const string request_xml = omaha_request.GetRequest();
166 const string key = "sessionid";
167 const string session_id =
168 FindAttributeKeyValueInXml(request_xml, key, kGuidSize);
169 // A valid |session_id| is either a GUID version 4 or empty string.
170 if (!session_id.empty()) {
171 EXPECT_TRUE(base::IsValidGUID(session_id));
172 }
173 EXPECT_EQ(gen_session_id, session_id);
174 }
175
TEST_F(OmahaRequestBuilderXmlTest,GetRequestXmlPlatformUpdateTest)176 TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlPlatformUpdateTest) {
177 OmahaRequestBuilderXml omaha_request{nullptr,
178 false,
179 false,
180 0,
181 0,
182 0,
183 ""};
184 const string request_xml = omaha_request.GetRequest();
185 EXPECT_EQ(1, CountSubstringInString(request_xml, "<updatecheck"))
186 << request_xml;
187 }
188
TEST_F(OmahaRequestBuilderXmlTest,GetRequestXmlPlatformUpdateWithDlcsTest)189 TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlPlatformUpdateWithDlcsTest) {
190 params_.set_dlc_apps_params(
191 {{params_.GetDlcAppId("dlc_no_0"), {.name = "dlc_no_0"}},
192 {params_.GetDlcAppId("dlc_no_1"), {.name = "dlc_no_1"}}});
193 OmahaRequestBuilderXml omaha_request{nullptr,
194 false,
195 false,
196 0,
197 0,
198 0,
199 ""};
200 const string request_xml = omaha_request.GetRequest();
201 EXPECT_EQ(3, CountSubstringInString(request_xml, "<updatecheck"))
202 << request_xml;
203 }
204
TEST_F(OmahaRequestBuilderXmlTest,GetRequestXmlDlcInstallationTest)205 TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlDlcInstallationTest) {
206 const std::map<std::string, OmahaRequestParams::AppParams> dlcs = {
207 {params_.GetDlcAppId("dlc_no_0"), {.name = "dlc_no_0"}},
208 {params_.GetDlcAppId("dlc_no_1"), {.name = "dlc_no_1"}}};
209 params_.set_dlc_apps_params(dlcs);
210 params_.set_is_install(true);
211 OmahaRequestBuilderXml omaha_request{nullptr,
212 false,
213 false,
214 0,
215 0,
216 0,
217 ""};
218 const string request_xml = omaha_request.GetRequest();
219 EXPECT_EQ(2, CountSubstringInString(request_xml, "<updatecheck"))
220 << request_xml;
221
222 auto FindAppId = [request_xml](size_t pos) -> size_t {
223 return request_xml.find("<app appid", pos);
224 };
225 // Skip over the Platform AppID, which is always first.
226 size_t pos = FindAppId(0);
227 for (auto&& _ : dlcs) {
228 (void)_;
229 EXPECT_NE(string::npos, (pos = FindAppId(pos + 1))) << request_xml;
230 const string dlc_app_id_version = FindAttributeKeyValueInXml(
231 request_xml.substr(pos), "version", string(kNoVersion).size());
232 EXPECT_EQ(kNoVersion, dlc_app_id_version);
233
234 const string false_str = "false";
235 const string dlc_app_id_delta_okay = FindAttributeKeyValueInXml(
236 request_xml.substr(pos), "delta_okay", false_str.length());
237 EXPECT_EQ(false_str, dlc_app_id_delta_okay);
238 }
239 }
240
TEST_F(OmahaRequestBuilderXmlTest,GetRequestXmlDlcNoPing)241 TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlDlcNoPing) {
242 params_.set_dlc_apps_params(
243 {{params_.GetDlcAppId("dlc_no_0"), {.name = "dlc_no_0"}}});
244 OmahaRequestBuilderXml omaha_request{nullptr,
245 false,
246 false,
247 0,
248 0,
249 0,
250 ""};
251 const string request_xml = omaha_request.GetRequest();
252 EXPECT_EQ(0, CountSubstringInString(request_xml, "<ping")) << request_xml;
253 }
254
TEST_F(OmahaRequestBuilderXmlTest,GetRequestXmlDlcPingRollCallNoActive)255 TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlDlcPingRollCallNoActive) {
256 params_.set_dlc_apps_params(
257 {{params_.GetDlcAppId("dlc_no_0"),
258 {.active_counting_type = OmahaRequestParams::kDateBased,
259 .name = "dlc_no_0",
260 .ping_date_last_active = 25,
261 .ping_date_last_rollcall = 36,
262 .send_ping = true}}});
263 OmahaRequestBuilderXml omaha_request{nullptr,
264 false,
265 false,
266 0,
267 0,
268 0,
269 ""};
270 const string request_xml = omaha_request.GetRequest();
271 EXPECT_EQ(1, CountSubstringInString(request_xml, "<ping rd=\"36\""))
272 << request_xml;
273 }
274
TEST_F(OmahaRequestBuilderXmlTest,GetRequestXmlDlcPingRollCallAndActive)275 TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlDlcPingRollCallAndActive) {
276 params_.set_dlc_apps_params(
277 {{params_.GetDlcAppId("dlc_no_0"),
278 {.active_counting_type = OmahaRequestParams::kDateBased,
279 .name = "dlc_no_0",
280 .ping_active = 1,
281 .ping_date_last_active = 25,
282 .ping_date_last_rollcall = 36,
283 .send_ping = true}}});
284 OmahaRequestBuilderXml omaha_request{nullptr,
285 false,
286 false,
287 0,
288 0,
289 0,
290 ""};
291 const string request_xml = omaha_request.GetRequest();
292 EXPECT_EQ(1,
293 CountSubstringInString(request_xml,
294 "<ping active=\"1\" ad=\"25\" rd=\"36\""))
295 << request_xml;
296 }
297
TEST_F(OmahaRequestBuilderXmlTest,GetRequestXmlUpdateCompleteEvent)298 TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlUpdateCompleteEvent) {
299 OmahaEvent event(OmahaEvent::kTypeUpdateComplete);
300 OmahaRequestBuilderXml omaha_request{&event,
301 false,
302 false,
303 0,
304 0,
305 0,
306 ""};
307 const string request_xml = omaha_request.GetRequest();
308 LOG(INFO) << request_xml;
309 EXPECT_EQ(
310 1,
311 CountSubstringInString(
312 request_xml, "<event eventtype=\"3\" eventresult=\"1\"></event>"))
313 << request_xml;
314 }
315
TEST_F(OmahaRequestBuilderXmlTest,GetRequestXmlUpdateCompleteEventSomeDlcsExcluded)316 TEST_F(OmahaRequestBuilderXmlTest,
317 GetRequestXmlUpdateCompleteEventSomeDlcsExcluded) {
318 params_.set_dlc_apps_params({
319 {params_.GetDlcAppId("dlc_1"), {.updated = true}},
320 {params_.GetDlcAppId("dlc_2"), {.updated = false}},
321 });
322 OmahaEvent event(OmahaEvent::kTypeUpdateComplete);
323 OmahaRequestBuilderXml omaha_request{&event,
324 false,
325 false,
326 0,
327 0,
328 0,
329 ""};
330 const string request_xml = omaha_request.GetRequest();
331 EXPECT_EQ(
332 2,
333 CountSubstringInString(
334 request_xml, "<event eventtype=\"3\" eventresult=\"1\"></event>"))
335 << request_xml;
336 EXPECT_EQ(
337 1,
338 CountSubstringInString(
339 request_xml,
340 "<event eventtype=\"3\" eventresult=\"0\" errorcode=\"62\"></event>"))
341 << request_xml;
342 }
343
TEST_F(OmahaRequestBuilderXmlTest,GetRequestXmlUpdateCompleteEventAllDlcsExcluded)344 TEST_F(OmahaRequestBuilderXmlTest,
345 GetRequestXmlUpdateCompleteEventAllDlcsExcluded) {
346 params_.set_dlc_apps_params({
347 {params_.GetDlcAppId("dlc_1"), {.updated = false}},
348 {params_.GetDlcAppId("dlc_2"), {.updated = false}},
349 });
350 OmahaEvent event(OmahaEvent::kTypeUpdateComplete);
351 OmahaRequestBuilderXml omaha_request{&event,
352 false,
353 false,
354 0,
355 0,
356 0,
357 ""};
358 const string request_xml = omaha_request.GetRequest();
359 EXPECT_EQ(
360 1,
361 CountSubstringInString(
362 request_xml, "<event eventtype=\"3\" eventresult=\"1\"></event>"))
363 << request_xml;
364 EXPECT_EQ(
365 2,
366 CountSubstringInString(
367 request_xml,
368 "<event eventtype=\"3\" eventresult=\"0\" errorcode=\"62\"></event>"))
369 << request_xml;
370 }
371
TEST_F(OmahaRequestBuilderXmlTest,GetRequestXmlDlcCohortMissingCheck)372 TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlDlcCohortMissingCheck) {
373 constexpr char kDlcId[] = "test-dlc-id";
374 params_.set_dlc_apps_params(
375 {{params_.GetDlcAppId(kDlcId), {.name = kDlcId}}});
376 OmahaEvent event(OmahaEvent::kTypeUpdateDownloadStarted);
377 OmahaRequestBuilderXml omaha_request{&event, false, false, 0, 0, 0, ""};
378 const string request_xml = omaha_request.GetRequest();
379
380 // Check that no cohorts are in the request.
381 EXPECT_EQ(0, CountSubstringInString(request_xml, "cohort=")) << request_xml;
382 EXPECT_EQ(0, CountSubstringInString(request_xml, "cohortname="))
383 << request_xml;
384 EXPECT_EQ(0, CountSubstringInString(request_xml, "cohorthint="))
385 << request_xml;
386 }
387
TEST_F(OmahaRequestBuilderXmlTest,GetRequestXmlDlcCohortCheck)388 TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlDlcCohortCheck) {
389 const string kDlcId = "test-dlc-id";
390 params_.set_dlc_apps_params(
391 {{params_.GetDlcAppId(kDlcId), {.name = kDlcId}}});
392 auto* fake_prefs = FakeSystemState::Get()->fake_prefs();
393 OmahaEvent event(OmahaEvent::kTypeUpdateDownloadStarted);
394 OmahaRequestBuilderXml omaha_request{&event, false, false, 0, 0, 0, ""};
395 // DLC App ID Expectations.
396 const string dlc_cohort_key = PrefsInterface::CreateSubKey(
397 {kDlcPrefsSubDir, kDlcId, kPrefsOmahaCohort});
398 const string kDlcCohortVal = "test-cohort";
399 EXPECT_TRUE(fake_prefs->SetString(dlc_cohort_key, kDlcCohortVal));
400 const string dlc_cohort_name_key = PrefsInterface::CreateSubKey(
401 {kDlcPrefsSubDir, kDlcId, kPrefsOmahaCohortName});
402 const string kDlcCohortNameVal = "test-cohortname";
403 EXPECT_TRUE(fake_prefs->SetString(dlc_cohort_name_key, kDlcCohortNameVal));
404 const string dlc_cohort_hint_key = PrefsInterface::CreateSubKey(
405 {kDlcPrefsSubDir, kDlcId, kPrefsOmahaCohortHint});
406 const string kDlcCohortHintVal = "test-cohortval";
407 EXPECT_TRUE(fake_prefs->SetString(dlc_cohort_hint_key, kDlcCohortHintVal));
408 const string request_xml = omaha_request.GetRequest();
409
410 EXPECT_EQ(1,
411 CountSubstringInString(
412 request_xml,
413 base::StringPrintf(
414 "cohort=\"%s\" cohortname=\"%s\" cohorthint=\"%s\"",
415 kDlcCohortVal.c_str(),
416 kDlcCohortNameVal.c_str(),
417 kDlcCohortHintVal.c_str())))
418 << request_xml;
419 }
420
421 } // namespace chromeos_update_engine
422