// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/common/extensions/update_manifest.h" #include <algorithm> #include "base/memory/scoped_ptr.h" #include "base/stl_util.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/version.h" #include "libxml/tree.h" #include "third_party/libxml/chromium/libxml_utils.h" static const char* kExpectedGupdateProtocol = "2.0"; static const char* kExpectedGupdateXmlns = "http://www.google.com/update2/response"; UpdateManifest::Result::Result() : size(0), diff_size(0) {} UpdateManifest::Result::~Result() {} UpdateManifest::Results::Results() : daystart_elapsed_seconds(kNoDaystart) {} UpdateManifest::Results::~Results() {} UpdateManifest::UpdateManifest() { } UpdateManifest::~UpdateManifest() {} void UpdateManifest::ParseError(const char* details, ...) { va_list args; va_start(args, details); if (errors_.length() > 0) { // TODO(asargent) make a platform abstracted newline? errors_ += "\r\n"; } base::StringAppendV(&errors_, details, args); va_end(args); } // Checks whether a given node's name matches |expected_name| and // |expected_namespace|. static bool TagNameEquals(const xmlNode* node, const char* expected_name, const xmlNs* expected_namespace) { if (node->ns != expected_namespace) { return false; } return 0 == strcmp(expected_name, reinterpret_cast<const char*>(node->name)); } // Returns child nodes of |root| with name |name| in namespace |xml_namespace|. static std::vector<xmlNode*> GetChildren(xmlNode* root, xmlNs* xml_namespace, const char* name) { std::vector<xmlNode*> result; for (xmlNode* child = root->children; child != NULL; child = child->next) { if (!TagNameEquals(child, name, xml_namespace)) { continue; } result.push_back(child); } return result; } // Returns the value of a named attribute, or the empty string. static std::string GetAttribute(xmlNode* node, const char* attribute_name) { const xmlChar* name = reinterpret_cast<const xmlChar*>(attribute_name); for (xmlAttr* attr = node->properties; attr != NULL; attr = attr->next) { if (!xmlStrcmp(attr->name, name) && attr->children && attr->children->content) { return std::string(reinterpret_cast<const char*>( attr->children->content)); } } return std::string(); } // This is used for the xml parser to report errors. This assumes the context // is a pointer to a std::string where the error message should be appended. static void XmlErrorFunc(void *context, const char *message, ...) { va_list args; va_start(args, message); std::string* error = static_cast<std::string*>(context); base::StringAppendV(error, message, args); va_end(args); } // Utility class for cleaning up the xml document when leaving a scope. class ScopedXmlDocument { public: explicit ScopedXmlDocument(xmlDocPtr document) : document_(document) {} ~ScopedXmlDocument() { if (document_) xmlFreeDoc(document_); } xmlDocPtr get() { return document_; } private: xmlDocPtr document_; }; // Returns a pointer to the xmlNs on |node| with the |expected_href|, or // NULL if there isn't one with that href. static xmlNs* GetNamespace(xmlNode* node, const char* expected_href) { const xmlChar* href = reinterpret_cast<const xmlChar*>(expected_href); for (xmlNs* ns = node->ns; ns != NULL; ns = ns->next) { if (ns->href && !xmlStrcmp(ns->href, href)) { return ns; } } return NULL; } // Helper function that reads in values for a single <app> tag. It returns a // boolean indicating success or failure. On failure, it writes a error message // into |error_detail|. static bool ParseSingleAppTag(xmlNode* app_node, xmlNs* xml_namespace, UpdateManifest::Result* result, std::string *error_detail) { // Read the extension id. result->extension_id = GetAttribute(app_node, "appid"); if (result->extension_id.length() == 0) { *error_detail = "Missing appid on app node"; return false; } // Get the updatecheck node. std::vector<xmlNode*> updates = GetChildren(app_node, xml_namespace, "updatecheck"); if (updates.size() > 1) { *error_detail = "Too many updatecheck tags on app (expecting only 1)."; return false; } if (updates.empty()) { *error_detail = "Missing updatecheck on app."; return false; } xmlNode *updatecheck = updates[0]; if (GetAttribute(updatecheck, "status") == "noupdate") { return true; } // Find the url to the crx file. result->crx_url = GURL(GetAttribute(updatecheck, "codebase")); if (!result->crx_url.is_valid()) { *error_detail = "Invalid codebase url: '"; *error_detail += result->crx_url.possibly_invalid_spec(); *error_detail += "'."; return false; } // Get the version. result->version = GetAttribute(updatecheck, "version"); if (result->version.length() == 0) { *error_detail = "Missing version for updatecheck."; return false; } Version version(result->version); if (!version.IsValid()) { *error_detail = "Invalid version: '"; *error_detail += result->version; *error_detail += "'."; return false; } // Get the minimum browser version (not required). result->browser_min_version = GetAttribute(updatecheck, "prodversionmin"); if (result->browser_min_version.length()) { Version browser_min_version(result->browser_min_version); if (!browser_min_version.IsValid()) { *error_detail = "Invalid prodversionmin: '"; *error_detail += result->browser_min_version; *error_detail += "'."; return false; } } // package_hash is optional. It is only required for blacklist. It is a // sha256 hash of the package in hex format. result->package_hash = GetAttribute(updatecheck, "hash"); int size = 0; if (base::StringToInt(GetAttribute(updatecheck, "size"), &size)) { result->size = size; } // package_fingerprint is optional. It identifies the package, preferably // with a modified sha256 hash of the package in hex format. result->package_fingerprint = GetAttribute(updatecheck, "fp"); // Differential update information is optional. result->diff_crx_url = GURL(GetAttribute(updatecheck, "codebasediff")); result->diff_package_hash = GetAttribute(updatecheck, "hashdiff"); int sizediff = 0; if (base::StringToInt(GetAttribute(updatecheck, "sizediff"), &sizediff)) { result->diff_size = sizediff; } return true; } bool UpdateManifest::Parse(const std::string& manifest_xml) { results_.list.resize(0); results_.daystart_elapsed_seconds = kNoDaystart; errors_ = ""; if (manifest_xml.length() < 1) { ParseError("Empty xml"); return false; } std::string xml_errors; ScopedXmlErrorFunc error_func(&xml_errors, &XmlErrorFunc); // Start up the xml parser with the manifest_xml contents. ScopedXmlDocument document(xmlParseDoc( reinterpret_cast<const xmlChar*>(manifest_xml.c_str()))); if (!document.get()) { ParseError("%s", xml_errors.c_str()); return false; } xmlNode *root = xmlDocGetRootElement(document.get()); if (!root) { ParseError("Missing root node"); return false; } // Look for the required namespace declaration. xmlNs* gupdate_ns = GetNamespace(root, kExpectedGupdateXmlns); if (!gupdate_ns) { ParseError("Missing or incorrect xmlns on gupdate tag"); return false; } if (!TagNameEquals(root, "gupdate", gupdate_ns)) { ParseError("Missing gupdate tag"); return false; } // Check for the gupdate "protocol" attribute. if (GetAttribute(root, "protocol") != kExpectedGupdateProtocol) { ParseError("Missing/incorrect protocol on gupdate tag " "(expected '%s')", kExpectedGupdateProtocol); return false; } // Parse the first <daystart> if it's present. std::vector<xmlNode*> daystarts = GetChildren(root, gupdate_ns, "daystart"); if (!daystarts.empty()) { xmlNode* first = daystarts[0]; std::string elapsed_seconds = GetAttribute(first, "elapsed_seconds"); int parsed_elapsed = kNoDaystart; if (base::StringToInt(elapsed_seconds, &parsed_elapsed)) { results_.daystart_elapsed_seconds = parsed_elapsed; } } // Parse each of the <app> tags. std::vector<xmlNode*> apps = GetChildren(root, gupdate_ns, "app"); for (unsigned int i = 0; i < apps.size(); i++) { Result current; std::string error; if (!ParseSingleAppTag(apps[i], gupdate_ns, ¤t, &error)) { ParseError("%s", error.c_str()); } else { results_.list.push_back(current); } } return true; }