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/omaha_request_builder_xml.h"
18 
19 #include <string>
20 #include <utility>
21 #include <vector>
22 
23 #include <base/guid.h>
24 #include <gtest/gtest.h>
25 
26 #include "update_engine/fake_system_state.h"
27 
28 using std::pair;
29 using std::string;
30 using std::vector;
31 
32 namespace chromeos_update_engine {
33 
34 namespace {
35 // Helper to find key and extract value from the given string |xml|, instead
36 // of using a full parser. The attribute key will be followed by "=\"" as xml
37 // attribute values must be within double quotes (not single quotes).
FindAttributeKeyValueInXml(const string & xml,const string & key,const size_t val_size)38 static string FindAttributeKeyValueInXml(const string& xml,
39                                          const string& key,
40                                          const size_t val_size) {
41   string key_with_quotes = key + "=\"";
42   const size_t val_start_pos = xml.find(key);
43   if (val_start_pos == string::npos)
44     return "";
45   return xml.substr(val_start_pos + key_with_quotes.size(), val_size);
46 }
47 // Helper to find the count of substring in a string.
CountSubstringInString(const string & str,const string & substr)48 static size_t CountSubstringInString(const string& str, const string& substr) {
49   size_t count = 0, pos = 0;
50   while ((pos = str.find(substr, pos ? pos + 1 : 0)) != string::npos)
51     ++count;
52   return count;
53 }
54 }  // namespace
55 
56 class OmahaRequestBuilderXmlTest : public ::testing::Test {
57  protected:
SetUp()58   void SetUp() override {}
TearDown()59   void TearDown() override {}
60 
61   FakeSystemState fake_system_state_;
62   static constexpr size_t kGuidSize = 36;
63 };
64 
TEST_F(OmahaRequestBuilderXmlTest,XmlEncodeTest)65 TEST_F(OmahaRequestBuilderXmlTest, XmlEncodeTest) {
66   string output;
67   vector<pair<string, string>> xml_encode_pairs = {
68       {"ab", "ab"},
69       {"a<b", "a&lt;b"},
70       {"<&>\"\'\\", "&lt;&amp;&gt;&quot;&apos;\\"},
71       {"&lt;&amp;&gt;", "&amp;lt;&amp;amp;&amp;gt;"}};
72   for (const auto& xml_encode_pair : xml_encode_pairs) {
73     const auto& before_encoding = xml_encode_pair.first;
74     const auto& after_encoding = xml_encode_pair.second;
75     EXPECT_TRUE(XmlEncode(before_encoding, &output));
76     EXPECT_EQ(after_encoding, output);
77   }
78   // Check that unterminated UTF-8 strings are handled properly.
79   EXPECT_FALSE(XmlEncode("\xc2", &output));
80   // Fail with invalid ASCII-7 chars.
81   EXPECT_FALSE(XmlEncode("This is an 'n' with a tilde: \xc3\xb1", &output));
82 }
83 
TEST_F(OmahaRequestBuilderXmlTest,XmlEncodeWithDefaultTest)84 TEST_F(OmahaRequestBuilderXmlTest, XmlEncodeWithDefaultTest) {
85   EXPECT_EQ("", XmlEncodeWithDefault(""));
86   EXPECT_EQ("&lt;&amp;&gt;", XmlEncodeWithDefault("<&>", "something else"));
87   EXPECT_EQ("<not escaped>", XmlEncodeWithDefault("\xc2", "<not escaped>"));
88 }
89 
TEST_F(OmahaRequestBuilderXmlTest,PlatformGetAppTest)90 TEST_F(OmahaRequestBuilderXmlTest, PlatformGetAppTest) {
91   OmahaRequestParams omaha_request_params{&fake_system_state_};
92   omaha_request_params.set_device_requisition("device requisition");
93   OmahaRequestBuilderXml omaha_request{nullptr,
94                                        &omaha_request_params,
95                                        false,
96                                        false,
97                                        0,
98                                        0,
99                                        0,
100                                        fake_system_state_.prefs(),
101                                        ""};
102   OmahaAppData dlc_app_data = {.id = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
103                                .version = "",
104                                .skip_update = false,
105                                .is_dlc = false};
106 
107   // Verify that the attributes that shouldn't be missing for Platform AppID are
108   // in fact present in the <app ...></app>.
109   const string app = omaha_request.GetApp(dlc_app_data);
110   EXPECT_NE(string::npos, app.find("lang="));
111   EXPECT_NE(string::npos, app.find("fw_version="));
112   EXPECT_NE(string::npos, app.find("ec_version="));
113   EXPECT_NE(string::npos, app.find("requisition="));
114 }
115 
TEST_F(OmahaRequestBuilderXmlTest,DlcGetAppTest)116 TEST_F(OmahaRequestBuilderXmlTest, DlcGetAppTest) {
117   OmahaRequestParams omaha_request_params{&fake_system_state_};
118   omaha_request_params.set_device_requisition("device requisition");
119   OmahaRequestBuilderXml omaha_request{nullptr,
120                                        &omaha_request_params,
121                                        false,
122                                        false,
123                                        0,
124                                        0,
125                                        0,
126                                        fake_system_state_.prefs(),
127                                        ""};
128   OmahaAppData dlc_app_data = {
129       .id = "_dlc_id", .version = "", .skip_update = false, .is_dlc = true};
130 
131   // Verify that the attributes that should be missing for DLC AppIDs are in
132   // fact not present in the <app ...></app>.
133   const string app = omaha_request.GetApp(dlc_app_data);
134   EXPECT_EQ(string::npos, app.find("lang="));
135   EXPECT_EQ(string::npos, app.find("fw_version="));
136   EXPECT_EQ(string::npos, app.find("ec_version="));
137   EXPECT_EQ(string::npos, app.find("requisition="));
138 }
139 
TEST_F(OmahaRequestBuilderXmlTest,GetRequestXmlRequestIdTest)140 TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlRequestIdTest) {
141   OmahaRequestParams omaha_request_params{&fake_system_state_};
142   OmahaRequestBuilderXml omaha_request{nullptr,
143                                        &omaha_request_params,
144                                        false,
145                                        false,
146                                        0,
147                                        0,
148                                        0,
149                                        fake_system_state_.prefs(),
150                                        ""};
151   const string request_xml = omaha_request.GetRequest();
152   const string key = "requestid";
153   const string request_id =
154       FindAttributeKeyValueInXml(request_xml, key, kGuidSize);
155   // A valid |request_id| is either a GUID version 4 or empty string.
156   if (!request_id.empty())
157     EXPECT_TRUE(base::IsValidGUID(request_id));
158 }
159 
TEST_F(OmahaRequestBuilderXmlTest,GetRequestXmlSessionIdTest)160 TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlSessionIdTest) {
161   const string gen_session_id = base::GenerateGUID();
162   OmahaRequestParams omaha_request_params{&fake_system_state_};
163   OmahaRequestBuilderXml omaha_request{nullptr,
164                                        &omaha_request_params,
165                                        false,
166                                        false,
167                                        0,
168                                        0,
169                                        0,
170                                        fake_system_state_.prefs(),
171                                        gen_session_id};
172   const string request_xml = omaha_request.GetRequest();
173   const string key = "sessionid";
174   const string session_id =
175       FindAttributeKeyValueInXml(request_xml, key, kGuidSize);
176   // A valid |session_id| is either a GUID version 4 or empty string.
177   if (!session_id.empty()) {
178     EXPECT_TRUE(base::IsValidGUID(session_id));
179   }
180   EXPECT_EQ(gen_session_id, session_id);
181 }
182 
TEST_F(OmahaRequestBuilderXmlTest,GetRequestXmlPlatformUpdateTest)183 TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlPlatformUpdateTest) {
184   OmahaRequestParams omaha_request_params{&fake_system_state_};
185   OmahaRequestBuilderXml omaha_request{nullptr,
186                                        &omaha_request_params,
187                                        false,
188                                        false,
189                                        0,
190                                        0,
191                                        0,
192                                        fake_system_state_.prefs(),
193                                        ""};
194   const string request_xml = omaha_request.GetRequest();
195   EXPECT_EQ(1, CountSubstringInString(request_xml, "<updatecheck"))
196       << request_xml;
197 }
198 
TEST_F(OmahaRequestBuilderXmlTest,GetRequestXmlPlatformUpdateWithDlcsTest)199 TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlPlatformUpdateWithDlcsTest) {
200   OmahaRequestParams omaha_request_params{&fake_system_state_};
201   omaha_request_params.set_dlc_apps_params(
202       {{omaha_request_params.GetDlcAppId("dlc_no_0"), {.name = "dlc_no_0"}},
203        {omaha_request_params.GetDlcAppId("dlc_no_1"), {.name = "dlc_no_1"}}});
204   OmahaRequestBuilderXml omaha_request{nullptr,
205                                        &omaha_request_params,
206                                        false,
207                                        false,
208                                        0,
209                                        0,
210                                        0,
211                                        fake_system_state_.prefs(),
212                                        ""};
213   const string request_xml = omaha_request.GetRequest();
214   EXPECT_EQ(3, CountSubstringInString(request_xml, "<updatecheck"))
215       << request_xml;
216 }
217 
TEST_F(OmahaRequestBuilderXmlTest,GetRequestXmlDlcInstallationTest)218 TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlDlcInstallationTest) {
219   OmahaRequestParams omaha_request_params{&fake_system_state_};
220   const std::map<std::string, OmahaRequestParams::AppParams> dlcs = {
221       {omaha_request_params.GetDlcAppId("dlc_no_0"), {.name = "dlc_no_0"}},
222       {omaha_request_params.GetDlcAppId("dlc_no_1"), {.name = "dlc_no_1"}}};
223   omaha_request_params.set_dlc_apps_params(dlcs);
224   omaha_request_params.set_is_install(true);
225   OmahaRequestBuilderXml omaha_request{nullptr,
226                                        &omaha_request_params,
227                                        false,
228                                        false,
229                                        0,
230                                        0,
231                                        0,
232                                        fake_system_state_.prefs(),
233                                        ""};
234   const string request_xml = omaha_request.GetRequest();
235   EXPECT_EQ(2, CountSubstringInString(request_xml, "<updatecheck"))
236       << request_xml;
237 
238   auto FindAppId = [request_xml](size_t pos) -> size_t {
239     return request_xml.find("<app appid", pos);
240   };
241   // Skip over the Platform AppID, which is always first.
242   size_t pos = FindAppId(0);
243   for (auto&& _ : dlcs) {
244     (void)_;
245     EXPECT_NE(string::npos, (pos = FindAppId(pos + 1))) << request_xml;
246     const string dlc_app_id_version = FindAttributeKeyValueInXml(
247         request_xml.substr(pos), "version", string(kNoVersion).size());
248     EXPECT_EQ(kNoVersion, dlc_app_id_version);
249 
250     const string false_str = "false";
251     const string dlc_app_id_delta_okay = FindAttributeKeyValueInXml(
252         request_xml.substr(pos), "delta_okay", false_str.length());
253     EXPECT_EQ(false_str, dlc_app_id_delta_okay);
254   }
255 }
256 
TEST_F(OmahaRequestBuilderXmlTest,GetRequestXmlDlcNoPing)257 TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlDlcNoPing) {
258   OmahaRequestParams omaha_request_params{&fake_system_state_};
259   omaha_request_params.set_dlc_apps_params(
260       {{omaha_request_params.GetDlcAppId("dlc_no_0"), {.name = "dlc_no_0"}}});
261   OmahaRequestBuilderXml omaha_request{nullptr,
262                                        &omaha_request_params,
263                                        false,
264                                        false,
265                                        0,
266                                        0,
267                                        0,
268                                        fake_system_state_.prefs(),
269                                        ""};
270   const string request_xml = omaha_request.GetRequest();
271   EXPECT_EQ(0, CountSubstringInString(request_xml, "<ping")) << request_xml;
272 }
273 
TEST_F(OmahaRequestBuilderXmlTest,GetRequestXmlDlcPingRollCallNoActive)274 TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlDlcPingRollCallNoActive) {
275   OmahaRequestParams omaha_request_params{&fake_system_state_};
276   omaha_request_params.set_dlc_apps_params(
277       {{omaha_request_params.GetDlcAppId("dlc_no_0"),
278         {.active_counting_type = OmahaRequestParams::kDateBased,
279          .name = "dlc_no_0",
280          .ping_date_last_active = 25,
281          .ping_date_last_rollcall = 36,
282          .send_ping = true}}});
283   OmahaRequestBuilderXml omaha_request{nullptr,
284                                        &omaha_request_params,
285                                        false,
286                                        false,
287                                        0,
288                                        0,
289                                        0,
290                                        fake_system_state_.prefs(),
291                                        ""};
292   const string request_xml = omaha_request.GetRequest();
293   EXPECT_EQ(1, CountSubstringInString(request_xml, "<ping rd=\"36\""))
294       << request_xml;
295 }
296 
TEST_F(OmahaRequestBuilderXmlTest,GetRequestXmlDlcPingRollCallAndActive)297 TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlDlcPingRollCallAndActive) {
298   OmahaRequestParams omaha_request_params{&fake_system_state_};
299   omaha_request_params.set_dlc_apps_params(
300       {{omaha_request_params.GetDlcAppId("dlc_no_0"),
301         {.active_counting_type = OmahaRequestParams::kDateBased,
302          .name = "dlc_no_0",
303          .ping_active = 1,
304          .ping_date_last_active = 25,
305          .ping_date_last_rollcall = 36,
306          .send_ping = true}}});
307   OmahaRequestBuilderXml omaha_request{nullptr,
308                                        &omaha_request_params,
309                                        false,
310                                        false,
311                                        0,
312                                        0,
313                                        0,
314                                        fake_system_state_.prefs(),
315                                        ""};
316   const string request_xml = omaha_request.GetRequest();
317   EXPECT_EQ(1,
318             CountSubstringInString(request_xml,
319                                    "<ping active=\"1\" ad=\"25\" rd=\"36\""))
320       << request_xml;
321 }
322 }  // namespace chromeos_update_engine
323