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