Home | History | Annotate | Download | only in extensions
      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 
     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 
     24 UpdateManifest::Result::~Result() {}
     25 
     26 UpdateManifest::Results::Results() : daystart_elapsed_seconds(kNoDaystart) {}
     27 
     28 UpdateManifest::Results::~Results() {}
     29 
     30 UpdateManifest::UpdateManifest() {
     31 }
     32 
     33 UpdateManifest::~UpdateManifest() {}
     34 
     35 void 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|.
     49 static 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|.
     58 static 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.
     71 static 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.
     85 static 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.
     94 class 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.
    112 static 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|.
    126 static 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 
    197 bool 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, &current, &error)) {
    260       ParseError("%s", error.c_str());
    261     } else {
    262       results_.list.push_back(current);
    263     }
    264   }
    265 
    266   return true;
    267 }
    268