1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "components/update_client/update_response.h"
9 #include "base/memory/scoped_ptr.h"
10 #include "base/stl_util.h"
11 #include "base/strings/string_number_conversions.h"
12 #include "base/strings/string_util.h"
13 #include "base/strings/stringprintf.h"
14 #include "base/version.h"
15 #include "libxml/tree.h"
16 #include "third_party/libxml/chromium/libxml_utils.h"
18 namespace update_client
{
20 static const char* kExpectedResponseProtocol
= "3.0";
22 UpdateResponse::UpdateResponse() {
24 UpdateResponse::~UpdateResponse() {
27 UpdateResponse::Results::Results() : daystart_elapsed_seconds(kNoDaystart
) {
29 UpdateResponse::Results::~Results() {
32 UpdateResponse::Result::Result() {
35 UpdateResponse::Result::~Result() {
38 UpdateResponse::Result::Manifest::Manifest() {
40 UpdateResponse::Result::Manifest::~Manifest() {
43 UpdateResponse::Result::Manifest::Package::Package() : size(0), sizediff(0) {
45 UpdateResponse::Result::Manifest::Package::~Package() {
48 void UpdateResponse::ParseError(const char* details
, ...) {
50 va_start(args
, details
);
52 if (!errors_
.empty()) {
56 base::StringAppendV(&errors_
, details
, args
);
60 // Checks whether a given node's name matches |expected_name|.
61 static bool TagNameEquals(const xmlNode
* node
, const char* expected_name
) {
62 return 0 == strcmp(expected_name
, reinterpret_cast<const char*>(node
->name
));
65 // Returns child nodes of |root| with name |name|.
66 static std::vector
<xmlNode
*> GetChildren(xmlNode
* root
, const char* name
) {
67 std::vector
<xmlNode
*> result
;
68 for (xmlNode
* child
= root
->children
; child
!= NULL
; child
= child
->next
) {
69 if (!TagNameEquals(child
, name
)) {
72 result
.push_back(child
);
77 // Returns the value of a named attribute, or the empty string.
78 static std::string
GetAttribute(xmlNode
* node
, const char* attribute_name
) {
79 const xmlChar
* name
= reinterpret_cast<const xmlChar
*>(attribute_name
);
80 for (xmlAttr
* attr
= node
->properties
; attr
!= NULL
; attr
= attr
->next
) {
81 if (!xmlStrcmp(attr
->name
, name
) && attr
->children
&&
82 attr
->children
->content
) {
84 reinterpret_cast<const char*>(attr
->children
->content
));
90 // This is used for the xml parser to report errors. This assumes the context
91 // is a pointer to a std::string where the error message should be appended.
92 static void XmlErrorFunc(void* context
, const char* message
, ...) {
94 va_start(args
, message
);
95 std::string
* error
= static_cast<std::string
*>(context
);
96 base::StringAppendV(error
, message
, args
);
100 // Utility class for cleaning up the xml document when leaving a scope.
101 class ScopedXmlDocument
{
103 explicit ScopedXmlDocument(xmlDocPtr document
) : document_(document
) {}
104 ~ScopedXmlDocument() {
106 xmlFreeDoc(document_
);
109 xmlDocPtr
get() { return document_
; }
115 // Parses the <package> tag.
116 bool ParsePackageTag(xmlNode
* package
,
117 UpdateResponse::Result
* result
,
118 std::string
* error
) {
119 UpdateResponse::Result::Manifest::Package p
;
120 p
.name
= GetAttribute(package
, "name");
121 if (p
.name
.empty()) {
122 *error
= "Missing name for package.";
126 p
.namediff
= GetAttribute(package
, "namediff");
128 // package_fingerprint is optional. It identifies the package, preferably
129 // with a modified sha256 hash of the package in hex format.
130 p
.fingerprint
= GetAttribute(package
, "fp");
132 p
.hash_sha256
= GetAttribute(package
, "hash_sha256");
134 if (base::StringToInt(GetAttribute(package
, "size"), &size
)) {
138 p
.hashdiff_sha256
= GetAttribute(package
, "hashdiff_sha256");
140 if (base::StringToInt(GetAttribute(package
, "sizediff"), &sizediff
)) {
141 p
.sizediff
= sizediff
;
144 result
->manifest
.packages
.push_back(p
);
149 // Parses the <manifest> tag.
150 bool ParseManifestTag(xmlNode
* manifest
,
151 UpdateResponse::Result
* result
,
152 std::string
* error
) {
154 result
->manifest
.version
= GetAttribute(manifest
, "version");
155 if (result
->manifest
.version
.empty()) {
156 *error
= "Missing version for manifest.";
159 Version
version(result
->manifest
.version
);
160 if (!version
.IsValid()) {
161 *error
= "Invalid version: '";
162 *error
+= result
->manifest
.version
;
167 // Get the minimum browser version (not required).
168 result
->manifest
.browser_min_version
=
169 GetAttribute(manifest
, "prodversionmin");
170 if (result
->manifest
.browser_min_version
.length()) {
171 Version
browser_min_version(result
->manifest
.browser_min_version
);
172 if (!browser_min_version
.IsValid()) {
173 *error
= "Invalid prodversionmin: '";
174 *error
+= result
->manifest
.browser_min_version
;
180 // Get the <packages> node.
181 std::vector
<xmlNode
*> packages
= GetChildren(manifest
, "packages");
182 if (packages
.empty()) {
183 *error
= "Missing packages tag on manifest.";
187 // Parse each of the <package> tags.
188 std::vector
<xmlNode
*> package
= GetChildren(packages
[0], "package");
189 for (size_t i
= 0; i
!= package
.size(); ++i
) {
190 if (!ParsePackageTag(package
[i
], result
, error
))
197 // Parses the <urls> tag and its children in the <updatecheck>.
198 bool ParseUrlsTag(xmlNode
* urls
,
199 UpdateResponse::Result
* result
,
200 std::string
* error
) {
201 // Get the url nodes.
202 std::vector
<xmlNode
*> url
= GetChildren(urls
, "url");
204 *error
= "Missing url tags on urls.";
208 // Get the list of urls for full and optionally, for diff updates.
209 // There can only be either a codebase or a codebasediff attribute in a tag.
210 for (size_t i
= 0; i
!= url
.size(); ++i
) {
211 // Find the url to the crx file.
212 const GURL
crx_url(GetAttribute(url
[i
], "codebase"));
213 if (crx_url
.is_valid()) {
214 result
->crx_urls
.push_back(crx_url
);
217 const GURL
crx_diffurl(GetAttribute(url
[i
], "codebasediff"));
218 if (crx_diffurl
.is_valid()) {
219 result
->crx_diffurls
.push_back(crx_diffurl
);
224 // Expect at least one url for full update.
225 if (result
->crx_urls
.empty()) {
226 *error
= "Missing valid url for full update.";
233 // Parses the <updatecheck> tag.
234 bool ParseUpdateCheckTag(xmlNode
* updatecheck
,
235 UpdateResponse::Result
* result
,
236 std::string
* error
) {
237 if (GetAttribute(updatecheck
, "status") == "noupdate") {
241 // Get the <urls> tag.
242 std::vector
<xmlNode
*> urls
= GetChildren(updatecheck
, "urls");
244 *error
= "Missing urls on updatecheck.";
248 if (!ParseUrlsTag(urls
[0], result
, error
)) {
252 std::vector
<xmlNode
*> manifests
= GetChildren(updatecheck
, "manifest");
253 if (manifests
.empty()) {
254 *error
= "Missing manifest on updatecheck.";
258 return ParseManifestTag(manifests
[0], result
, error
);
261 // Parses a single <app> tag.
262 bool ParseAppTag(xmlNode
* app
,
263 UpdateResponse::Result
* result
,
264 std::string
* error
) {
266 result
->extension_id
= GetAttribute(app
, "appid");
267 if (result
->extension_id
.empty()) {
268 *error
= "Missing appid on app node";
272 // Get the <updatecheck> tag.
273 std::vector
<xmlNode
*> updates
= GetChildren(app
, "updatecheck");
274 if (updates
.empty()) {
275 *error
= "Missing updatecheck on app.";
279 return ParseUpdateCheckTag(updates
[0], result
, error
);
282 bool UpdateResponse::Parse(const std::string
& response_xml
) {
283 results_
.daystart_elapsed_seconds
= kNoDaystart
;
284 results_
.list
.clear();
287 if (response_xml
.length() < 1) {
288 ParseError("Empty xml");
292 std::string xml_errors
;
293 ScopedXmlErrorFunc
error_func(&xml_errors
, &XmlErrorFunc
);
295 // Start up the xml parser with the manifest_xml contents.
296 ScopedXmlDocument
document(
297 xmlParseDoc(reinterpret_cast<const xmlChar
*>(response_xml
.c_str())));
298 if (!document
.get()) {
299 ParseError("%s", xml_errors
.c_str());
303 xmlNode
* root
= xmlDocGetRootElement(document
.get());
305 ParseError("Missing root node");
309 if (!TagNameEquals(root
, "response")) {
310 ParseError("Missing response tag");
314 // Check for the response "protocol" attribute.
315 if (GetAttribute(root
, "protocol") != kExpectedResponseProtocol
) {
317 "Missing/incorrect protocol on response tag "
319 kExpectedResponseProtocol
);
323 // Parse the first <daystart> if it is present.
324 std::vector
<xmlNode
*> daystarts
= GetChildren(root
, "daystart");
325 if (!daystarts
.empty()) {
326 xmlNode
* first
= daystarts
[0];
327 std::string elapsed_seconds
= GetAttribute(first
, "elapsed_seconds");
328 int parsed_elapsed
= kNoDaystart
;
329 if (base::StringToInt(elapsed_seconds
, &parsed_elapsed
)) {
330 results_
.daystart_elapsed_seconds
= parsed_elapsed
;
334 // Parse each of the <app> tags.
335 std::vector
<xmlNode
*> apps
= GetChildren(root
, "app");
336 for (size_t i
= 0; i
!= apps
.size(); ++i
) {
339 if (ParseAppTag(apps
[i
], &result
, &error
)) {
340 results_
.list
.push_back(result
);
342 ParseError("%s", error
.c_str());
349 } // namespace update_client