blob: 8439b4224b55e55becea9f60d0ae6c99ee958c98 [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
Jae Hoon Kim6ada5912019-06-14 10:11:34 -070023#include <base/guid.h>
Amin Hassani7fca2862019-03-28 16:09:22 -070024#include <base/logging.h>
25#include <base/strings/string_number_conversions.h>
26#include <base/strings/string_util.h>
27#include <base/strings/stringprintf.h>
28#include <base/time/time.h>
29
30#include "update_engine/common/constants.h"
31#include "update_engine/common/prefs_interface.h"
32#include "update_engine/common/utils.h"
33#include "update_engine/omaha_request_params.h"
34
35using std::string;
36
37namespace chromeos_update_engine {
38
39const int kNeverPinged = -1;
Jae Hoon Kim5fc00a22020-01-08 20:15:42 -080040const char kNoVersion[] = "0.0.0.0";
Amin Hassani7fca2862019-03-28 16:09:22 -070041
42bool XmlEncode(const string& input, string* output) {
43 if (std::find_if(input.begin(), input.end(), [](const char c) {
44 return c & 0x80;
45 }) != input.end()) {
46 LOG(WARNING) << "Invalid ASCII-7 string passed to the XML encoder:";
47 utils::HexDumpString(input);
48 return false;
49 }
50 output->clear();
51 // We need at least input.size() space in the output, but the code below will
52 // handle it if we need more.
53 output->reserve(input.size());
54 for (char c : input) {
55 switch (c) {
56 case '\"':
57 output->append("&quot;");
58 break;
59 case '\'':
60 output->append("&apos;");
61 break;
62 case '&':
63 output->append("&amp;");
64 break;
65 case '<':
66 output->append("&lt;");
67 break;
68 case '>':
69 output->append("&gt;");
70 break;
71 default:
72 output->push_back(c);
73 }
74 }
75 return true;
76}
77
78string XmlEncodeWithDefault(const string& input, const string& default_value) {
79 string output;
80 if (XmlEncode(input, &output))
81 return output;
82 return default_value;
83}
84
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -070085string OmahaRequestBuilderXml::GetPing() const {
86 // Returns an XML ping element attribute assignment with attribute
87 // |name| and value |ping_days| if |ping_days| has a value that needs
88 // to be sent, or an empty string otherwise.
89 auto GetPingAttribute = [](const char* name, int ping_days) -> string {
90 if (ping_days > 0 || ping_days == kNeverPinged)
91 return base::StringPrintf(" %s=\"%d\"", name, ping_days);
92 return "";
93 };
Amin Hassani7fca2862019-03-28 16:09:22 -070094
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -070095 string ping_active = GetPingAttribute("a", ping_active_days_);
96 string ping_roll_call = GetPingAttribute("r", ping_roll_call_days_);
Amin Hassani7fca2862019-03-28 16:09:22 -070097 if (!ping_active.empty() || !ping_roll_call.empty()) {
98 return base::StringPrintf(" <ping active=\"1\"%s%s></ping>\n",
99 ping_active.c_str(),
100 ping_roll_call.c_str());
101 }
102 return "";
103}
104
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700105string OmahaRequestBuilderXml::GetAppBody(bool skip_updatecheck) const {
Amin Hassani7fca2862019-03-28 16:09:22 -0700106 string app_body;
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700107 if (event_ == nullptr) {
108 if (include_ping_)
109 app_body = GetPing();
110 if (!ping_only_) {
Amin Hassani7fca2862019-03-28 16:09:22 -0700111 if (!skip_updatecheck) {
112 app_body += " <updatecheck";
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700113 if (!params_->target_version_prefix().empty()) {
Amin Hassani7fca2862019-03-28 16:09:22 -0700114 app_body += base::StringPrintf(
115 " targetversionprefix=\"%s\"",
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700116 XmlEncodeWithDefault(params_->target_version_prefix()).c_str());
Amin Hassani7fca2862019-03-28 16:09:22 -0700117 // Rollback requires target_version_prefix set.
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700118 if (params_->rollback_allowed()) {
Amin Hassani7fca2862019-03-28 16:09:22 -0700119 app_body += " rollback_allowed=\"true\"";
120 }
121 }
Amin Hassani7fca2862019-03-28 16:09:22 -0700122 app_body += "></updatecheck>\n";
123 }
124
125 // If this is the first update check after a reboot following a previous
126 // update, generate an event containing the previous version number. If
127 // the previous version preference file doesn't exist the event is still
128 // generated with a previous version of 0.0.0.0 -- this is relevant for
129 // older clients or new installs. The previous version event is not sent
130 // for ping-only requests because they come before the client has
131 // rebooted. The previous version event is also not sent if it was already
132 // sent for this new version with a previous updatecheck.
133 string prev_version;
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700134 if (!prefs_->GetString(kPrefsPreviousVersion, &prev_version)) {
Jae Hoon Kim5fc00a22020-01-08 20:15:42 -0800135 prev_version = kNoVersion;
Amin Hassani7fca2862019-03-28 16:09:22 -0700136 }
137 // We only store a non-empty previous version value after a successful
138 // update in the previous boot. After reporting it back to the server,
139 // we clear the previous version value so it doesn't get reported again.
140 if (!prev_version.empty()) {
141 app_body += base::StringPrintf(
142 " <event eventtype=\"%d\" eventresult=\"%d\" "
143 "previousversion=\"%s\"></event>\n",
144 OmahaEvent::kTypeRebootedAfterUpdate,
145 OmahaEvent::kResultSuccess,
Jae Hoon Kim5fc00a22020-01-08 20:15:42 -0800146 XmlEncodeWithDefault(prev_version, kNoVersion).c_str());
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700147 LOG_IF(WARNING, !prefs_->SetString(kPrefsPreviousVersion, ""))
Amin Hassani7fca2862019-03-28 16:09:22 -0700148 << "Unable to reset the previous version.";
149 }
150 }
151 } else {
152 // The error code is an optional attribute so append it only if the result
153 // is not success.
154 string error_code;
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700155 if (event_->result != OmahaEvent::kResultSuccess) {
Amin Hassani7fca2862019-03-28 16:09:22 -0700156 error_code = base::StringPrintf(" errorcode=\"%d\"",
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700157 static_cast<int>(event_->error_code));
Amin Hassani7fca2862019-03-28 16:09:22 -0700158 }
159 app_body = base::StringPrintf(
160 " <event eventtype=\"%d\" eventresult=\"%d\"%s></event>\n",
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700161 event_->type,
162 event_->result,
Amin Hassani7fca2862019-03-28 16:09:22 -0700163 error_code.c_str());
164 }
165
166 return app_body;
167}
168
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700169string OmahaRequestBuilderXml::GetCohortArg(const string arg_name,
Askar Aitzhan18fff842019-06-21 23:24:37 +0200170 const string prefs_key,
171 const string override_value) const {
Amin Hassani7fca2862019-03-28 16:09:22 -0700172 string cohort_value;
Askar Aitzhan18fff842019-06-21 23:24:37 +0200173 if (!override_value.empty()) {
174 // |override_value| take precedence over pref value.
175 cohort_value = override_value;
176 } else {
177 // There's nothing wrong with not having a given cohort setting, so we check
178 // existence first to avoid the warning log message.
179 if (!prefs_->Exists(prefs_key))
180 return "";
181 if (!prefs_->GetString(prefs_key, &cohort_value) || cohort_value.empty())
182 return "";
183 }
Amin Hassani7fca2862019-03-28 16:09:22 -0700184 // This is a sanity check to avoid sending a huge XML file back to Ohama due
185 // to a compromised stateful partition making the update check fail in low
186 // network environments envent after a reboot.
187 if (cohort_value.size() > 1024) {
188 LOG(WARNING) << "The omaha cohort setting " << arg_name
189 << " has a too big value, which must be an error or an "
190 "attacker trying to inhibit updates.";
191 return "";
192 }
193
194 string escaped_xml_value;
195 if (!XmlEncode(cohort_value, &escaped_xml_value)) {
196 LOG(WARNING) << "The omaha cohort setting " << arg_name
197 << " is ASCII-7 invalid, ignoring it.";
198 return "";
199 }
200
201 return base::StringPrintf(
202 "%s=\"%s\" ", arg_name.c_str(), escaped_xml_value.c_str());
203}
204
205bool IsValidComponentID(const string& id) {
206 for (char c : id) {
207 if (!isalnum(c) && c != '-' && c != '_' && c != '.')
208 return false;
209 }
210 return true;
211}
212
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700213string OmahaRequestBuilderXml::GetApp(const OmahaAppData& app_data) const {
214 string app_body = GetAppBody(app_data.skip_update);
Amin Hassani7fca2862019-03-28 16:09:22 -0700215 string app_versions;
216
217 // If we are downgrading to a more stable channel and we are allowed to do
218 // powerwash, then pass 0.0.0.0 as the version. This is needed to get the
219 // highest-versioned payload on the destination channel.
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700220 if (params_->ShouldPowerwash()) {
Amin Hassani7fca2862019-03-28 16:09:22 -0700221 LOG(INFO) << "Passing OS version as 0.0.0.0 as we are set to powerwash "
222 << "on downgrading to the version in the more stable channel";
Jae Hoon Kim5fc00a22020-01-08 20:15:42 -0800223 app_versions = "version=\"" + string(kNoVersion) + "\" from_version=\"" +
224 XmlEncodeWithDefault(app_data.version, kNoVersion) + "\" ";
Amin Hassani7fca2862019-03-28 16:09:22 -0700225 } else {
226 app_versions = "version=\"" +
Jae Hoon Kim5fc00a22020-01-08 20:15:42 -0800227 XmlEncodeWithDefault(app_data.version, kNoVersion) + "\" ";
Amin Hassani7fca2862019-03-28 16:09:22 -0700228 }
229
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700230 string download_channel = params_->download_channel();
Amin Hassani7fca2862019-03-28 16:09:22 -0700231 string app_channels =
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700232 "track=\"" + XmlEncodeWithDefault(download_channel) + "\" ";
233 if (params_->current_channel() != download_channel) {
Amin Hassani7fca2862019-03-28 16:09:22 -0700234 app_channels += "from_track=\"" +
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700235 XmlEncodeWithDefault(params_->current_channel()) + "\" ";
Amin Hassani7fca2862019-03-28 16:09:22 -0700236 }
237
Jae Hoon Kim5fc00a22020-01-08 20:15:42 -0800238 string delta_okay_str =
239 params_->delta_okay() && !params_->is_install() ? "true" : "false";
Amin Hassani7fca2862019-03-28 16:09:22 -0700240
241 // If install_date_days is not set (e.g. its value is -1 ), don't
242 // include the attribute.
243 string install_date_in_days_str = "";
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700244 if (install_date_in_days_ >= 0) {
Amin Hassani7fca2862019-03-28 16:09:22 -0700245 install_date_in_days_str =
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700246 base::StringPrintf("installdate=\"%d\" ", install_date_in_days_);
Amin Hassani7fca2862019-03-28 16:09:22 -0700247 }
248
249 string app_cohort_args;
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700250 app_cohort_args += GetCohortArg("cohort", kPrefsOmahaCohort);
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700251 app_cohort_args += GetCohortArg("cohortname", kPrefsOmahaCohortName);
Amin Hassani7fca2862019-03-28 16:09:22 -0700252
Askar Aitzhan18fff842019-06-21 23:24:37 +0200253 // Policy provided value overrides pref.
254 string autoupdate_token = params_->autoupdate_token();
255 app_cohort_args += GetCohortArg("cohorthint",
256 kPrefsOmahaCohortHint,
257 autoupdate_token /* override_value */);
258
Amin Hassani7fca2862019-03-28 16:09:22 -0700259 string fingerprint_arg;
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700260 if (!params_->os_build_fingerprint().empty()) {
Amin Hassani7fca2862019-03-28 16:09:22 -0700261 fingerprint_arg = "fingerprint=\"" +
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700262 XmlEncodeWithDefault(params_->os_build_fingerprint()) +
Amin Hassani7fca2862019-03-28 16:09:22 -0700263 "\" ";
264 }
265
266 string buildtype_arg;
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700267 if (!params_->os_build_type().empty()) {
Amin Hassani7fca2862019-03-28 16:09:22 -0700268 buildtype_arg = "os_build_type=\"" +
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700269 XmlEncodeWithDefault(params_->os_build_type()) + "\" ";
Amin Hassani7fca2862019-03-28 16:09:22 -0700270 }
271
272 string product_components_args;
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700273 if (!params_->ShouldPowerwash() && !app_data.product_components.empty()) {
Amin Hassani7fca2862019-03-28 16:09:22 -0700274 brillo::KeyValueStore store;
275 if (store.LoadFromString(app_data.product_components)) {
276 for (const string& key : store.GetKeys()) {
277 if (!IsValidComponentID(key)) {
278 LOG(ERROR) << "Invalid component id: " << key;
279 continue;
280 }
281 string version;
282 if (!store.GetString(key, &version)) {
283 LOG(ERROR) << "Failed to get version for " << key
284 << " in product_components.";
285 continue;
286 }
287 product_components_args +=
288 base::StringPrintf("_%s.version=\"%s\" ",
289 key.c_str(),
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700290 XmlEncodeWithDefault(version).c_str());
Amin Hassani7fca2862019-03-28 16:09:22 -0700291 }
292 } else {
293 LOG(ERROR) << "Failed to parse product_components:\n"
294 << app_data.product_components;
295 }
296 }
297
Matt Ziegelbaumaa8e1a42019-05-09 21:41:58 -0400298 string requisition_arg;
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700299 if (!params_->device_requisition().empty()) {
Matt Ziegelbaumaa8e1a42019-05-09 21:41:58 -0400300 requisition_arg = "requisition=\"" +
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700301 XmlEncodeWithDefault(params_->device_requisition()) +
Matt Ziegelbaumaa8e1a42019-05-09 21:41:58 -0400302 "\" ";
303 }
304
Amin Hassani7fca2862019-03-28 16:09:22 -0700305 // clang-format off
306 string app_xml = " <app "
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700307 "appid=\"" + XmlEncodeWithDefault(app_data.id) + "\" " +
Amin Hassani7fca2862019-03-28 16:09:22 -0700308 app_cohort_args +
309 app_versions +
310 app_channels +
311 product_components_args +
312 fingerprint_arg +
313 buildtype_arg +
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700314 "board=\"" + XmlEncodeWithDefault(params_->os_board()) + "\" " +
315 "hardware_class=\"" + XmlEncodeWithDefault(params_->hwid()) + "\" " +
Jae Hoon Kim37d15372020-01-08 18:11:26 -0800316 "delta_okay=\"" + delta_okay_str + "\" " +
317 install_date_in_days_str +
318
319 // DLC excluded for installs and updates.
320 (app_data.is_dlc ? "" :
321 "lang=\"" + XmlEncodeWithDefault(params_->app_lang(), "en-US") + "\" " +
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700322 "fw_version=\"" + XmlEncodeWithDefault(params_->fw_version()) + "\" " +
323 "ec_version=\"" + XmlEncodeWithDefault(params_->ec_version()) + "\" " +
Jae Hoon Kim37d15372020-01-08 18:11:26 -0800324 requisition_arg) +
325
Amin Hassani7fca2862019-03-28 16:09:22 -0700326 ">\n" +
327 app_body +
328 " </app>\n";
329 // clang-format on
330 return app_xml;
331}
332
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700333string OmahaRequestBuilderXml::GetOs() const {
Amin Hassani7fca2862019-03-28 16:09:22 -0700334 string os_xml =
335 " <os "
336 "version=\"" +
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700337 XmlEncodeWithDefault(params_->os_version()) + "\" " + "platform=\"" +
338 XmlEncodeWithDefault(params_->os_platform()) + "\" " + "sp=\"" +
339 XmlEncodeWithDefault(params_->os_sp()) +
Amin Hassani7fca2862019-03-28 16:09:22 -0700340 "\">"
341 "</os>\n";
342 return os_xml;
343}
344
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700345string OmahaRequestBuilderXml::GetRequest() const {
346 string os_xml = GetOs();
347 string app_xml = GetApps();
Amin Hassani7fca2862019-03-28 16:09:22 -0700348
349 string request_xml = base::StringPrintf(
350 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
Jae Hoon Kimedb65502019-06-14 11:52:17 -0700351 "<request requestid=\"%s\" sessionid=\"%s\""
Jae Hoon Kim6ada5912019-06-14 10:11:34 -0700352 " protocol=\"3.0\" updater=\"%s\" updaterversion=\"%s\""
Amin Hassani7fca2862019-03-28 16:09:22 -0700353 " installsource=\"%s\" ismachine=\"1\">\n%s%s</request>\n",
Jae Hoon Kim6ada5912019-06-14 10:11:34 -0700354 base::GenerateGUID().c_str() /* requestid */,
Jae Hoon Kimedb65502019-06-14 11:52:17 -0700355 session_id_.c_str(),
Amin Hassani7fca2862019-03-28 16:09:22 -0700356 constants::kOmahaUpdaterID,
357 kOmahaUpdaterVersion,
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700358 params_->interactive() ? "ondemandupdate" : "scheduler",
Amin Hassani7fca2862019-03-28 16:09:22 -0700359 os_xml.c_str(),
360 app_xml.c_str());
361
362 return request_xml;
363}
364
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700365string OmahaRequestBuilderXml::GetApps() const {
366 string app_xml = "";
367 OmahaAppData product_app = {
368 .id = params_->GetAppId(),
369 .version = params_->app_version(),
370 .product_components = params_->product_components(),
371 // Skips updatecheck for platform app in case of an install operation.
Jae Hoon Kim37d15372020-01-08 18:11:26 -0800372 .skip_update = params_->is_install(),
373 .is_dlc = false};
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700374 app_xml += GetApp(product_app);
375 if (!params_->system_app_id().empty()) {
376 OmahaAppData system_app = {.id = params_->system_app_id(),
377 .version = params_->system_version(),
Jae Hoon Kim37d15372020-01-08 18:11:26 -0800378 .skip_update = false,
379 .is_dlc = false};
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700380 app_xml += GetApp(system_app);
381 }
382 // Create APP ID according to |dlc_module_id| (sticking the current AppID to
383 // the DLC module ID with an underscode).
384 for (const auto& dlc_module_id : params_->dlc_module_ids()) {
385 OmahaAppData dlc_module_app = {
386 .id = params_->GetAppId() + "_" + dlc_module_id,
Jae Hoon Kim5fc00a22020-01-08 20:15:42 -0800387 .version = params_->is_install() ? kNoVersion : params_->app_version(),
Jae Hoon Kim37d15372020-01-08 18:11:26 -0800388 .skip_update = false,
389 .is_dlc = true};
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700390 app_xml += GetApp(dlc_module_app);
391 }
392 return app_xml;
393}
394
Amin Hassani7fca2862019-03-28 16:09:22 -0700395} // namespace chromeos_update_engine