blob: ad7c42492d43b94613b9864578216c71d4555050 [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
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -070083string OmahaRequestBuilderXml::GetPing() const {
84 // Returns an XML ping element attribute assignment with attribute
85 // |name| and value |ping_days| if |ping_days| has a value that needs
86 // to be sent, or an empty string otherwise.
87 auto GetPingAttribute = [](const char* name, int ping_days) -> string {
88 if (ping_days > 0 || ping_days == kNeverPinged)
89 return base::StringPrintf(" %s=\"%d\"", name, ping_days);
90 return "";
91 };
Amin Hassani7fca2862019-03-28 16:09:22 -070092
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -070093 string ping_active = GetPingAttribute("a", ping_active_days_);
94 string ping_roll_call = GetPingAttribute("r", ping_roll_call_days_);
Amin Hassani7fca2862019-03-28 16:09:22 -070095 if (!ping_active.empty() || !ping_roll_call.empty()) {
96 return base::StringPrintf(" <ping active=\"1\"%s%s></ping>\n",
97 ping_active.c_str(),
98 ping_roll_call.c_str());
99 }
100 return "";
101}
102
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700103string OmahaRequestBuilderXml::GetAppBody(bool skip_updatecheck) const {
Amin Hassani7fca2862019-03-28 16:09:22 -0700104 string app_body;
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700105 if (event_ == nullptr) {
106 if (include_ping_)
107 app_body = GetPing();
108 if (!ping_only_) {
Amin Hassani7fca2862019-03-28 16:09:22 -0700109 if (!skip_updatecheck) {
110 app_body += " <updatecheck";
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700111 if (!params_->target_version_prefix().empty()) {
Amin Hassani7fca2862019-03-28 16:09:22 -0700112 app_body += base::StringPrintf(
113 " targetversionprefix=\"%s\"",
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700114 XmlEncodeWithDefault(params_->target_version_prefix()).c_str());
Amin Hassani7fca2862019-03-28 16:09:22 -0700115 // Rollback requires target_version_prefix set.
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700116 if (params_->rollback_allowed()) {
Amin Hassani7fca2862019-03-28 16:09:22 -0700117 app_body += " rollback_allowed=\"true\"";
118 }
119 }
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700120 string autoupdate_token = params_->autoupdate_token();
Askar Aitzhan570ca872019-04-24 11:16:12 +0200121 if (!autoupdate_token.empty()) {
122 app_body += base::StringPrintf(
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700123 " token=\"%s\"", XmlEncodeWithDefault(autoupdate_token).c_str());
Askar Aitzhan570ca872019-04-24 11:16:12 +0200124 }
125
Amin Hassani7fca2862019-03-28 16:09:22 -0700126 app_body += "></updatecheck>\n";
127 }
128
129 // If this is the first update check after a reboot following a previous
130 // update, generate an event containing the previous version number. If
131 // the previous version preference file doesn't exist the event is still
132 // generated with a previous version of 0.0.0.0 -- this is relevant for
133 // older clients or new installs. The previous version event is not sent
134 // for ping-only requests because they come before the client has
135 // rebooted. The previous version event is also not sent if it was already
136 // sent for this new version with a previous updatecheck.
137 string prev_version;
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700138 if (!prefs_->GetString(kPrefsPreviousVersion, &prev_version)) {
Amin Hassani7fca2862019-03-28 16:09:22 -0700139 prev_version = "0.0.0.0";
140 }
141 // We only store a non-empty previous version value after a successful
142 // update in the previous boot. After reporting it back to the server,
143 // we clear the previous version value so it doesn't get reported again.
144 if (!prev_version.empty()) {
145 app_body += base::StringPrintf(
146 " <event eventtype=\"%d\" eventresult=\"%d\" "
147 "previousversion=\"%s\"></event>\n",
148 OmahaEvent::kTypeRebootedAfterUpdate,
149 OmahaEvent::kResultSuccess,
150 XmlEncodeWithDefault(prev_version, "0.0.0.0").c_str());
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700151 LOG_IF(WARNING, !prefs_->SetString(kPrefsPreviousVersion, ""))
Amin Hassani7fca2862019-03-28 16:09:22 -0700152 << "Unable to reset the previous version.";
153 }
154 }
155 } else {
156 // The error code is an optional attribute so append it only if the result
157 // is not success.
158 string error_code;
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700159 if (event_->result != OmahaEvent::kResultSuccess) {
Amin Hassani7fca2862019-03-28 16:09:22 -0700160 error_code = base::StringPrintf(" errorcode=\"%d\"",
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700161 static_cast<int>(event_->error_code));
Amin Hassani7fca2862019-03-28 16:09:22 -0700162 }
163 app_body = base::StringPrintf(
164 " <event eventtype=\"%d\" eventresult=\"%d\"%s></event>\n",
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700165 event_->type,
166 event_->result,
Amin Hassani7fca2862019-03-28 16:09:22 -0700167 error_code.c_str());
168 }
169
170 return app_body;
171}
172
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700173string OmahaRequestBuilderXml::GetCohortArg(const string arg_name,
174 const string prefs_key) const {
Amin Hassani7fca2862019-03-28 16:09:22 -0700175 // There's nothing wrong with not having a given cohort setting, so we check
176 // existence first to avoid the warning log message.
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700177 if (!prefs_->Exists(prefs_key))
Amin Hassani7fca2862019-03-28 16:09:22 -0700178 return "";
179 string cohort_value;
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700180 if (!prefs_->GetString(prefs_key, &cohort_value) || cohort_value.empty())
Amin Hassani7fca2862019-03-28 16:09:22 -0700181 return "";
182 // This is a sanity check to avoid sending a huge XML file back to Ohama due
183 // to a compromised stateful partition making the update check fail in low
184 // network environments envent after a reboot.
185 if (cohort_value.size() > 1024) {
186 LOG(WARNING) << "The omaha cohort setting " << arg_name
187 << " has a too big value, which must be an error or an "
188 "attacker trying to inhibit updates.";
189 return "";
190 }
191
192 string escaped_xml_value;
193 if (!XmlEncode(cohort_value, &escaped_xml_value)) {
194 LOG(WARNING) << "The omaha cohort setting " << arg_name
195 << " is ASCII-7 invalid, ignoring it.";
196 return "";
197 }
198
199 return base::StringPrintf(
200 "%s=\"%s\" ", arg_name.c_str(), escaped_xml_value.c_str());
201}
202
203bool IsValidComponentID(const string& id) {
204 for (char c : id) {
205 if (!isalnum(c) && c != '-' && c != '_' && c != '.')
206 return false;
207 }
208 return true;
209}
210
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700211string OmahaRequestBuilderXml::GetApp(const OmahaAppData& app_data) const {
212 string app_body = GetAppBody(app_data.skip_update);
Amin Hassani7fca2862019-03-28 16:09:22 -0700213 string app_versions;
214
215 // If we are downgrading to a more stable channel and we are allowed to do
216 // powerwash, then pass 0.0.0.0 as the version. This is needed to get the
217 // highest-versioned payload on the destination channel.
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700218 if (params_->ShouldPowerwash()) {
Amin Hassani7fca2862019-03-28 16:09:22 -0700219 LOG(INFO) << "Passing OS version as 0.0.0.0 as we are set to powerwash "
220 << "on downgrading to the version in the more stable channel";
221 app_versions = "version=\"0.0.0.0\" from_version=\"" +
222 XmlEncodeWithDefault(app_data.version, "0.0.0.0") + "\" ";
223 } else {
224 app_versions = "version=\"" +
225 XmlEncodeWithDefault(app_data.version, "0.0.0.0") + "\" ";
226 }
227
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700228 string download_channel = params_->download_channel();
Amin Hassani7fca2862019-03-28 16:09:22 -0700229 string app_channels =
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700230 "track=\"" + XmlEncodeWithDefault(download_channel) + "\" ";
231 if (params_->current_channel() != download_channel) {
Amin Hassani7fca2862019-03-28 16:09:22 -0700232 app_channels += "from_track=\"" +
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700233 XmlEncodeWithDefault(params_->current_channel()) + "\" ";
Amin Hassani7fca2862019-03-28 16:09:22 -0700234 }
235
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700236 string delta_okay_str = params_->delta_okay() ? "true" : "false";
Amin Hassani7fca2862019-03-28 16:09:22 -0700237
238 // If install_date_days is not set (e.g. its value is -1 ), don't
239 // include the attribute.
240 string install_date_in_days_str = "";
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700241 if (install_date_in_days_ >= 0) {
Amin Hassani7fca2862019-03-28 16:09:22 -0700242 install_date_in_days_str =
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700243 base::StringPrintf("installdate=\"%d\" ", install_date_in_days_);
Amin Hassani7fca2862019-03-28 16:09:22 -0700244 }
245
246 string app_cohort_args;
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700247 app_cohort_args += GetCohortArg("cohort", kPrefsOmahaCohort);
248 app_cohort_args += GetCohortArg("cohorthint", kPrefsOmahaCohortHint);
249 app_cohort_args += GetCohortArg("cohortname", kPrefsOmahaCohortName);
Amin Hassani7fca2862019-03-28 16:09:22 -0700250
251 string fingerprint_arg;
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700252 if (!params_->os_build_fingerprint().empty()) {
Amin Hassani7fca2862019-03-28 16:09:22 -0700253 fingerprint_arg = "fingerprint=\"" +
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700254 XmlEncodeWithDefault(params_->os_build_fingerprint()) +
Amin Hassani7fca2862019-03-28 16:09:22 -0700255 "\" ";
256 }
257
258 string buildtype_arg;
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700259 if (!params_->os_build_type().empty()) {
Amin Hassani7fca2862019-03-28 16:09:22 -0700260 buildtype_arg = "os_build_type=\"" +
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700261 XmlEncodeWithDefault(params_->os_build_type()) + "\" ";
Amin Hassani7fca2862019-03-28 16:09:22 -0700262 }
263
264 string product_components_args;
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700265 if (!params_->ShouldPowerwash() && !app_data.product_components.empty()) {
Amin Hassani7fca2862019-03-28 16:09:22 -0700266 brillo::KeyValueStore store;
267 if (store.LoadFromString(app_data.product_components)) {
268 for (const string& key : store.GetKeys()) {
269 if (!IsValidComponentID(key)) {
270 LOG(ERROR) << "Invalid component id: " << key;
271 continue;
272 }
273 string version;
274 if (!store.GetString(key, &version)) {
275 LOG(ERROR) << "Failed to get version for " << key
276 << " in product_components.";
277 continue;
278 }
279 product_components_args +=
280 base::StringPrintf("_%s.version=\"%s\" ",
281 key.c_str(),
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700282 XmlEncodeWithDefault(version).c_str());
Amin Hassani7fca2862019-03-28 16:09:22 -0700283 }
284 } else {
285 LOG(ERROR) << "Failed to parse product_components:\n"
286 << app_data.product_components;
287 }
288 }
289
Matt Ziegelbaumaa8e1a42019-05-09 21:41:58 -0400290 string requisition_arg;
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700291 if (!params_->device_requisition().empty()) {
Matt Ziegelbaumaa8e1a42019-05-09 21:41:58 -0400292 requisition_arg = "requisition=\"" +
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700293 XmlEncodeWithDefault(params_->device_requisition()) +
Matt Ziegelbaumaa8e1a42019-05-09 21:41:58 -0400294 "\" ";
295 }
296
Amin Hassani7fca2862019-03-28 16:09:22 -0700297 // clang-format off
298 string app_xml = " <app "
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700299 "appid=\"" + XmlEncodeWithDefault(app_data.id) + "\" " +
Amin Hassani7fca2862019-03-28 16:09:22 -0700300 app_cohort_args +
301 app_versions +
302 app_channels +
303 product_components_args +
304 fingerprint_arg +
305 buildtype_arg +
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700306 "lang=\"" + XmlEncodeWithDefault(params_->app_lang(), "en-US") + "\" " +
307 "board=\"" + XmlEncodeWithDefault(params_->os_board()) + "\" " +
308 "hardware_class=\"" + XmlEncodeWithDefault(params_->hwid()) + "\" " +
Amin Hassani7fca2862019-03-28 16:09:22 -0700309 "delta_okay=\"" + delta_okay_str + "\" "
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700310 "fw_version=\"" + XmlEncodeWithDefault(params_->fw_version()) + "\" " +
311 "ec_version=\"" + XmlEncodeWithDefault(params_->ec_version()) + "\" " +
Amin Hassani7fca2862019-03-28 16:09:22 -0700312 install_date_in_days_str +
Matt Ziegelbaumaa8e1a42019-05-09 21:41:58 -0400313 requisition_arg +
Amin Hassani7fca2862019-03-28 16:09:22 -0700314 ">\n" +
315 app_body +
316 " </app>\n";
317 // clang-format on
318 return app_xml;
319}
320
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700321string OmahaRequestBuilderXml::GetOs() const {
Amin Hassani7fca2862019-03-28 16:09:22 -0700322 string os_xml =
323 " <os "
324 "version=\"" +
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700325 XmlEncodeWithDefault(params_->os_version()) + "\" " + "platform=\"" +
326 XmlEncodeWithDefault(params_->os_platform()) + "\" " + "sp=\"" +
327 XmlEncodeWithDefault(params_->os_sp()) +
Amin Hassani7fca2862019-03-28 16:09:22 -0700328 "\">"
329 "</os>\n";
330 return os_xml;
331}
332
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700333string OmahaRequestBuilderXml::GetRequest() const {
334 string os_xml = GetOs();
335 string app_xml = GetApps();
Amin Hassani7fca2862019-03-28 16:09:22 -0700336
337 string request_xml = base::StringPrintf(
338 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
339 "<request protocol=\"3.0\" updater=\"%s\" updaterversion=\"%s\""
340 " installsource=\"%s\" ismachine=\"1\">\n%s%s</request>\n",
341 constants::kOmahaUpdaterID,
342 kOmahaUpdaterVersion,
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700343 params_->interactive() ? "ondemandupdate" : "scheduler",
Amin Hassani7fca2862019-03-28 16:09:22 -0700344 os_xml.c_str(),
345 app_xml.c_str());
346
347 return request_xml;
348}
349
Jae Hoon Kimb7ee3872019-06-06 14:59:03 -0700350string OmahaRequestBuilderXml::GetApps() const {
351 string app_xml = "";
352 OmahaAppData product_app = {
353 .id = params_->GetAppId(),
354 .version = params_->app_version(),
355 .product_components = params_->product_components(),
356 // Skips updatecheck for platform app in case of an install operation.
357 .skip_update = params_->is_install()};
358 app_xml += GetApp(product_app);
359 if (!params_->system_app_id().empty()) {
360 OmahaAppData system_app = {.id = params_->system_app_id(),
361 .version = params_->system_version(),
362 .skip_update = false};
363 app_xml += GetApp(system_app);
364 }
365 // Create APP ID according to |dlc_module_id| (sticking the current AppID to
366 // the DLC module ID with an underscode).
367 for (const auto& dlc_module_id : params_->dlc_module_ids()) {
368 OmahaAppData dlc_module_app = {
369 .id = params_->GetAppId() + "_" + dlc_module_id,
370 .version = params_->app_version(),
371 .skip_update = false};
372 app_xml += GetApp(dlc_module_app);
373 }
374 return app_xml;
375}
376
Amin Hassani7fca2862019-03-28 16:09:22 -0700377} // namespace chromeos_update_engine