1// Copyright 2013 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/browser/component_updater/update_response.h"
6#include <algorithm>
7#include "base/memory/scoped_ptr.h"
8#include "base/stl_util.h"
9#include "base/strings/string_number_conversions.h"
10#include "base/strings/string_util.h"
11#include "base/strings/stringprintf.h"
12#include "base/version.h"
13#include "libxml/tree.h"
14#include "third_party/libxml/chromium/libxml_utils.h"
15
16namespace component_updater {
17
18static const char* kExpectedResponseProtocol = "3.0";
19
20UpdateResponse::UpdateResponse() {}
21UpdateResponse::~UpdateResponse() {}
22
23UpdateResponse::Results::Results() : daystart_elapsed_seconds(kNoDaystart) {}
24UpdateResponse::Results::~Results() {}
25
26UpdateResponse::Result::Result() {}
27
28UpdateResponse::Result::~Result() {}
29
30UpdateResponse::Result::Manifest::Manifest() {}
31UpdateResponse::Result::Manifest::~Manifest() {}
32
33UpdateResponse::Result::Manifest::Package::Package() : size(0), sizediff(0) {}
34UpdateResponse::Result::Manifest::Package::~Package() {}
35
36void UpdateResponse::ParseError(const char* details, ...) {
37  va_list args;
38  va_start(args, details);
39
40  if (!errors_.empty()) {
41    errors_ += "\r\n";
42  }
43
44  base::StringAppendV(&errors_, details, args);
45  va_end(args);
46}
47
48// Checks whether a given node's name matches |expected_name|.
49static bool TagNameEquals(const xmlNode* node, const char* expected_name) {
50  return 0 == strcmp(expected_name, reinterpret_cast<const char*>(node->name));
51}
52
53// Returns child nodes of |root| with name |name|.
54static std::vector<xmlNode*> GetChildren(xmlNode* root, const char* name) {
55  std::vector<xmlNode*> result;
56  for (xmlNode* child = root->children; child != NULL; child = child->next) {
57    if (!TagNameEquals(child, name)) {
58      continue;
59    }
60    result.push_back(child);
61  }
62  return result;
63}
64
65// Returns the value of a named attribute, or the empty string.
66static std::string GetAttribute(xmlNode* node, const char* attribute_name) {
67  const xmlChar* name = reinterpret_cast<const xmlChar*>(attribute_name);
68  for (xmlAttr* attr = node->properties; attr != NULL; attr = attr->next) {
69    if (!xmlStrcmp(attr->name, name) && attr->children &&
70        attr->children->content) {
71      return std::string(
72          reinterpret_cast<const char*>(attr->children->content));
73    }
74  }
75  return std::string();
76}
77
78// This is used for the xml parser to report errors. This assumes the context
79// is a pointer to a std::string where the error message should be appended.
80static void XmlErrorFunc(void* context, const char* message, ...) {
81  va_list args;
82  va_start(args, message);
83  std::string* error = static_cast<std::string*>(context);
84  base::StringAppendV(error, message, args);
85  va_end(args);
86}
87
88// Utility class for cleaning up the xml document when leaving a scope.
89class ScopedXmlDocument {
90 public:
91  explicit ScopedXmlDocument(xmlDocPtr document) : document_(document) {}
92  ~ScopedXmlDocument() {
93    if (document_)
94      xmlFreeDoc(document_);
95  }
96
97  xmlDocPtr get() { return document_; }
98
99 private:
100  xmlDocPtr document_;
101};
102
103// Parses the <package> tag.
104bool ParsePackageTag(xmlNode* package,
105                     UpdateResponse::Result* result,
106                     std::string* error) {
107  UpdateResponse::Result::Manifest::Package p;
108  p.name = GetAttribute(package, "name");
109  if (p.name.empty()) {
110    *error = "Missing name for package.";
111    return false;
112  }
113
114  p.namediff = GetAttribute(package, "namediff");
115
116  // package_fingerprint is optional. It identifies the package, preferably
117  // with a modified sha256 hash of the package in hex format.
118  p.fingerprint = GetAttribute(package, "fp");
119
120  p.hash_sha256 = GetAttribute(package, "hash_sha256");
121  int size = 0;
122  if (base::StringToInt(GetAttribute(package, "size"), &size)) {
123    p.size = size;
124  }
125
126  p.hashdiff_sha256 = GetAttribute(package, "hashdiff_sha256");
127  int sizediff = 0;
128  if (base::StringToInt(GetAttribute(package, "sizediff"), &sizediff)) {
129    p.sizediff = sizediff;
130  }
131
132  result->manifest.packages.push_back(p);
133
134  return true;
135}
136
137// Parses the <manifest> tag.
138bool ParseManifestTag(xmlNode* manifest,
139                      UpdateResponse::Result* result,
140                      std::string* error) {
141  // Get the version.
142  result->manifest.version = GetAttribute(manifest, "version");
143  if (result->manifest.version.empty()) {
144    *error = "Missing version for manifest.";
145    return false;
146  }
147  Version version(result->manifest.version);
148  if (!version.IsValid()) {
149    *error = "Invalid version: '";
150    *error += result->manifest.version;
151    *error += "'.";
152    return false;
153  }
154
155  // Get the minimum browser version (not required).
156  result->manifest.browser_min_version =
157      GetAttribute(manifest, "prodversionmin");
158  if (result->manifest.browser_min_version.length()) {
159    Version browser_min_version(result->manifest.browser_min_version);
160    if (!browser_min_version.IsValid()) {
161      *error = "Invalid prodversionmin: '";
162      *error += result->manifest.browser_min_version;
163      *error += "'.";
164      return false;
165    }
166  }
167
168  // Get the <packages> node.
169  std::vector<xmlNode*> packages = GetChildren(manifest, "packages");
170  if (packages.empty()) {
171    *error = "Missing packages tag on manifest.";
172    return false;
173  }
174
175  // Parse each of the <package> tags.
176  std::vector<xmlNode*> package = GetChildren(packages[0], "package");
177  for (size_t i = 0; i != package.size(); ++i) {
178    if (!ParsePackageTag(package[i], result, error))
179      return false;
180  }
181
182  return true;
183}
184
185// Parses the <urls> tag and its children in the <updatecheck>.
186bool ParseUrlsTag(xmlNode* urls,
187                  UpdateResponse::Result* result,
188                  std::string* error) {
189  // Get the url nodes.
190  std::vector<xmlNode*> url = GetChildren(urls, "url");
191  if (url.empty()) {
192    *error = "Missing url tags on urls.";
193    return false;
194  }
195
196  // Get the list of urls for full and optionally, for diff updates.
197  // There can only be either a codebase or a codebasediff attribute in a tag.
198  for (size_t i = 0; i != url.size(); ++i) {
199    // Find the url to the crx file.
200    const GURL crx_url(GetAttribute(url[i], "codebase"));
201    if (crx_url.is_valid()) {
202      result->crx_urls.push_back(crx_url);
203      continue;
204    }
205    const GURL crx_diffurl(GetAttribute(url[i], "codebasediff"));
206    if (crx_diffurl.is_valid()) {
207      result->crx_diffurls.push_back(crx_diffurl);
208      continue;
209    }
210  }
211
212  // Expect at least one url for full update.
213  if (result->crx_urls.empty()) {
214    *error = "Missing valid url for full update.";
215    return false;
216  }
217
218  return true;
219}
220
221// Parses the <updatecheck> tag.
222bool ParseUpdateCheckTag(xmlNode* updatecheck,
223                         UpdateResponse::Result* result,
224                         std::string* error) {
225  if (GetAttribute(updatecheck, "status") == "noupdate") {
226    return true;
227  }
228
229  // Get the <urls> tag.
230  std::vector<xmlNode*> urls = GetChildren(updatecheck, "urls");
231  if (urls.empty()) {
232    *error = "Missing urls on updatecheck.";
233    return false;
234  }
235
236  if (!ParseUrlsTag(urls[0], result, error)) {
237    return false;
238  }
239
240  std::vector<xmlNode*> manifests = GetChildren(updatecheck, "manifest");
241  if (urls.empty()) {
242    *error = "Missing urls on updatecheck.";
243    return false;
244  }
245
246  return ParseManifestTag(manifests[0], result, error);
247}
248
249// Parses a single <app> tag.
250bool ParseAppTag(xmlNode* app,
251                 UpdateResponse::Result* result,
252                 std::string* error) {
253  // Read the crx id.
254  result->extension_id = GetAttribute(app, "appid");
255  if (result->extension_id.empty()) {
256    *error = "Missing appid on app node";
257    return false;
258  }
259
260  // Get the <updatecheck> tag.
261  std::vector<xmlNode*> updates = GetChildren(app, "updatecheck");
262  if (updates.empty()) {
263    *error = "Missing updatecheck on app.";
264    return false;
265  }
266
267  return ParseUpdateCheckTag(updates[0], result, error);
268}
269
270bool UpdateResponse::Parse(const std::string& response_xml) {
271  results_.daystart_elapsed_seconds = kNoDaystart;
272  results_.list.clear();
273  errors_.clear();
274
275  if (response_xml.length() < 1) {
276    ParseError("Empty xml");
277    return false;
278  }
279
280  std::string xml_errors;
281  ScopedXmlErrorFunc error_func(&xml_errors, &XmlErrorFunc);
282
283  // Start up the xml parser with the manifest_xml contents.
284  ScopedXmlDocument document(
285      xmlParseDoc(reinterpret_cast<const xmlChar*>(response_xml.c_str())));
286  if (!document.get()) {
287    ParseError("%s", xml_errors.c_str());
288    return false;
289  }
290
291  xmlNode* root = xmlDocGetRootElement(document.get());
292  if (!root) {
293    ParseError("Missing root node");
294    return false;
295  }
296
297  if (!TagNameEquals(root, "response")) {
298    ParseError("Missing response tag");
299    return false;
300  }
301
302  // Check for the response "protocol" attribute.
303  if (GetAttribute(root, "protocol") != kExpectedResponseProtocol) {
304    ParseError(
305        "Missing/incorrect protocol on response tag "
306        "(expected '%s')",
307        kExpectedResponseProtocol);
308    return false;
309  }
310
311  // Parse the first <daystart> if it is present.
312  std::vector<xmlNode*> daystarts = GetChildren(root, "daystart");
313  if (!daystarts.empty()) {
314    xmlNode* first = daystarts[0];
315    std::string elapsed_seconds = GetAttribute(first, "elapsed_seconds");
316    int parsed_elapsed = kNoDaystart;
317    if (base::StringToInt(elapsed_seconds, &parsed_elapsed)) {
318      results_.daystart_elapsed_seconds = parsed_elapsed;
319    }
320  }
321
322  // Parse each of the <app> tags.
323  std::vector<xmlNode*> apps = GetChildren(root, "app");
324  for (size_t i = 0; i != apps.size(); ++i) {
325    Result result;
326    std::string error;
327    if (ParseAppTag(apps[i], &result, &error)) {
328      results_.list.push_back(result);
329    } else {
330      ParseError("%s", error.c_str());
331    }
332  }
333
334  return true;
335}
336
337}  // namespace component_updater
338