- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / common / extensions / update_manifest.cc
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 }