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