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