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