update_manifest.cc revision 21d179b334e59e9a3bfcaed4c4430bef1bc5759d
1// Copyright (c) 2010 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.
4
5#include "chrome/common/extensions/update_manifest.h"
6
7#include <algorithm>
8
9#include "base/scoped_ptr.h"
10#include "base/stl_util-inl.h"
11#include "base/string_util.h"
12#include "base/string_number_conversions.h"
13#include "base/stringprintf.h"
14#include "base/version.h"
15#include "chrome/common/libxml_utils.h"
16#include "libxml/tree.h"
17
18static const char* kExpectedGupdateProtocol = "2.0";
19static const char* kExpectedGupdateXmlns =
20    "http://www.google.com/update2/response";
21
22UpdateManifest::Results::Results() : daystart_elapsed_seconds(kNoDaystart) {}
23
24UpdateManifest::Results::~Results() {}
25
26UpdateManifest::UpdateManifest() {
27}
28
29UpdateManifest::~UpdateManifest() {}
30
31void UpdateManifest::ParseError(const char* details, ...) {
32  va_list args;
33  va_start(args, details);
34
35  if (errors_.length() > 0) {
36    // TODO(asargent) make a platform abstracted newline?
37    errors_ += "\r\n";
38  }
39  base::StringAppendV(&errors_, details, args);
40  va_end(args);
41}
42
43// Checks whether a given node's name matches |expected_name| and
44// |expected_namespace|.
45static bool TagNameEquals(const xmlNode* node, const char* expected_name,
46                          const xmlNs* expected_namespace) {
47  if (node->ns != expected_namespace) {
48    return false;
49  }
50  return 0 == strcmp(expected_name, reinterpret_cast<const char*>(node->name));
51}
52
53// Returns child nodes of |root| with name |name| in namespace |xml_namespace|.
54static std::vector<xmlNode*> GetChildren(xmlNode* root, xmlNs* xml_namespace,
55                                         const char* name) {
56  std::vector<xmlNode*> result;
57  for (xmlNode* child = root->children; child != NULL; child = child->next) {
58    if (!TagNameEquals(child, name, xml_namespace)) {
59      continue;
60    }
61    result.push_back(child);
62  }
63  return result;
64}
65
66// Returns the value of a named attribute, or the empty string.
67static std::string GetAttribute(xmlNode* node, const char* attribute_name) {
68  const xmlChar* name = reinterpret_cast<const xmlChar*>(attribute_name);
69  for (xmlAttr* attr = node->properties; attr != NULL; attr = attr->next) {
70    if (!xmlStrcmp(attr->name, name) && attr->children &&
71        attr->children->content) {
72      return std::string(reinterpret_cast<const char*>(
73          attr->children->content));
74    }
75  }
76  return std::string();
77}
78
79// This is used for the xml parser to report errors. This assumes the context
80// is a pointer to a std::string where the error message should be appended.
81static void XmlErrorFunc(void *context, const char *message, ...) {
82  va_list args;
83  va_start(args, message);
84  std::string* error = static_cast<std::string*>(context);
85  base::StringAppendV(error, message, args);
86  va_end(args);
87}
88
89// Utility class for cleaning up the xml document when leaving a scope.
90class ScopedXmlDocument {
91 public:
92  explicit ScopedXmlDocument(xmlDocPtr document) : document_(document) {}
93  ~ScopedXmlDocument() {
94    if (document_)
95      xmlFreeDoc(document_);
96  }
97
98  xmlDocPtr get() {
99    return document_;
100  }
101
102 private:
103  xmlDocPtr document_;
104};
105
106// Returns a pointer to the xmlNs on |node| with the |expected_href|, or
107// NULL if there isn't one with that href.
108static xmlNs* GetNamespace(xmlNode* node, const char* expected_href) {
109  const xmlChar* href = reinterpret_cast<const xmlChar*>(expected_href);
110  for (xmlNs* ns = node->ns; ns != NULL; ns = ns->next) {
111    if (ns->href && !xmlStrcmp(ns->href, href)) {
112      return ns;
113    }
114  }
115  return NULL;
116}
117
118
119// Helper function that reads in values for a single <app> tag. It returns a
120// boolean indicating success or failure. On failure, it writes a error message
121// into |error_detail|.
122static bool ParseSingleAppTag(xmlNode* app_node, xmlNs* xml_namespace,
123                              UpdateManifest::Result* result,
124                              std::string *error_detail) {
125  // Read the extension id.
126  result->extension_id = GetAttribute(app_node, "appid");
127  if (result->extension_id.length() == 0) {
128    *error_detail = "Missing appid on app node";
129    return false;
130  }
131
132  // Get the updatecheck node.
133  std::vector<xmlNode*> updates = GetChildren(app_node, xml_namespace,
134                                              "updatecheck");
135  if (updates.size() > 1) {
136    *error_detail = "Too many updatecheck tags on app (expecting only 1).";
137    return false;
138  }
139  if (updates.size() == 0) {
140    *error_detail = "Missing updatecheck on app.";
141    return false;
142  }
143  xmlNode *updatecheck = updates[0];
144
145  if (GetAttribute(updatecheck, "status") == "noupdate") {
146    return true;
147  }
148
149  // Find the url to the crx file.
150  result->crx_url = GURL(GetAttribute(updatecheck, "codebase"));
151  if (!result->crx_url.is_valid()) {
152    *error_detail = "Invalid codebase url: '";
153    *error_detail += GetAttribute(updatecheck, "codebase");
154    *error_detail += "'.";
155    return false;
156  }
157
158  // Get the version.
159  result->version = GetAttribute(updatecheck, "version");
160  if (result->version.length() == 0) {
161    *error_detail = "Missing version for updatecheck.";
162    return false;
163  }
164  scoped_ptr<Version> version(Version::GetVersionFromString(result->version));
165  if (!version.get()) {
166    *error_detail = "Invalid version: '";
167    *error_detail += result->version;
168    *error_detail += "'.";
169    return false;
170  }
171
172  // Get the minimum browser version (not required).
173  result->browser_min_version = GetAttribute(updatecheck, "prodversionmin");
174  if (result->browser_min_version.length()) {
175    scoped_ptr<Version> browser_min_version(
176      Version::GetVersionFromString(result->browser_min_version));
177    if (!browser_min_version.get()) {
178      *error_detail = "Invalid prodversionmin: '";
179      *error_detail += result->browser_min_version;
180      *error_detail += "'.";
181      return false;
182    }
183  }
184
185  // package_hash is optional. It is only required for blacklist. It is a
186  // sha256 hash of the package in hex format.
187  result->package_hash = GetAttribute(updatecheck, "hash");
188
189  return true;
190}
191
192
193bool UpdateManifest::Parse(const std::string& manifest_xml) {
194  results_.list.resize(0);
195  results_.daystart_elapsed_seconds = kNoDaystart;
196  errors_ = "";
197
198  if (manifest_xml.length() < 1) {
199     ParseError("Empty xml");
200    return false;
201  }
202
203  std::string xml_errors;
204  ScopedXmlErrorFunc error_func(&xml_errors, &XmlErrorFunc);
205
206  // Start up the xml parser with the manifest_xml contents.
207  ScopedXmlDocument document(xmlParseDoc(
208      reinterpret_cast<const xmlChar*>(manifest_xml.c_str())));
209  if (!document.get()) {
210    ParseError("%s", xml_errors.c_str());
211    return false;
212  }
213
214  xmlNode *root = xmlDocGetRootElement(document.get());
215  if (!root) {
216    ParseError("Missing root node");
217    return false;
218  }
219
220  // Look for the required namespace declaration.
221  xmlNs* gupdate_ns = GetNamespace(root, kExpectedGupdateXmlns);
222  if (!gupdate_ns) {
223    ParseError("Missing or incorrect xmlns on gupdate tag");
224    return false;
225  }
226
227  if (!TagNameEquals(root, "gupdate", gupdate_ns)) {
228    ParseError("Missing gupdate tag");
229    return false;
230  }
231
232  // Check for the gupdate "protocol" attribute.
233  if (GetAttribute(root, "protocol") != kExpectedGupdateProtocol) {
234    ParseError("Missing/incorrect protocol on gupdate tag "
235        "(expected '%s')", kExpectedGupdateProtocol);
236    return false;
237  }
238
239  // Parse the first <daystart> if it's present.
240  std::vector<xmlNode*> daystarts = GetChildren(root, gupdate_ns, "daystart");
241  if (daystarts.size() > 0) {
242    xmlNode* first = daystarts[0];
243    std::string elapsed_seconds = GetAttribute(first, "elapsed_seconds");
244    int parsed_elapsed = kNoDaystart;
245    if (base::StringToInt(elapsed_seconds, &parsed_elapsed)) {
246      results_.daystart_elapsed_seconds = parsed_elapsed;
247    }
248  }
249
250  // Parse each of the <app> tags.
251  std::vector<xmlNode*> apps = GetChildren(root, gupdate_ns, "app");
252  for (unsigned int i = 0; i < apps.size(); i++) {
253    Result current;
254    std::string error;
255    if (!ParseSingleAppTag(apps[i], gupdate_ns, &current, &error)) {
256      ParseError("%s", error.c_str());
257    } else {
258      results_.list.push_back(current);
259    }
260  }
261
262  return true;
263}
264