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(&params_);
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&lt;b"},
79       {"<&>\"\'\\", "&lt;&amp;&gt;&quot;&apos;\\"},
80       {"&lt;&amp;&gt;", "&amp;lt;&amp;amp;&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("&lt;&amp;&gt;", 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