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