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