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