Fix crash on app list start page contents not existing.
[chromium-blink-merge.git] / extensions / common / update_manifest.cc
blob57162da71e7bc0d82a98040a4bc45ce9e4d0ffdf
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 "extensions/common/update_manifest.h"
7 #include <algorithm>
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()
23 : size(0),
24 diff_size(0) {}
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, ...) {
38 va_list args;
39 va_start(args, details);
41 if (errors_.length() > 0) {
42 // TODO(asargent) make a platform abstracted newline?
43 errors_ += "\r\n";
45 base::StringAppendV(&errors_, details, args);
46 va_end(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) {
54 return false;
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,
61 const char* name) {
62 std::vector<xmlNode*> result;
63 for (xmlNode* child = root->children; child != NULL; child = child->next) {
64 if (!TagNameEquals(child, name, xml_namespace)) {
65 continue;
67 result.push_back(child);
69 return result;
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));
82 return std::string();
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, ...) {
88 va_list args;
89 va_start(args, message);
90 std::string* error = static_cast<std::string*>(context);
91 base::StringAppendV(error, message, args);
92 va_end(args);
95 // Utility class for cleaning up the xml document when leaving a scope.
96 class ScopedXmlDocument {
97 public:
98 explicit ScopedXmlDocument(xmlDocPtr document) : document_(document) {}
99 ~ScopedXmlDocument() {
100 if (document_)
101 xmlFreeDoc(document_);
104 xmlDocPtr get() {
105 return document_;
108 private:
109 xmlDocPtr 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)) {
118 return ns;
121 return NULL;
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";
135 return false;
138 // Get the updatecheck node.
139 std::vector<xmlNode*> updates = GetChildren(app_node, xml_namespace,
140 "updatecheck");
141 if (updates.size() > 1) {
142 *error_detail = "Too many updatecheck tags on app (expecting only 1).";
143 return false;
145 if (updates.empty()) {
146 *error_detail = "Missing updatecheck on app.";
147 return false;
149 xmlNode *updatecheck = updates[0];
151 if (GetAttribute(updatecheck, "status") == "noupdate") {
152 return true;
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 += "'.";
161 return false;
164 // Get the version.
165 result->version = GetAttribute(updatecheck, "version");
166 if (result->version.length() == 0) {
167 *error_detail = "Missing version for updatecheck.";
168 return false;
170 Version version(result->version);
171 if (!version.IsValid()) {
172 *error_detail = "Invalid version: '";
173 *error_detail += result->version;
174 *error_detail += "'.";
175 return false;
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 += "'.";
186 return false;
190 // package_hash is optional. It is a sha256 hash of the package in hex
191 // format.
192 result->package_hash = GetAttribute(updatecheck, "hash_sha256");
194 int size = 0;
195 if (base::StringToInt(GetAttribute(updatecheck, "size"), &size)) {
196 result->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");
206 int sizediff = 0;
207 if (base::StringToInt(GetAttribute(updatecheck, "sizediff"), &sizediff)) {
208 result->diff_size = sizediff;
211 return true;
215 bool UpdateManifest::Parse(const std::string& manifest_xml) {
216 results_.list.resize(0);
217 results_.daystart_elapsed_seconds = kNoDaystart;
218 errors_ = "";
220 if (manifest_xml.length() < 1) {
221 ParseError("Empty xml");
222 return false;
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());
233 return false;
236 xmlNode *root = xmlDocGetRootElement(document.get());
237 if (!root) {
238 ParseError("Missing root node");
239 return false;
242 // Look for the required namespace declaration.
243 xmlNs* gupdate_ns = GetNamespace(root, kExpectedGupdateXmlns);
244 if (!gupdate_ns) {
245 ParseError("Missing or incorrect xmlns on gupdate tag");
246 return false;
249 if (!TagNameEquals(root, "gupdate", gupdate_ns)) {
250 ParseError("Missing gupdate tag");
251 return false;
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);
258 return false;
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++) {
275 Result current;
276 std::string error;
277 if (!ParseSingleAppTag(apps[i], gupdate_ns, &current, &error)) {
278 ParseError("%s", error.c_str());
279 } else {
280 results_.list.push_back(current);
284 return true;