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