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