1 // Copyright (c) 2012 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 "chrome/common/extensions/update_manifest.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 static const char* kExpectedGupdateProtocol
= "2.0";
19 static const char* kExpectedGupdateXmlns
=
20 "http://www.google.com/update2/response";
22 UpdateManifest::Result::Result()
26 UpdateManifest::Result::~Result() {}
28 UpdateManifest::Results::Results() : daystart_elapsed_seconds(kNoDaystart
) {}
30 UpdateManifest::Results::~Results() {}
32 UpdateManifest::UpdateManifest() {
35 UpdateManifest::~UpdateManifest() {}
37 void UpdateManifest::ParseError(const char* details
, ...) {
39 va_start(args
, details
);
41 if (errors_
.length() > 0) {
42 // TODO(asargent) make a platform abstracted newline?
45 base::StringAppendV(&errors_
, details
, args
);
49 // Checks whether a given node's name matches |expected_name| and
50 // |expected_namespace|.
51 static bool TagNameEquals(const xmlNode
* node
, const char* expected_name
,
52 const xmlNs
* expected_namespace
) {
53 if (node
->ns
!= expected_namespace
) {
56 return 0 == strcmp(expected_name
, reinterpret_cast<const char*>(node
->name
));
59 // Returns child nodes of |root| with name |name| in namespace |xml_namespace|.
60 static std::vector
<xmlNode
*> GetChildren(xmlNode
* root
, xmlNs
* xml_namespace
,
62 std::vector
<xmlNode
*> result
;
63 for (xmlNode
* child
= root
->children
; child
!= NULL
; child
= child
->next
) {
64 if (!TagNameEquals(child
, name
, xml_namespace
)) {
67 result
.push_back(child
);
72 // Returns the value of a named attribute, or the empty string.
73 static std::string
GetAttribute(xmlNode
* node
, const char* attribute_name
) {
74 const xmlChar
* name
= reinterpret_cast<const xmlChar
*>(attribute_name
);
75 for (xmlAttr
* attr
= node
->properties
; attr
!= NULL
; attr
= attr
->next
) {
76 if (!xmlStrcmp(attr
->name
, name
) && attr
->children
&&
77 attr
->children
->content
) {
78 return std::string(reinterpret_cast<const char*>(
79 attr
->children
->content
));
85 // This is used for the xml parser to report errors. This assumes the context
86 // is a pointer to a std::string where the error message should be appended.
87 static void XmlErrorFunc(void *context
, const char *message
, ...) {
89 va_start(args
, message
);
90 std::string
* error
= static_cast<std::string
*>(context
);
91 base::StringAppendV(error
, message
, args
);
95 // Utility class for cleaning up the xml document when leaving a scope.
96 class ScopedXmlDocument
{
98 explicit ScopedXmlDocument(xmlDocPtr document
) : document_(document
) {}
99 ~ScopedXmlDocument() {
101 xmlFreeDoc(document_
);
112 // Returns a pointer to the xmlNs on |node| with the |expected_href|, or
113 // NULL if there isn't one with that href.
114 static xmlNs
* GetNamespace(xmlNode
* node
, const char* expected_href
) {
115 const xmlChar
* href
= reinterpret_cast<const xmlChar
*>(expected_href
);
116 for (xmlNs
* ns
= node
->ns
; ns
!= NULL
; ns
= ns
->next
) {
117 if (ns
->href
&& !xmlStrcmp(ns
->href
, href
)) {
125 // Helper function that reads in values for a single <app> tag. It returns a
126 // boolean indicating success or failure. On failure, it writes a error message
127 // into |error_detail|.
128 static bool ParseSingleAppTag(xmlNode
* app_node
, xmlNs
* xml_namespace
,
129 UpdateManifest::Result
* result
,
130 std::string
*error_detail
) {
131 // Read the extension id.
132 result
->extension_id
= GetAttribute(app_node
, "appid");
133 if (result
->extension_id
.length() == 0) {
134 *error_detail
= "Missing appid on app node";
138 // Get the updatecheck node.
139 std::vector
<xmlNode
*> updates
= GetChildren(app_node
, xml_namespace
,
141 if (updates
.size() > 1) {
142 *error_detail
= "Too many updatecheck tags on app (expecting only 1).";
145 if (updates
.empty()) {
146 *error_detail
= "Missing updatecheck on app.";
149 xmlNode
*updatecheck
= updates
[0];
151 if (GetAttribute(updatecheck
, "status") == "noupdate") {
155 // Find the url to the crx file.
156 result
->crx_url
= GURL(GetAttribute(updatecheck
, "codebase"));
157 if (!result
->crx_url
.is_valid()) {
158 *error_detail
= "Invalid codebase url: '";
159 *error_detail
+= result
->crx_url
.possibly_invalid_spec();
160 *error_detail
+= "'.";
165 result
->version
= GetAttribute(updatecheck
, "version");
166 if (result
->version
.length() == 0) {
167 *error_detail
= "Missing version for updatecheck.";
170 Version
version(result
->version
);
171 if (!version
.IsValid()) {
172 *error_detail
= "Invalid version: '";
173 *error_detail
+= result
->version
;
174 *error_detail
+= "'.";
178 // Get the minimum browser version (not required).
179 result
->browser_min_version
= GetAttribute(updatecheck
, "prodversionmin");
180 if (result
->browser_min_version
.length()) {
181 Version
browser_min_version(result
->browser_min_version
);
182 if (!browser_min_version
.IsValid()) {
183 *error_detail
= "Invalid prodversionmin: '";
184 *error_detail
+= result
->browser_min_version
;
185 *error_detail
+= "'.";
190 // package_hash is optional. It is only required for blacklist. It is a
191 // sha256 hash of the package in hex format.
192 result
->package_hash
= GetAttribute(updatecheck
, "hash");
195 if (base::StringToInt(GetAttribute(updatecheck
, "size"), &size
)) {
199 // package_fingerprint is optional. It identifies the package, preferably
200 // with a modified sha256 hash of the package in hex format.
201 result
->package_fingerprint
= GetAttribute(updatecheck
, "fp");
203 // Differential update information is optional.
204 result
->diff_crx_url
= GURL(GetAttribute(updatecheck
, "codebasediff"));
205 result
->diff_package_hash
= GetAttribute(updatecheck
, "hashdiff");
207 if (base::StringToInt(GetAttribute(updatecheck
, "sizediff"), &sizediff
)) {
208 result
->diff_size
= sizediff
;
215 bool UpdateManifest::Parse(const std::string
& manifest_xml
) {
216 results_
.list
.resize(0);
217 results_
.daystart_elapsed_seconds
= kNoDaystart
;
220 if (manifest_xml
.length() < 1) {
221 ParseError("Empty xml");
225 std::string xml_errors
;
226 ScopedXmlErrorFunc
error_func(&xml_errors
, &XmlErrorFunc
);
228 // Start up the xml parser with the manifest_xml contents.
229 ScopedXmlDocument
document(xmlParseDoc(
230 reinterpret_cast<const xmlChar
*>(manifest_xml
.c_str())));
231 if (!document
.get()) {
232 ParseError("%s", xml_errors
.c_str());
236 xmlNode
*root
= xmlDocGetRootElement(document
.get());
238 ParseError("Missing root node");
242 // Look for the required namespace declaration.
243 xmlNs
* gupdate_ns
= GetNamespace(root
, kExpectedGupdateXmlns
);
245 ParseError("Missing or incorrect xmlns on gupdate tag");
249 if (!TagNameEquals(root
, "gupdate", gupdate_ns
)) {
250 ParseError("Missing gupdate tag");
254 // Check for the gupdate "protocol" attribute.
255 if (GetAttribute(root
, "protocol") != kExpectedGupdateProtocol
) {
256 ParseError("Missing/incorrect protocol on gupdate tag "
257 "(expected '%s')", kExpectedGupdateProtocol
);
261 // Parse the first <daystart> if it's present.
262 std::vector
<xmlNode
*> daystarts
= GetChildren(root
, gupdate_ns
, "daystart");
263 if (!daystarts
.empty()) {
264 xmlNode
* first
= daystarts
[0];
265 std::string elapsed_seconds
= GetAttribute(first
, "elapsed_seconds");
266 int parsed_elapsed
= kNoDaystart
;
267 if (base::StringToInt(elapsed_seconds
, &parsed_elapsed
)) {
268 results_
.daystart_elapsed_seconds
= parsed_elapsed
;
272 // Parse each of the <app> tags.
273 std::vector
<xmlNode
*> apps
= GetChildren(root
, gupdate_ns
, "app");
274 for (unsigned int i
= 0; i
< apps
.size(); i
++) {
277 if (!ParseSingleAppTag(apps
[i
], gupdate_ns
, ¤t
, &error
)) {
278 ParseError("%s", error
.c_str());
280 results_
.list
.push_back(current
);