summaryrefslogtreecommitdiffstats
path: root/chrome/common
diff options
context:
space:
mode:
authorasargent@chromium.org <asargent@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-08-20 16:18:21 +0000
committerasargent@chromium.org <asargent@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-08-20 16:18:21 +0000
commitdbb92e0d55eede17bf2ada8370650d39834e6060 (patch)
tree941532e52eceab1115b60556960abde35f786368 /chrome/common
parente667f718463a78b7d1de5a51e71982211f00f875 (diff)
downloadchromium_src-dbb92e0d55eede17bf2ada8370650d39834e6060.zip
chromium_src-dbb92e0d55eede17bf2ada8370650d39834e6060.tar.gz
chromium_src-dbb92e0d55eede17bf2ada8370650d39834e6060.tar.bz2
Do extensions update manifest XML parsing in a sandboxed process.
This involves moving the xml parsing code from static functions in extension_updater.cc to a UpdateManifest class, and switching from logging any errors directly to collecting them up and passing them across the IPC channel. BUG=http://crbug.com/12677 TEST=extensions auto-update should still work correctly Review URL: http://codereview.chromium.org/164541 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@23822 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/common')
-rw-r--r--chrome/common/extensions/update_manifest.cc230
-rw-r--r--chrome/common/extensions/update_manifest.h71
-rw-r--r--chrome/common/extensions/update_manifest_unittest.cc119
-rw-r--r--chrome/common/render_messages.h34
-rw-r--r--chrome/common/render_messages_internal.h45
5 files changed, 484 insertions, 15 deletions
diff --git a/chrome/common/extensions/update_manifest.cc b/chrome/common/extensions/update_manifest.cc
new file mode 100644
index 0000000..b4c32b1
--- /dev/null
+++ b/chrome/common/extensions/update_manifest.cc
@@ -0,0 +1,230 @@
+// Copyright (c) 2009 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/stl_util-inl.h"
+#include "base/string_util.h"
+#include "base/values.h"
+#include "base/version.h"
+#include "chrome/common/libxml_utils.h"
+#include "libxml/tree.h"
+
+static const char* kExpectedGupdateProtocol = "2.0";
+static const char* kExpectedGupdateXmlns =
+ "http://www.google.com/update2/response";
+
+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";
+ }
+ StringAppendV(&errors_, details, 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);
+ StringAppendV(error, message, 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.size() == 0) {
+ *error_detail = "Missing updatecheck on app";
+ return false;
+ }
+ xmlNode *updatecheck = updates[0];
+
+ // 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";
+ return false;
+ }
+
+ // Get the version.
+ result->version = GetAttribute(updatecheck, "version");
+ if (result->version.length() == 0) {
+ *error_detail = "Missing version for updatecheck";
+ return false;
+ }
+ scoped_ptr<Version> version(Version::GetVersionFromString(result->version));
+ if (!version.get()) {
+ *error_detail = "Invalid version";
+ return false;
+ }
+
+ // Get the minimum browser version (not required).
+ result->browser_min_version = GetAttribute(updatecheck, "prodversionmin");
+ if (result->browser_min_version.length()) {
+ scoped_ptr<Version> browser_min_version(
+ Version::GetVersionFromString(result->browser_min_version));
+ if (!browser_min_version.get()) {
+ *error_detail = "Invalid prodversionmin";
+ 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");
+
+ return true;
+}
+
+
+bool UpdateManifest::Parse(const std::string& manifest_xml) {
+ results_.resize(0);
+
+ if (manifest_xml.length() < 1) {
+ 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(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 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, &current, &error)) {
+ ParseError(error.c_str());
+ return false;
+ }
+ results_.push_back(current);
+ }
+
+ return true;
+}
diff --git a/chrome/common/extensions/update_manifest.h b/chrome/common/extensions/update_manifest.h
new file mode 100644
index 0000000..c1e5b2b
--- /dev/null
+++ b/chrome/common/extensions/update_manifest.h
@@ -0,0 +1,71 @@
+// Copyright (c) 2009 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.
+
+#ifndef CHROME_COMMON_EXTENSIONS_UPDATE_MANIFEST_H_
+#define CHROME_COMMON_EXTENSIONS_UPDATE_MANIFEST_H_
+
+#include <string>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/scoped_ptr.h"
+#include "base/values.h"
+#include "base/version.h"
+#include "googleurl/src/gurl.h"
+
+class Version;
+
+class UpdateManifest {
+ public:
+
+ // An update manifest looks like this:
+ //
+ // <?xml version='1.0' encoding='UTF-8'?>
+ // <gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>
+ // <app appid='12345'>
+ // <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'
+ // version='1.2.3.4' prodversionmin='2.0.143.0'
+ // hash="12345"/>
+ // </app>
+ // </gupdate>
+ //
+ // The "appid" attribute of the <app> tag refers to the unique id of the
+ // extension. The "codebase" attribute of the <updatecheck> tag is the url to
+ // fetch the updated crx file, and the "prodversionmin" attribute refers to
+ // the minimum version of the chrome browser that the update applies to.
+
+ // The result of parsing one <app> tag in an xml update check manifest.
+ struct Result {
+ std::string extension_id;
+ std::string version;
+ std::string browser_min_version;
+ std::string package_hash;
+ GURL crx_url;
+ };
+
+ typedef std::vector<Result> ResultList;
+
+ UpdateManifest();
+ ~UpdateManifest();
+
+ // Parses an update manifest xml string into Result data. Returns a bool
+ // indicating success or failure. On success, the results are available by
+ // calling results(), and on failure, the explanation for why is available
+ // by calling errors().
+ bool Parse(const std::string& manifest_xml);
+
+ const ResultList& results() { return results_; }
+ const std::string& errors() { return errors_; }
+
+ private:
+ ResultList results_;
+
+ std::string errors_;
+
+ // Helper function that adds parse error details to our errors_ string.
+ void ParseError(const char* details, ...);
+};
+
+#endif // CHROME_COMMON_EXTENSIONS_UPDATE_MANIFEST_H_
+
diff --git a/chrome/common/extensions/update_manifest_unittest.cc b/chrome/common/extensions/update_manifest_unittest.cc
new file mode 100644
index 0000000..7adc17f
--- /dev/null
+++ b/chrome/common/extensions/update_manifest_unittest.cc
@@ -0,0 +1,119 @@
+// Copyright (c) 2009 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 "base/scoped_vector.h"
+#include "base/version.h"
+#include "chrome/common/extensions/update_manifest.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "libxml/globals.h"
+
+static const char* kValidXml =
+"<?xml version='1.0' encoding='UTF-8'?>"
+"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
+" <app appid='12345'>"
+" <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
+" version='1.2.3.4' prodversionmin='2.0.143.0' />"
+" </app>"
+"</gupdate>";
+
+const char *valid_xml_with_hash =
+"<?xml version='1.0' encoding='UTF-8'?>"
+"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
+" <app appid='12345'>"
+" <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
+" version='1.2.3.4' prodversionmin='2.0.143.0' "
+" hash='1234'/>"
+" </app>"
+"</gupdate>";
+
+static const char* kMissingAppId =
+"<?xml version='1.0'?>"
+"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
+" <app>"
+" <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
+" version='1.2.3.4' />"
+" </app>"
+"</gupdate>";
+
+static const char* kInvalidCodebase =
+"<?xml version='1.0'?>"
+"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
+" <app appid='12345' status='ok'>"
+" <updatecheck codebase='example.com/extension_1.2.3.4.crx'"
+" version='1.2.3.4' />"
+" </app>"
+"</gupdate>";
+
+static const char* kMissingVersion =
+"<?xml version='1.0'?>"
+"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
+" <app appid='12345' status='ok'>"
+" <updatecheck codebase='http://example.com/extension_1.2.3.4.crx' />"
+" </app>"
+"</gupdate>";
+
+static const char* kInvalidVersion =
+"<?xml version='1.0'?>"
+"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
+" <app appid='12345' status='ok'>"
+" <updatecheck codebase='http://example.com/extension_1.2.3.4.crx' "
+" version='1.2.3.a'/>"
+" </app>"
+"</gupdate>";
+
+static const char* kUsesNamespacePrefix =
+"<?xml version='1.0' encoding='UTF-8'?>"
+"<g:gupdate xmlns:g='http://www.google.com/update2/response' protocol='2.0'>"
+" <g:app appid='12345'>"
+" <g:updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
+" version='1.2.3.4' prodversionmin='2.0.143.0' />"
+" </g:app>"
+"</g:gupdate>";
+
+// Includes unrelated <app> tags from other xml namespaces - this should
+// not cause problems.
+static const char* kSimilarTagnames =
+"<?xml version='1.0' encoding='UTF-8'?>"
+"<gupdate xmlns='http://www.google.com/update2/response'"
+" xmlns:a='http://a' protocol='2.0'>"
+" <a:app/>"
+" <b:app xmlns:b='http://b' />"
+" <app appid='12345'>"
+" <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
+" version='1.2.3.4' prodversionmin='2.0.143.0' />"
+" </app>"
+"</gupdate>";
+
+
+TEST(ExtensionUpdateManifestTest, TestUpdateManifest) {
+ UpdateManifest parser;
+
+ // Test parsing of a number of invalid xml cases
+ EXPECT_FALSE(parser.Parse(""));
+ EXPECT_FALSE(parser.Parse(kMissingAppId));
+ EXPECT_FALSE(parser.Parse(kInvalidCodebase));
+ EXPECT_FALSE(parser.Parse(kMissingVersion));
+ EXPECT_FALSE(parser.Parse(kInvalidVersion));
+
+ // Parse some valid XML, and check that all params came out as expected
+ EXPECT_TRUE(parser.Parse(kValidXml));
+ EXPECT_FALSE(parser.results().empty());
+ const UpdateManifest::Result* firstResult = &parser.results().at(0);
+ EXPECT_EQ(GURL("http://example.com/extension_1.2.3.4.crx"),
+ firstResult->crx_url);
+
+ EXPECT_EQ("1.2.3.4", firstResult->version);
+ EXPECT_EQ("2.0.143.0", firstResult->browser_min_version);
+
+ // Parse some xml that uses namespace prefixes.
+ EXPECT_TRUE(parser.Parse(kUsesNamespacePrefix));
+ EXPECT_TRUE(parser.Parse(kSimilarTagnames));
+ xmlCleanupGlobals();
+
+ // Parse xml with hash value
+ EXPECT_TRUE(parser.Parse(valid_xml_with_hash));
+ EXPECT_FALSE(parser.results().empty());
+ firstResult = &parser.results().at(0);
+ EXPECT_EQ("1234", firstResult->package_hash);
+}
diff --git a/chrome/common/render_messages.h b/chrome/common/render_messages.h
index 5dce342..f89bcf4 100644
--- a/chrome/common/render_messages.h
+++ b/chrome/common/render_messages.h
@@ -16,6 +16,7 @@
#include "chrome/browser/renderer_host/resource_handler.h"
#include "chrome/common/common_param_traits.h"
#include "chrome/common/css_colors.h"
+#include "chrome/common/extensions/update_manifest.h"
#include "chrome/common/filter_policy.h"
#include "chrome/common/modal_dialog_event.h"
#include "chrome/common/page_transition_types.h"
@@ -2004,6 +2005,39 @@ struct SimilarTypeTraits<ViewType::Type> {
typedef int Type;
};
+// Traits for UpdateManifest::Result.
+template <>
+struct ParamTraits<UpdateManifest::Result> {
+ typedef UpdateManifest::Result param_type;
+ static void Write(Message* m, const param_type& p) {
+ WriteParam(m, p.extension_id);
+ WriteParam(m, p.version);
+ WriteParam(m, p.browser_min_version);
+ WriteParam(m, p.package_hash);
+ WriteParam(m, p.crx_url);
+ }
+ static bool Read(const Message* m, void** iter, param_type* p) {
+ return ReadParam(m, iter, &p->extension_id) &&
+ ReadParam(m, iter, &p->version) &&
+ ReadParam(m, iter, &p->browser_min_version) &&
+ ReadParam(m, iter, &p->package_hash) &&
+ ReadParam(m, iter, &p->crx_url);
+ }
+ static void Log(const param_type& p, std::wstring* l) {
+ l->append(L"(");
+ LogParam(p.extension_id, l);
+ l->append(L", ");
+ LogParam(p.version, l);
+ l->append(L", ");
+ LogParam(p.browser_min_version, l);
+ l->append(L", ");
+ LogParam(p.package_hash, l);
+ l->append(L", ");
+ LogParam(p.crx_url, l);
+ l->append(L")");
+ }
+};
+
} // namespace IPC
diff --git a/chrome/common/render_messages_internal.h b/chrome/common/render_messages_internal.h
index 2a6b4d4..f94f8e3 100644
--- a/chrome/common/render_messages_internal.h
+++ b/chrome/common/render_messages_internal.h
@@ -19,6 +19,7 @@
#include "base/shared_memory.h"
#include "base/values.h"
#include "chrome/common/css_colors.h"
+#include "chrome/common/extensions/update_manifest.h"
#include "chrome/common/transport_dib.h"
#include "chrome/common/view_types.h"
#include "ipc/ipc_channel_handle.h"
@@ -628,25 +629,10 @@ IPC_BEGIN_MESSAGES(View)
IPC_MESSAGE_ROUTED1(ViewMsg_SetActive,
bool /* active */)
- //---------------------------------------------------------------------------
- // Utility process messages:
- // These are messages from the browser to the utility process. They're here
- // because we ran out of spare message types.
-
- // Tell the utility process to unpack the given extension file in its
- // directory and verify that it is valid.
- IPC_MESSAGE_CONTROL1(UtilityMsg_UnpackExtension,
- FilePath /* extension_filename */)
-
// Response message to ViewHostMsg_CreateDedicatedWorker. Sent when the
// worker has started.
IPC_MESSAGE_ROUTED0(ViewMsg_DedicatedWorkerCreated)
- // Tell the utility process to parse the given JSON data and verify its
- // validity.
- IPC_MESSAGE_CONTROL1(UtilityMsg_UnpackWebResource,
- std::string /* JSON data */)
-
// Tell the renderer which browser window it's being attached to.
IPC_MESSAGE_ROUTED1(ViewMsg_UpdateBrowserWindowId,
int /* id of browser window */)
@@ -675,6 +661,25 @@ IPC_BEGIN_MESSAGES(View)
int32 /* the ID of the message we're replying to */,
int64 /* the size of the given DB file */)
+ //---------------------------------------------------------------------------
+ // Utility process messages:
+ // These are messages from the browser to the utility process. They're here
+ // because we ran out of spare message types.
+
+ // Tell the utility process to unpack the given extension file in its
+ // directory and verify that it is valid.
+ IPC_MESSAGE_CONTROL1(UtilityMsg_UnpackExtension,
+ FilePath /* extension_filename */)
+
+ // Tell the utility process to parse the given JSON data and verify its
+ // validity.
+ IPC_MESSAGE_CONTROL1(UtilityMsg_UnpackWebResource,
+ std::string /* JSON data */)
+
+ // Tell the utility process to parse the given xml document.
+ IPC_MESSAGE_CONTROL1(UtilityMsg_ParseUpdateManifest,
+ std::string /* xml document contents */)
+
IPC_END_MESSAGES(View)
@@ -1626,6 +1631,16 @@ IPC_BEGIN_MESSAGES(ViewHost)
IPC_MESSAGE_CONTROL1(UtilityHostMsg_UnpackWebResource_Failed,
std::string /* error_message, if any */)
+ // Reply when the utility process has succeeded in parsing an update manifest
+ // xml document.
+ IPC_MESSAGE_CONTROL1(UtilityHostMsg_ParseUpdateManifest_Succeeded,
+ std::vector<UpdateManifest::Result> /* updates */)
+
+ // Reply when an error occured parsing the update manifest. |error_message|
+ // is a description of what went wrong suitable for logging.
+ IPC_MESSAGE_CONTROL1(UtilityHostMsg_ParseUpdateManifest_Failed,
+ std::string /* error_message, if any */)
+
// Sent by the renderer process to acknowledge receipt of a
// ViewMsg_CSSInsertRequest message and css has been inserted into the frame.
IPC_MESSAGE_ROUTED0(ViewHostMsg_OnCSSInserted)