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