| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012 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/common/extensions/update_manifest.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 static const char* kExpectedGupdateProtocol = "2.0"; | |
| 19 static const char* kExpectedGupdateXmlns = | |
| 20 "http://www.google.com/update2/response"; | |
| 21 | |
| 22 UpdateManifest::Result::Result() | |
| 23 : size(0), | |
| 24 diff_size(0) {} | |
| 25 | |
| 26 UpdateManifest::Result::~Result() {} | |
| 27 | |
| 28 UpdateManifest::Results::Results() : daystart_elapsed_seconds(kNoDaystart) {} | |
| 29 | |
| 30 UpdateManifest::Results::~Results() {} | |
| 31 | |
| 32 UpdateManifest::UpdateManifest() { | |
| 33 } | |
| 34 | |
| 35 UpdateManifest::~UpdateManifest() {} | |
| 36 | |
| 37 void UpdateManifest::ParseError(const char* details, ...) { | |
| 38 va_list args; | |
| 39 va_start(args, details); | |
| 40 | |
| 41 if (errors_.length() > 0) { | |
| 42 // TODO(asargent) make a platform abstracted newline? | |
| 43 errors_ += "\r\n"; | |
| 44 } | |
| 45 base::StringAppendV(&errors_, details, args); | |
| 46 va_end(args); | |
| 47 } | |
| 48 | |
| 49 // Checks whether a given node's name matches |expected_name| and | |
| 50 // |expected_namespace|. | |
| 51 static bool TagNameEquals(const xmlNode* node, const char* expected_name, | |
| 52 const xmlNs* expected_namespace) { | |
| 53 if (node->ns != expected_namespace) { | |
| 54 return false; | |
| 55 } | |
| 56 return 0 == strcmp(expected_name, reinterpret_cast<const char*>(node->name)); | |
| 57 } | |
| 58 | |
| 59 // Returns child nodes of |root| with name |name| in namespace |xml_namespace|. | |
| 60 static std::vector<xmlNode*> GetChildren(xmlNode* root, xmlNs* xml_namespace, | |
| 61 const char* name) { | |
| 62 std::vector<xmlNode*> result; | |
| 63 for (xmlNode* child = root->children; child != NULL; child = child->next) { | |
| 64 if (!TagNameEquals(child, name, xml_namespace)) { | |
| 65 continue; | |
| 66 } | |
| 67 result.push_back(child); | |
| 68 } | |
| 69 return result; | |
| 70 } | |
| 71 | |
| 72 // Returns the value of a named attribute, or the empty string. | |
| 73 static std::string GetAttribute(xmlNode* node, const char* attribute_name) { | |
| 74 const xmlChar* name = reinterpret_cast<const xmlChar*>(attribute_name); | |
| 75 for (xmlAttr* attr = node->properties; attr != NULL; attr = attr->next) { | |
| 76 if (!xmlStrcmp(attr->name, name) && attr->children && | |
| 77 attr->children->content) { | |
| 78 return std::string(reinterpret_cast<const char*>( | |
| 79 attr->children->content)); | |
| 80 } | |
| 81 } | |
| 82 return std::string(); | |
| 83 } | |
| 84 | |
| 85 // This is used for the xml parser to report errors. This assumes the context | |
| 86 // is a pointer to a std::string where the error message should be appended. | |
| 87 static void XmlErrorFunc(void *context, const char *message, ...) { | |
| 88 va_list args; | |
| 89 va_start(args, message); | |
| 90 std::string* error = static_cast<std::string*>(context); | |
| 91 base::StringAppendV(error, message, args); | |
| 92 va_end(args); | |
| 93 } | |
| 94 | |
| 95 // Utility class for cleaning up the xml document when leaving a scope. | |
| 96 class ScopedXmlDocument { | |
| 97 public: | |
| 98 explicit ScopedXmlDocument(xmlDocPtr document) : document_(document) {} | |
| 99 ~ScopedXmlDocument() { | |
| 100 if (document_) | |
| 101 xmlFreeDoc(document_); | |
| 102 } | |
| 103 | |
| 104 xmlDocPtr get() { | |
| 105 return document_; | |
| 106 } | |
| 107 | |
| 108 private: | |
| 109 xmlDocPtr document_; | |
| 110 }; | |
| 111 | |
| 112 // Returns a pointer to the xmlNs on |node| with the |expected_href|, or | |
| 113 // NULL if there isn't one with that href. | |
| 114 static xmlNs* GetNamespace(xmlNode* node, const char* expected_href) { | |
| 115 const xmlChar* href = reinterpret_cast<const xmlChar*>(expected_href); | |
| 116 for (xmlNs* ns = node->ns; ns != NULL; ns = ns->next) { | |
| 117 if (ns->href && !xmlStrcmp(ns->href, href)) { | |
| 118 return ns; | |
| 119 } | |
| 120 } | |
| 121 return NULL; | |
| 122 } | |
| 123 | |
| 124 | |
| 125 // Helper function that reads in values for a single <app> tag. It returns a | |
| 126 // boolean indicating success or failure. On failure, it writes a error message | |
| 127 // into |error_detail|. | |
| 128 static bool ParseSingleAppTag(xmlNode* app_node, xmlNs* xml_namespace, | |
| 129 UpdateManifest::Result* result, | |
| 130 std::string *error_detail) { | |
| 131 // Read the extension id. | |
| 132 result->extension_id = GetAttribute(app_node, "appid"); | |
| 133 if (result->extension_id.length() == 0) { | |
| 134 *error_detail = "Missing appid on app node"; | |
| 135 return false; | |
| 136 } | |
| 137 | |
| 138 // Get the updatecheck node. | |
| 139 std::vector<xmlNode*> updates = GetChildren(app_node, xml_namespace, | |
| 140 "updatecheck"); | |
| 141 if (updates.size() > 1) { | |
| 142 *error_detail = "Too many updatecheck tags on app (expecting only 1)."; | |
| 143 return false; | |
| 144 } | |
| 145 if (updates.empty()) { | |
| 146 *error_detail = "Missing updatecheck on app."; | |
| 147 return false; | |
| 148 } | |
| 149 xmlNode *updatecheck = updates[0]; | |
| 150 | |
| 151 if (GetAttribute(updatecheck, "status") == "noupdate") { | |
| 152 return true; | |
| 153 } | |
| 154 | |
| 155 // Find the url to the crx file. | |
| 156 result->crx_url = GURL(GetAttribute(updatecheck, "codebase")); | |
| 157 if (!result->crx_url.is_valid()) { | |
| 158 *error_detail = "Invalid codebase url: '"; | |
| 159 *error_detail += result->crx_url.possibly_invalid_spec(); | |
| 160 *error_detail += "'."; | |
| 161 return false; | |
| 162 } | |
| 163 | |
| 164 // Get the version. | |
| 165 result->version = GetAttribute(updatecheck, "version"); | |
| 166 if (result->version.length() == 0) { | |
| 167 *error_detail = "Missing version for updatecheck."; | |
| 168 return false; | |
| 169 } | |
| 170 Version version(result->version); | |
| 171 if (!version.IsValid()) { | |
| 172 *error_detail = "Invalid version: '"; | |
| 173 *error_detail += result->version; | |
| 174 *error_detail += "'."; | |
| 175 return false; | |
| 176 } | |
| 177 | |
| 178 // Get the minimum browser version (not required). | |
| 179 result->browser_min_version = GetAttribute(updatecheck, "prodversionmin"); | |
| 180 if (result->browser_min_version.length()) { | |
| 181 Version browser_min_version(result->browser_min_version); | |
| 182 if (!browser_min_version.IsValid()) { | |
| 183 *error_detail = "Invalid prodversionmin: '"; | |
| 184 *error_detail += result->browser_min_version; | |
| 185 *error_detail += "'."; | |
| 186 return false; | |
| 187 } | |
| 188 } | |
| 189 | |
| 190 // package_hash is optional. It is only required for blacklist. It is a | |
| 191 // sha256 hash of the package in hex format. | |
| 192 result->package_hash = GetAttribute(updatecheck, "hash"); | |
| 193 | |
| 194 int size = 0; | |
| 195 if (base::StringToInt(GetAttribute(updatecheck, "size"), &size)) { | |
| 196 result->size = size; | |
| 197 } | |
| 198 | |
| 199 // package_fingerprint is optional. It identifies the package, preferably | |
| 200 // with a modified sha256 hash of the package in hex format. | |
| 201 result->package_fingerprint = GetAttribute(updatecheck, "fp"); | |
| 202 | |
| 203 // Differential update information is optional. | |
| 204 result->diff_crx_url = GURL(GetAttribute(updatecheck, "codebasediff")); | |
| 205 result->diff_package_hash = GetAttribute(updatecheck, "hashdiff"); | |
| 206 int sizediff = 0; | |
| 207 if (base::StringToInt(GetAttribute(updatecheck, "sizediff"), &sizediff)) { | |
| 208 result->diff_size = sizediff; | |
| 209 } | |
| 210 | |
| 211 return true; | |
| 212 } | |
| 213 | |
| 214 | |
| 215 bool UpdateManifest::Parse(const std::string& manifest_xml) { | |
| 216 results_.list.resize(0); | |
| 217 results_.daystart_elapsed_seconds = kNoDaystart; | |
| 218 errors_ = ""; | |
| 219 | |
| 220 if (manifest_xml.length() < 1) { | |
| 221 ParseError("Empty xml"); | |
| 222 return false; | |
| 223 } | |
| 224 | |
| 225 std::string xml_errors; | |
| 226 ScopedXmlErrorFunc error_func(&xml_errors, &XmlErrorFunc); | |
| 227 | |
| 228 // Start up the xml parser with the manifest_xml contents. | |
| 229 ScopedXmlDocument document(xmlParseDoc( | |
| 230 reinterpret_cast<const xmlChar*>(manifest_xml.c_str()))); | |
| 231 if (!document.get()) { | |
| 232 ParseError("%s", xml_errors.c_str()); | |
| 233 return false; | |
| 234 } | |
| 235 | |
| 236 xmlNode *root = xmlDocGetRootElement(document.get()); | |
| 237 if (!root) { | |
| 238 ParseError("Missing root node"); | |
| 239 return false; | |
| 240 } | |
| 241 | |
| 242 // Look for the required namespace declaration. | |
| 243 xmlNs* gupdate_ns = GetNamespace(root, kExpectedGupdateXmlns); | |
| 244 if (!gupdate_ns) { | |
| 245 ParseError("Missing or incorrect xmlns on gupdate tag"); | |
| 246 return false; | |
| 247 } | |
| 248 | |
| 249 if (!TagNameEquals(root, "gupdate", gupdate_ns)) { | |
| 250 ParseError("Missing gupdate tag"); | |
| 251 return false; | |
| 252 } | |
| 253 | |
| 254 // Check for the gupdate "protocol" attribute. | |
| 255 if (GetAttribute(root, "protocol") != kExpectedGupdateProtocol) { | |
| 256 ParseError("Missing/incorrect protocol on gupdate tag " | |
| 257 "(expected '%s')", kExpectedGupdateProtocol); | |
| 258 return false; | |
| 259 } | |
| 260 | |
| 261 // Parse the first <daystart> if it's present. | |
| 262 std::vector<xmlNode*> daystarts = GetChildren(root, gupdate_ns, "daystart"); | |
| 263 if (!daystarts.empty()) { | |
| 264 xmlNode* first = daystarts[0]; | |
| 265 std::string elapsed_seconds = GetAttribute(first, "elapsed_seconds"); | |
| 266 int parsed_elapsed = kNoDaystart; | |
| 267 if (base::StringToInt(elapsed_seconds, &parsed_elapsed)) { | |
| 268 results_.daystart_elapsed_seconds = parsed_elapsed; | |
| 269 } | |
| 270 } | |
| 271 | |
| 272 // Parse each of the <app> tags. | |
| 273 std::vector<xmlNode*> apps = GetChildren(root, gupdate_ns, "app"); | |
| 274 for (unsigned int i = 0; i < apps.size(); i++) { | |
| 275 Result current; | |
| 276 std::string error; | |
| 277 if (!ParseSingleAppTag(apps[i], gupdate_ns, ¤t, &error)) { | |
| 278 ParseError("%s", error.c_str()); | |
| 279 } else { | |
| 280 results_.list.push_back(current); | |
| 281 } | |
| 282 } | |
| 283 | |
| 284 return true; | |
| 285 } | |
| OLD | NEW |