• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
18 namespace component_updater {
19 
20 static const char* kExpectedResponseProtocol = "3.0";
21 
UpdateResponse()22 UpdateResponse::UpdateResponse() {}
~UpdateResponse()23 UpdateResponse::~UpdateResponse() {}
24 
Results()25 UpdateResponse::Results::Results() : daystart_elapsed_seconds(kNoDaystart) {}
~Results()26 UpdateResponse::Results::~Results() {}
27 
Result()28 UpdateResponse::Result::Result() {}
29 
~Result()30 UpdateResponse::Result::~Result() {}
31 
Manifest()32 UpdateResponse::Result::Manifest::Manifest() {}
~Manifest()33 UpdateResponse::Result::Manifest::~Manifest() {}
34 
Package()35 UpdateResponse::Result::Manifest::Package::Package() : size(0), sizediff(0) {}
~Package()36 UpdateResponse::Result::Manifest::Package::~Package() {}
37 
ParseError(const char * details,...)38 void 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|.
TagNameEquals(const xmlNode * node,const char * expected_name)51 static 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|.
GetChildren(xmlNode * root,const char * name)56 static 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.
GetAttribute(xmlNode * node,const char * attribute_name)68 static 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.
XmlErrorFunc(void * context,const char * message,...)82 static 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.
91 class ScopedXmlDocument {
92  public:
ScopedXmlDocument(xmlDocPtr document)93   explicit ScopedXmlDocument(xmlDocPtr document) : document_(document) {}
~ScopedXmlDocument()94   ~ScopedXmlDocument() {
95     if (document_)
96       xmlFreeDoc(document_);
97   }
98 
get()99   xmlDocPtr get() { return document_; }
100 
101  private:
102   xmlDocPtr document_;
103 };
104 
105 // Parses the <package> tag.
ParsePackageTag(xmlNode * package,UpdateResponse::Result * result,std::string * error)106 bool 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.
ParseManifestTag(xmlNode * manifest,UpdateResponse::Result * result,std::string * error)140 bool 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>.
ParseUrlsTag(xmlNode * urls,UpdateResponse::Result * result,std::string * error)188 bool 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.
ParseUpdateCheckTag(xmlNode * updatecheck,UpdateResponse::Result * result,std::string * error)224 bool 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.
ParseAppTag(xmlNode * app,UpdateResponse::Result * result,std::string * error)252 bool 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 
Parse(const std::string & response_xml)272 bool 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