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 } 286