blob: 3e4a335908d184d360233eacd7ed7e409e5e3ab4 [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;
40
41bool XmlEncode(const string& input, string* output) {
42 if (std::find_if(input.begin(), input.end(), [](const char c) {
43 return c & 0x80;
44 }) != input.end()) {
45 LOG(WARNING) << "Invalid ASCII-7 string passed to the XML encoder:";
46 utils::HexDumpString(input);
47 return false;
48 }
49 output->clear();
50 // We need at least input.size() space in the output, but the code below will
51 // handle it if we need more.
52 output->reserve(input.size());
53 for (char c : input) {
54 switch (c) {
55 case '\"':
56 output->append("&quot;");
57 break;
58 case '\'':
59 output->append("&apos;");
60 break;
61 case '&':
62 output->append("&amp;");
63 break;
64 case '<':
65 output->append("&lt;");
66 break;
67 case '>':
68 output->append("&gt;");
69 break;
70 default:
71 output->push_back(c);
72 }
73 }
74 return true;
75}
76
77string XmlEncodeWithDefault(const string& input, const string& default_value) {
78 string output;
79 if (XmlEncode(input, &output))
80 return output;
81 return default_value;
82}
83
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -070084string OmahaRequestBuilderXml::GetPing() const {
85 // Returns an XML ping element attribute assignment with attribute
86 // |name| and value |ping_days| if |ping_days| has a value that needs
87 // to be sent, or an empty string otherwise.
88 auto GetPingAttribute = [](const char* name, int ping_days) -> string {
89 if (ping_days > 0 || ping_days == kNeverPinged)
90 return base::StringPrintf(" %s=\"%d\"", name, ping_days);
91 return "";
92 };
Amin Hassani7fca2862019-03-28 16:09:22 -070093
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -070094 string ping_active = GetPingAttribute("a", ping_active_days_);
95 string ping_roll_call = GetPingAttribute("r", ping_roll_call_days_);
Amin Hassani7fca2862019-03-28 16:09:22 -070096 if (!ping_active.empty() || !ping_roll_call.empty()) {
97 return base::StringPrintf(" <ping active=\"1\"%s%s></ping>\n",
98 ping_active.c_str(),
99 ping_roll_call.c_str());
100 }
101 return "";
102}
103
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700104string OmahaRequestBuilderXml::GetAppBody(bool skip_updatecheck) const {
Amin Hassani7fca2862019-03-28 16:09:22 -0700105 string app_body;
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700106 if (event_ == nullptr) {
107 if (include_ping_)
108 app_body = GetPing();
109 if (!ping_only_) {
Amin Hassani7fca2862019-03-28 16:09:22 -0700110 if (!skip_updatecheck) {
111 app_body += " <updatecheck";
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700112 if (!params_->target_version_prefix().empty()) {
Amin Hassani7fca2862019-03-28 16:09:22 -0700113 app_body += base::StringPrintf(
114 " targetversionprefix=\"%s\"",
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700115 XmlEncodeWithDefault(params_->target_version_prefix()).c_str());
Amin Hassani7fca2862019-03-28 16:09:22 -0700116 // Rollback requires target_version_prefix set.
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700117 if (params_->rollback_allowed()) {
Amin Hassani7fca2862019-03-28 16:09:22 -0700118 app_body += " rollback_allowed=\"true\"";
119 }
120 }
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700121 string autoupdate_token = params_->autoupdate_token();
Askar Aitzhan570ca872019-04-24 11:16:12 +0200122 if (!autoupdate_token.empty()) {
123 app_body += base::StringPrintf(
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700124 " token=\"%s\"", XmlEncodeWithDefault(autoupdate_token).c_str());
Askar Aitzhan570ca872019-04-24 11:16:12 +0200125 }
126
Amin Hassani7fca2862019-03-28 16:09:22 -0700127 app_body += "></updatecheck>\n";
128 }
129
130 // If this is the first update check after a reboot following a previous
131 // update, generate an event containing the previous version number. If
132 // the previous version preference file doesn't exist the event is still
133 // generated with a previous version of 0.0.0.0 -- this is relevant for
134 // older clients or new installs. The previous version event is not sent
135 // for ping-only requests because they come before the client has
136 // rebooted. The previous version event is also not sent if it was already
137 // sent for this new version with a previous updatecheck.
138 string prev_version;
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700139 if (!prefs_->GetString(kPrefsPreviousVersion, &prev_version)) {
Amin Hassani7fca2862019-03-28 16:09:22 -0700140 prev_version = "0.0.0.0";
141 }
142 // We only store a non-empty previous version value after a successful
143 // update in the previous boot. After reporting it back to the server,
144 // we clear the previous version value so it doesn't get reported again.
145 if (!prev_version.empty()) {
146 app_body += base::StringPrintf(
147 " <event eventtype=\"%d\" eventresult=\"%d\" "
148 "previousversion=\"%s\"></event>\n",
149 OmahaEvent::kTypeRebootedAfterUpdate,
150 OmahaEvent::kResultSuccess,
151 XmlEncodeWithDefault(prev_version, "0.0.0.0").c_str());
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700152 LOG_IF(WARNING, !prefs_->SetString(kPrefsPreviousVersion, ""))
Amin Hassani7fca2862019-03-28 16:09:22 -0700153 << "Unable to reset the previous version.";
154 }
155 }
156 } else {
157 // The error code is an optional attribute so append it only if the result
158 // is not success.
159 string error_code;
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700160 if (event_->result != OmahaEvent::kResultSuccess) {
Amin Hassani7fca2862019-03-28 16:09:22 -0700161 error_code = base::StringPrintf(" errorcode=\"%d\"",
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700162 static_cast<int>(event_->error_code));
Amin Hassani7fca2862019-03-28 16:09:22 -0700163 }
164 app_body = base::StringPrintf(
165 " <event eventtype=\"%d\" eventresult=\"%d\"%s></event>\n",
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700166 event_->type,
167 event_->result,
Amin Hassani7fca2862019-03-28 16:09:22 -0700168 error_code.c_str());
169 }
170
171 return app_body;
172}
173
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700174string OmahaRequestBuilderXml::GetCohortArg(const string arg_name,
175 const string prefs_key) const {
Amin Hassani7fca2862019-03-28 16:09:22 -0700176 // There's nothing wrong with not having a given cohort setting, so we check
177 // existence first to avoid the warning log message.
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700178 if (!prefs_->Exists(prefs_key))
Amin Hassani7fca2862019-03-28 16:09:22 -0700179 return "";
180 string cohort_value;
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700181 if (!prefs_->GetString(prefs_key, &cohort_value) || cohort_value.empty())
Amin Hassani7fca2862019-03-28 16:09:22 -0700182 return "";
183 // This is a sanity check to avoid sending a huge XML file back to Ohama due
184 // to a compromised stateful partition making the update check fail in low
185 // network environments envent after a reboot.
186 if (cohort_value.size() > 1024) {
187 LOG(WARNING) << "The omaha cohort setting " << arg_name
188 << " has a too big value, which must be an error or an "
189 "attacker trying to inhibit updates.";
190 return "";
191 }
192
193 string escaped_xml_value;
194 if (!XmlEncode(cohort_value, &escaped_xml_value)) {
195 LOG(WARNING) << "The omaha cohort setting " << arg_name
196 << " is ASCII-7 invalid, ignoring it.";
197 return "";
198 }
199
200 return base::StringPrintf(
201 "%s=\"%s\" ", arg_name.c_str(), escaped_xml_value.c_str());
202}
203
204bool IsValidComponentID(const string& id) {
205 for (char c : id) {
206 if (!isalnum(c) && c != '-' && c != '_' && c != '.')
207 return false;
208 }
209 return true;
210}
211
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700212string OmahaRequestBuilderXml::GetApp(const OmahaAppData& app_data) const {
213 string app_body = GetAppBody(app_data.skip_update);
Amin Hassani7fca2862019-03-28 16:09:22 -0700214 string app_versions;
215
216 // If we are downgrading to a more stable channel and we are allowed to do
217 // powerwash, then pass 0.0.0.0 as the version. This is needed to get the
218 // highest-versioned payload on the destination channel.
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700219 if (params_->ShouldPowerwash()) {
Amin Hassani7fca2862019-03-28 16:09:22 -0700220 LOG(INFO) << "Passing OS version as 0.0.0.0 as we are set to powerwash "
221 << "on downgrading to the version in the more stable channel";
222 app_versions = "version=\"0.0.0.0\" from_version=\"" +
223 XmlEncodeWithDefault(app_data.version, "0.0.0.0") + "\" ";
224 } else {
225 app_versions = "version=\"" +
226 XmlEncodeWithDefault(app_data.version, "0.0.0.0") + "\" ";
227 }
228
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700229 string download_channel = params_->download_channel();
Amin Hassani7fca2862019-03-28 16:09:22 -0700230 string app_channels =
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700231 "track=\"" + XmlEncodeWithDefault(download_channel) + "\" ";
232 if (params_->current_channel() != download_channel) {
Amin Hassani7fca2862019-03-28 16:09:22 -0700233 app_channels += "from_track=\"" +
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700234 XmlEncodeWithDefault(params_->current_channel()) + "\" ";
Amin Hassani7fca2862019-03-28 16:09:22 -0700235 }
236
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700237 string delta_okay_str = params_->delta_okay() ? "true" : "false";
Amin Hassani7fca2862019-03-28 16:09:22 -0700238
239 // If install_date_days is not set (e.g. its value is -1 ), don't
240 // include the attribute.
241 string install_date_in_days_str = "";
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700242 if (install_date_in_days_ >= 0) {
Amin Hassani7fca2862019-03-28 16:09:22 -0700243 install_date_in_days_str =
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700244 base::StringPrintf("installdate=\"%d\" ", install_date_in_days_);
Amin Hassani7fca2862019-03-28 16:09:22 -0700245 }
246
247 string app_cohort_args;
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700248 app_cohort_args += GetCohortArg("cohort", kPrefsOmahaCohort);
249 app_cohort_args += GetCohortArg("cohorthint", kPrefsOmahaCohortHint);
250 app_cohort_args += GetCohortArg("cohortname", kPrefsOmahaCohortName);
Amin Hassani7fca2862019-03-28 16:09:22 -0700251
252 string fingerprint_arg;
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700253 if (!params_->os_build_fingerprint().empty()) {
Amin Hassani7fca2862019-03-28 16:09:22 -0700254 fingerprint_arg = "fingerprint=\"" +
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700255 XmlEncodeWithDefault(params_->os_build_fingerprint()) +
Amin Hassani7fca2862019-03-28 16:09:22 -0700256 "\" ";
257 }
258
259 string buildtype_arg;
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700260 if (!params_->os_build_type().empty()) {
Amin Hassani7fca2862019-03-28 16:09:22 -0700261 buildtype_arg = "os_build_type=\"" +
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700262 XmlEncodeWithDefault(params_->os_build_type()) + "\" ";
Amin Hassani7fca2862019-03-28 16:09:22 -0700263 }
264
265 string product_components_args;
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700266 if (!params_->ShouldPowerwash() && !app_data.product_components.empty()) {
Amin Hassani7fca2862019-03-28 16:09:22 -0700267 brillo::KeyValueStore store;
268 if (store.LoadFromString(app_data.product_components)) {
269 for (const string& key : store.GetKeys()) {
270 if (!IsValidComponentID(key)) {
271 LOG(ERROR) << "Invalid component id: " << key;
272 continue;
273 }
274 string version;
275 if (!store.GetString(key, &version)) {
276 LOG(ERROR) << "Failed to get version for " << key
277 << " in product_components.";
278 continue;
279 }
280 product_components_args +=
281 base::StringPrintf("_%s.version=\"%s\" ",
282 key.c_str(),
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700283 XmlEncodeWithDefault(version).c_str());
Amin Hassani7fca2862019-03-28 16:09:22 -0700284 }
285 } else {
286 LOG(ERROR) << "Failed to parse product_components:\n"
287 << app_data.product_components;
288 }
289 }
290
Matt Ziegelbaumaa8e1a42019-05-09 21:41:58 -0400291 string requisition_arg;
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700292 if (!params_->device_requisition().empty()) {
Matt Ziegelbaumaa8e1a42019-05-09 21:41:58 -0400293 requisition_arg = "requisition=\"" +
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700294 XmlEncodeWithDefault(params_->device_requisition()) +
Matt Ziegelbaumaa8e1a42019-05-09 21:41:58 -0400295 "\" ";
296 }
297
Amin Hassani7fca2862019-03-28 16:09:22 -0700298 // clang-format off
299 string app_xml = " <app "
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700300 "appid=\"" + XmlEncodeWithDefault(app_data.id) + "\" " +
Amin Hassani7fca2862019-03-28 16:09:22 -0700301 app_cohort_args +
302 app_versions +
303 app_channels +
304 product_components_args +
305 fingerprint_arg +
306 buildtype_arg +
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700307 "lang=\"" + XmlEncodeWithDefault(params_->app_lang(), "en-US") + "\" " +
308 "board=\"" + XmlEncodeWithDefault(params_->os_board()) + "\" " +
309 "hardware_class=\"" + XmlEncodeWithDefault(params_->hwid()) + "\" " +
Amin Hassani7fca2862019-03-28 16:09:22 -0700310 "delta_okay=\"" + delta_okay_str + "\" "
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700311 "fw_version=\"" + XmlEncodeWithDefault(params_->fw_version()) + "\" " +
312 "ec_version=\"" + XmlEncodeWithDefault(params_->ec_version()) + "\" " +
Amin Hassani7fca2862019-03-28 16:09:22 -0700313 install_date_in_days_str +
Matt Ziegelbaumaa8e1a42019-05-09 21:41:58 -0400314 requisition_arg +
Amin Hassani7fca2862019-03-28 16:09:22 -0700315 ">\n" +
316 app_body +
317 " </app>\n";
318 // clang-format on
319 return app_xml;
320}
321
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700322string OmahaRequestBuilderXml::GetOs() const {
Amin Hassani7fca2862019-03-28 16:09:22 -0700323 string os_xml =
324 " <os "
325 "version=\"" +
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700326 XmlEncodeWithDefault(params_->os_version()) + "\" " + "platform=\"" +
327 XmlEncodeWithDefault(params_->os_platform()) + "\" " + "sp=\"" +
328 XmlEncodeWithDefault(params_->os_sp()) +
Amin Hassani7fca2862019-03-28 16:09:22 -0700329 "\">"
330 "</os>\n";
331 return os_xml;
332}
333
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700334string OmahaRequestBuilderXml::GetRequest() const {
335 string os_xml = GetOs();
336 string app_xml = GetApps();
Amin Hassani7fca2862019-03-28 16:09:22 -0700337
338 string request_xml = base::StringPrintf(
339 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
Jae Hoon Kimedb65502019-06-14 11:52:17 -0700340 "<request requestid=\"%s\" sessionid=\"%s\""
Jae Hoon Kim6ada5912019-06-14 10:11:34 -0700341 " protocol=\"3.0\" updater=\"%s\" updaterversion=\"%s\""
Amin Hassani7fca2862019-03-28 16:09:22 -0700342 " installsource=\"%s\" ismachine=\"1\">\n%s%s</request>\n",
Jae Hoon Kim6ada5912019-06-14 10:11:34 -0700343 base::GenerateGUID().c_str() /* requestid */,
Jae Hoon Kimedb65502019-06-14 11:52:17 -0700344 session_id_.c_str(),
Amin Hassani7fca2862019-03-28 16:09:22 -0700345 constants::kOmahaUpdaterID,
346 kOmahaUpdaterVersion,
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700347 params_->interactive() ? "ondemandupdate" : "scheduler",
Amin Hassani7fca2862019-03-28 16:09:22 -0700348 os_xml.c_str(),
349 app_xml.c_str());
350
351 return request_xml;
352}
353
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700354string OmahaRequestBuilderXml::GetApps() const {
355 string app_xml = "";
356 OmahaAppData product_app = {
357 .id = params_->GetAppId(),
358 .version = params_->app_version(),
359 .product_components = params_->product_components(),
360 // Skips updatecheck for platform app in case of an install operation.
361 .skip_update = params_->is_install()};
362 app_xml += GetApp(product_app);
363 if (!params_->system_app_id().empty()) {
364 OmahaAppData system_app = {.id = params_->system_app_id(),
365 .version = params_->system_version(),
366 .skip_update = false};
367 app_xml += GetApp(system_app);
368 }
369 // Create APP ID according to |dlc_module_id| (sticking the current AppID to
370 // the DLC module ID with an underscode).
371 for (const auto& dlc_module_id : params_->dlc_module_ids()) {
372 OmahaAppData dlc_module_app = {
373 .id = params_->GetAppId() + "_" + dlc_module_id,
374 .version = params_->app_version(),
375 .skip_update = false};
376 app_xml += GetApp(dlc_module_app);
377 }
378 return app_xml;
379}
380
Amin Hassani7fca2862019-03-28 16:09:22 -0700381} // namespace chromeos_update_engine