blob: aac01366e9b3332cc723470a69990bbc7e892172 [file] [log] [blame]
Amin Hassani7fca2862019-03-28 16:09:22 -07001//
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 <inttypes.h>
20
21#include <string>
22
23#include <base/logging.h>
24#include <base/strings/string_number_conversions.h>
25#include <base/strings/string_util.h>
26#include <base/strings/stringprintf.h>
27#include <base/time/time.h>
28
29#include "update_engine/common/constants.h"
30#include "update_engine/common/prefs_interface.h"
31#include "update_engine/common/utils.h"
32#include "update_engine/omaha_request_params.h"
33
34using std::string;
35
36namespace chromeos_update_engine {
37
38const int kNeverPinged = -1;
39
40bool XmlEncode(const string& input, string* output) {
41 if (std::find_if(input.begin(), input.end(), [](const char c) {
42 return c & 0x80;
43 }) != input.end()) {
44 LOG(WARNING) << "Invalid ASCII-7 string passed to the XML encoder:";
45 utils::HexDumpString(input);
46 return false;
47 }
48 output->clear();
49 // We need at least input.size() space in the output, but the code below will
50 // handle it if we need more.
51 output->reserve(input.size());
52 for (char c : input) {
53 switch (c) {
54 case '\"':
55 output->append("&quot;");
56 break;
57 case '\'':
58 output->append("&apos;");
59 break;
60 case '&':
61 output->append("&amp;");
62 break;
63 case '<':
64 output->append("&lt;");
65 break;
66 case '>':
67 output->append("&gt;");
68 break;
69 default:
70 output->push_back(c);
71 }
72 }
73 return true;
74}
75
76string XmlEncodeWithDefault(const string& input, const string& default_value) {
77 string output;
78 if (XmlEncode(input, &output))
79 return output;
80 return default_value;
81}
82
83string GetPingAttribute(const string& name, int ping_days) {
84 if (ping_days > 0 || ping_days == kNeverPinged)
85 return base::StringPrintf(" %s=\"%d\"", name.c_str(), ping_days);
86 return "";
87}
88
89string GetPingXml(int ping_active_days, int ping_roll_call_days) {
90 string ping_active = GetPingAttribute("a", ping_active_days);
91 string ping_roll_call = GetPingAttribute("r", ping_roll_call_days);
92 if (!ping_active.empty() || !ping_roll_call.empty()) {
93 return base::StringPrintf(" <ping active=\"1\"%s%s></ping>\n",
94 ping_active.c_str(),
95 ping_roll_call.c_str());
96 }
97 return "";
98}
99
100string GetAppBody(const OmahaEvent* event,
101 OmahaRequestParams* params,
102 bool ping_only,
103 bool include_ping,
104 bool skip_updatecheck,
105 int ping_active_days,
106 int ping_roll_call_days,
107 PrefsInterface* prefs) {
108 string app_body;
109 if (event == nullptr) {
110 if (include_ping)
111 app_body = GetPingXml(ping_active_days, ping_roll_call_days);
112 if (!ping_only) {
113 if (!skip_updatecheck) {
114 app_body += " <updatecheck";
115 if (!params->target_version_prefix().empty()) {
116 app_body += base::StringPrintf(
117 " targetversionprefix=\"%s\"",
118 XmlEncodeWithDefault(params->target_version_prefix(), "")
119 .c_str());
120 // Rollback requires target_version_prefix set.
121 if (params->rollback_allowed()) {
122 app_body += " rollback_allowed=\"true\"";
123 }
124 }
Askar Aitzhan570ca872019-04-24 11:16:12 +0200125 string autoupdate_token = params->autoupdate_token();
126 if (!autoupdate_token.empty()) {
127 app_body += base::StringPrintf(
128 " token=\"%s\"",
129 XmlEncodeWithDefault(autoupdate_token, "").c_str());
130 }
131
Amin Hassani7fca2862019-03-28 16:09:22 -0700132 app_body += "></updatecheck>\n";
133 }
134
135 // If this is the first update check after a reboot following a previous
136 // update, generate an event containing the previous version number. If
137 // the previous version preference file doesn't exist the event is still
138 // generated with a previous version of 0.0.0.0 -- this is relevant for
139 // older clients or new installs. The previous version event is not sent
140 // for ping-only requests because they come before the client has
141 // rebooted. The previous version event is also not sent if it was already
142 // sent for this new version with a previous updatecheck.
143 string prev_version;
144 if (!prefs->GetString(kPrefsPreviousVersion, &prev_version)) {
145 prev_version = "0.0.0.0";
146 }
147 // We only store a non-empty previous version value after a successful
148 // update in the previous boot. After reporting it back to the server,
149 // we clear the previous version value so it doesn't get reported again.
150 if (!prev_version.empty()) {
151 app_body += base::StringPrintf(
152 " <event eventtype=\"%d\" eventresult=\"%d\" "
153 "previousversion=\"%s\"></event>\n",
154 OmahaEvent::kTypeRebootedAfterUpdate,
155 OmahaEvent::kResultSuccess,
156 XmlEncodeWithDefault(prev_version, "0.0.0.0").c_str());
157 LOG_IF(WARNING, !prefs->SetString(kPrefsPreviousVersion, ""))
158 << "Unable to reset the previous version.";
159 }
160 }
161 } else {
162 // The error code is an optional attribute so append it only if the result
163 // is not success.
164 string error_code;
165 if (event->result != OmahaEvent::kResultSuccess) {
166 error_code = base::StringPrintf(" errorcode=\"%d\"",
167 static_cast<int>(event->error_code));
168 }
169 app_body = base::StringPrintf(
170 " <event eventtype=\"%d\" eventresult=\"%d\"%s></event>\n",
171 event->type,
172 event->result,
173 error_code.c_str());
174 }
175
176 return app_body;
177}
178
179string GetCohortArgXml(PrefsInterface* prefs,
180 const string arg_name,
181 const string prefs_key) {
182 // There's nothing wrong with not having a given cohort setting, so we check
183 // existence first to avoid the warning log message.
184 if (!prefs->Exists(prefs_key))
185 return "";
186 string cohort_value;
187 if (!prefs->GetString(prefs_key, &cohort_value) || cohort_value.empty())
188 return "";
189 // This is a sanity check to avoid sending a huge XML file back to Ohama due
190 // to a compromised stateful partition making the update check fail in low
191 // network environments envent after a reboot.
192 if (cohort_value.size() > 1024) {
193 LOG(WARNING) << "The omaha cohort setting " << arg_name
194 << " has a too big value, which must be an error or an "
195 "attacker trying to inhibit updates.";
196 return "";
197 }
198
199 string escaped_xml_value;
200 if (!XmlEncode(cohort_value, &escaped_xml_value)) {
201 LOG(WARNING) << "The omaha cohort setting " << arg_name
202 << " is ASCII-7 invalid, ignoring it.";
203 return "";
204 }
205
206 return base::StringPrintf(
207 "%s=\"%s\" ", arg_name.c_str(), escaped_xml_value.c_str());
208}
209
210bool IsValidComponentID(const string& id) {
211 for (char c : id) {
212 if (!isalnum(c) && c != '-' && c != '_' && c != '.')
213 return false;
214 }
215 return true;
216}
217
218string GetAppXml(const OmahaEvent* event,
219 OmahaRequestParams* params,
220 const OmahaAppData& app_data,
221 bool ping_only,
222 bool include_ping,
223 bool skip_updatecheck,
224 int ping_active_days,
225 int ping_roll_call_days,
226 int install_date_in_days,
227 SystemState* system_state) {
228 string app_body = GetAppBody(event,
229 params,
230 ping_only,
231 include_ping,
232 skip_updatecheck,
233 ping_active_days,
234 ping_roll_call_days,
235 system_state->prefs());
236 string app_versions;
237
238 // If we are downgrading to a more stable channel and we are allowed to do
239 // powerwash, then pass 0.0.0.0 as the version. This is needed to get the
240 // highest-versioned payload on the destination channel.
241 if (params->ShouldPowerwash()) {
242 LOG(INFO) << "Passing OS version as 0.0.0.0 as we are set to powerwash "
243 << "on downgrading to the version in the more stable channel";
244 app_versions = "version=\"0.0.0.0\" from_version=\"" +
245 XmlEncodeWithDefault(app_data.version, "0.0.0.0") + "\" ";
246 } else {
247 app_versions = "version=\"" +
248 XmlEncodeWithDefault(app_data.version, "0.0.0.0") + "\" ";
249 }
250
251 string download_channel = params->download_channel();
252 string app_channels =
253 "track=\"" + XmlEncodeWithDefault(download_channel, "") + "\" ";
254 if (params->current_channel() != download_channel) {
255 app_channels += "from_track=\"" +
256 XmlEncodeWithDefault(params->current_channel(), "") + "\" ";
257 }
258
259 string delta_okay_str = params->delta_okay() ? "true" : "false";
260
261 // If install_date_days is not set (e.g. its value is -1 ), don't
262 // include the attribute.
263 string install_date_in_days_str = "";
264 if (install_date_in_days >= 0) {
265 install_date_in_days_str =
266 base::StringPrintf("installdate=\"%d\" ", install_date_in_days);
267 }
268
269 string app_cohort_args;
270 app_cohort_args +=
271 GetCohortArgXml(system_state->prefs(), "cohort", kPrefsOmahaCohort);
272 app_cohort_args += GetCohortArgXml(
273 system_state->prefs(), "cohorthint", kPrefsOmahaCohortHint);
274 app_cohort_args += GetCohortArgXml(
275 system_state->prefs(), "cohortname", kPrefsOmahaCohortName);
276
277 string fingerprint_arg;
278 if (!params->os_build_fingerprint().empty()) {
279 fingerprint_arg = "fingerprint=\"" +
280 XmlEncodeWithDefault(params->os_build_fingerprint(), "") +
281 "\" ";
282 }
283
284 string buildtype_arg;
285 if (!params->os_build_type().empty()) {
286 buildtype_arg = "os_build_type=\"" +
287 XmlEncodeWithDefault(params->os_build_type(), "") + "\" ";
288 }
289
290 string product_components_args;
291 if (!params->ShouldPowerwash() && !app_data.product_components.empty()) {
292 brillo::KeyValueStore store;
293 if (store.LoadFromString(app_data.product_components)) {
294 for (const string& key : store.GetKeys()) {
295 if (!IsValidComponentID(key)) {
296 LOG(ERROR) << "Invalid component id: " << key;
297 continue;
298 }
299 string version;
300 if (!store.GetString(key, &version)) {
301 LOG(ERROR) << "Failed to get version for " << key
302 << " in product_components.";
303 continue;
304 }
305 product_components_args +=
306 base::StringPrintf("_%s.version=\"%s\" ",
307 key.c_str(),
308 XmlEncodeWithDefault(version, "").c_str());
309 }
310 } else {
311 LOG(ERROR) << "Failed to parse product_components:\n"
312 << app_data.product_components;
313 }
314 }
315
316 // clang-format off
317 string app_xml = " <app "
318 "appid=\"" + XmlEncodeWithDefault(app_data.id, "") + "\" " +
319 app_cohort_args +
320 app_versions +
321 app_channels +
322 product_components_args +
323 fingerprint_arg +
324 buildtype_arg +
325 "lang=\"" + XmlEncodeWithDefault(params->app_lang(), "en-US") + "\" " +
326 "board=\"" + XmlEncodeWithDefault(params->os_board(), "") + "\" " +
327 "hardware_class=\"" + XmlEncodeWithDefault(params->hwid(), "") + "\" " +
328 "delta_okay=\"" + delta_okay_str + "\" "
329 "fw_version=\"" + XmlEncodeWithDefault(params->fw_version(), "") + "\" " +
330 "ec_version=\"" + XmlEncodeWithDefault(params->ec_version(), "") + "\" " +
331 install_date_in_days_str +
332 ">\n" +
333 app_body +
334 " </app>\n";
335 // clang-format on
336 return app_xml;
337}
338
339string GetOsXml(OmahaRequestParams* params) {
340 string os_xml =
341 " <os "
342 "version=\"" +
343 XmlEncodeWithDefault(params->os_version(), "") + "\" " + "platform=\"" +
344 XmlEncodeWithDefault(params->os_platform(), "") + "\" " + "sp=\"" +
345 XmlEncodeWithDefault(params->os_sp(), "") +
346 "\">"
347 "</os>\n";
348 return os_xml;
349}
350
351string GetRequestXml(const OmahaEvent* event,
352 OmahaRequestParams* params,
353 bool ping_only,
354 bool include_ping,
355 int ping_active_days,
356 int ping_roll_call_days,
357 int install_date_in_days,
358 SystemState* system_state) {
359 string os_xml = GetOsXml(params);
360 OmahaAppData product_app = {
361 .id = params->GetAppId(),
362 .version = params->app_version(),
363 .product_components = params->product_components()};
364 // Skips updatecheck for platform app in case of an install operation.
365 string app_xml = GetAppXml(event,
366 params,
367 product_app,
368 ping_only,
369 include_ping,
370 params->is_install(), /* skip_updatecheck */
371 ping_active_days,
372 ping_roll_call_days,
373 install_date_in_days,
374 system_state);
375 if (!params->system_app_id().empty()) {
376 OmahaAppData system_app = {.id = params->system_app_id(),
377 .version = params->system_version()};
378 app_xml += GetAppXml(event,
379 params,
380 system_app,
381 ping_only,
382 include_ping,
383 false, /* skip_updatecheck */
384 ping_active_days,
385 ping_roll_call_days,
386 install_date_in_days,
387 system_state);
388 }
389 // Create APP ID according to |dlc_module_id| (sticking the current AppID to
390 // the DLC module ID with an underscode).
391 for (const auto& dlc_module_id : params->dlc_module_ids()) {
392 OmahaAppData dlc_module_app = {
393 .id = params->GetAppId() + "_" + dlc_module_id,
394 .version = params->app_version()};
395 app_xml += GetAppXml(event,
396 params,
397 dlc_module_app,
398 ping_only,
399 include_ping,
400 false, /* skip_updatecheck */
401 ping_active_days,
402 ping_roll_call_days,
403 install_date_in_days,
404 system_state);
405 }
406
407 string request_xml = base::StringPrintf(
408 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
409 "<request protocol=\"3.0\" updater=\"%s\" updaterversion=\"%s\""
410 " installsource=\"%s\" ismachine=\"1\">\n%s%s</request>\n",
411 constants::kOmahaUpdaterID,
412 kOmahaUpdaterVersion,
413 params->interactive() ? "ondemandupdate" : "scheduler",
414 os_xml.c_str(),
415 app_xml.c_str());
416
417 return request_xml;
418}
419
420} // namespace chromeos_update_engine