summaryrefslogtreecommitdiffstats
path: root/chrome
diff options
context:
space:
mode:
Diffstat (limited to 'chrome')
-rw-r--r--chrome/browser/extensions/extension_updater.cc338
-rw-r--r--chrome/browser/extensions/extension_updater.h59
-rw-r--r--chrome/browser/extensions/extension_updater_unittest.cc217
-rw-r--r--chrome/browser/extensions/extensions_service.cc3
-rw-r--r--chrome/browser/extensions/sandboxed_extension_unpacker.cc3
-rw-r--r--chrome/browser/utility_process_host.cc12
-rw-r--r--chrome/browser/utility_process_host.h13
-rw-r--r--chrome/chrome.gyp3
-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
-rw-r--r--chrome/utility/utility_thread.cc13
-rw-r--r--chrome/utility/utility_thread.h3
15 files changed, 724 insertions, 439 deletions
diff --git a/chrome/browser/extensions/extension_updater.cc b/chrome/browser/extensions/extension_updater.cc
index ad3e6f3..fe19ba5 100644
--- a/chrome/browser/extensions/extension_updater.cc
+++ b/chrome/browser/extensions/extension_updater.cc
@@ -11,22 +11,24 @@
#include "base/file_util.h"
#include "base/file_version_info.h"
#include "base/rand_util.h"
+#include "base/scoped_vector.h"
#include "base/sha2.h"
#include "base/string_util.h"
#include "base/time.h"
#include "base/thread.h"
+#include "base/version.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/extensions/extensions_service.h"
#include "chrome/browser/profile.h"
+#include "chrome/browser/utility_process_host.h"
+#include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/extensions/extension_error_reporter.h"
-#include "chrome/common/libxml_utils.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/pref_service.h"
#include "googleurl/src/gurl.h"
#include "net/base/escape.h"
#include "net/url_request/url_request_status.h"
-#include "libxml/tree.h"
using base::RandDouble;
using base::RandInt;
@@ -36,17 +38,13 @@ using prefs::kExtensionBlacklistUpdateVersion;
using prefs::kLastExtensionsUpdateCheck;
using prefs::kNextExtensionsUpdateCheck;
-const char* ExtensionUpdater::kExpectedGupdateProtocol = "2.0";
-const char* ExtensionUpdater::kExpectedGupdateXmlns =
- "http://www.google.com/update2/response";
-
// NOTE: HTTPS is used here to ensure the response from omaha can be trusted.
// The response contains a url for fetching the blacklist and a hash value
// for validation.
const char* ExtensionUpdater::kBlacklistUpdateUrl =
"https://clients2.google.com/service/update2/crx";
-// Update AppID for extesnion blacklist.
+// Update AppID for extension blacklist.
const char* ExtensionUpdater::kBlacklistAppID = "com.google.crx.blacklist";
// Wait at least 5 minutes after browser startup before we do any checks. If you
@@ -111,9 +109,10 @@ class ExtensionUpdaterFileHandler
ExtensionUpdater::ExtensionUpdater(ExtensionUpdateService* service,
PrefService* prefs,
int frequency_seconds,
- MessageLoop* file_io_loop)
+ MessageLoop* file_io_loop,
+ MessageLoop* io_loop)
: service_(service), frequency_seconds_(frequency_seconds),
- file_io_loop_(file_io_loop), prefs_(prefs),
+ file_io_loop_(file_io_loop), io_loop_(io_loop), prefs_(prefs),
file_handler_(new ExtensionUpdaterFileHandler(MessageLoop::current(),
file_io_loop_)) {
Init();
@@ -221,6 +220,77 @@ void ExtensionUpdater::OnURLFetchComplete(
}
}
+// Utility class to handle doing xml parsing in a sandboxed utility process.
+class SafeManifestParser : public UtilityProcessHost::Client {
+ public:
+ SafeManifestParser(const std::string& xml, ExtensionUpdater* updater,
+ MessageLoop* updater_loop, MessageLoop* io_loop)
+ : xml_(xml), updater_loop_(updater_loop), io_loop_(io_loop),
+ updater_(updater) {
+ }
+
+ ~SafeManifestParser() {}
+
+ // Posts a task over to the IO loop to start the parsing of xml_ in a
+ // utility process.
+ void Start() {
+ DCHECK(MessageLoop::current() == updater_loop_);
+ io_loop_->PostTask(FROM_HERE,
+ NewRunnableMethod(this, &SafeManifestParser::ParseInSandbox,
+ g_browser_process->resource_dispatcher_host()));
+ }
+
+ // Creates the sandboxed utility process and tells it to start parsing.
+ void ParseInSandbox(ResourceDispatcherHost* rdh) {
+ DCHECK(MessageLoop::current() == io_loop_);
+
+ // TODO(asargent) we shouldn't need to do this branch here - instead
+ // UtilityProcessHost should handle it for us. (http://crbug.com/19192)
+ if (rdh && !CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kSingleProcess)) {
+ UtilityProcessHost* host = new UtilityProcessHost(
+ rdh, this, updater_loop_);
+ host->StartUpdateManifestParse(xml_);
+ } else {
+ UpdateManifest manifest;
+ if (manifest.Parse(xml_)) {
+ updater_loop_->PostTask(FROM_HERE, NewRunnableMethod(this,
+ &SafeManifestParser::OnParseUpdateManifestSucceeded,
+ manifest.results()));
+ } else {
+ updater_loop_->PostTask(FROM_HERE, NewRunnableMethod(this,
+ &SafeManifestParser::OnParseUpdateManifestFailed,
+ manifest.errors()));
+ }
+ }
+ }
+
+ // Callback from the utility process when parsing succeeded.
+ virtual void OnParseUpdateManifestSucceeded(
+ const UpdateManifest::ResultList& list) {
+ DCHECK(MessageLoop::current() == updater_loop_);
+ updater_->HandleManifestResults(list);
+ }
+
+ // Callback from the utility process when parsing failed.
+ virtual void OnParseUpdateManifestFailed(const std::string& error_message) {
+ DCHECK(MessageLoop::current() == updater_loop_);
+ LOG(WARNING) << "Error parsing update manifest:\n" << error_message;
+ }
+
+ private:
+ const std::string& xml_;
+
+ // The MessageLoop we use to call back the ExtensionUpdater.
+ MessageLoop* updater_loop_;
+
+ // The MessageLoop where we create the utility process.
+ MessageLoop* io_loop_;
+
+ scoped_refptr<ExtensionUpdater> updater_;
+};
+
+
void ExtensionUpdater::OnManifestFetchComplete(const GURL& url,
const URLRequestStatus& status,
int response_code,
@@ -228,17 +298,9 @@ void ExtensionUpdater::OnManifestFetchComplete(const GURL& url,
// We want to try parsing the manifest, and if it indicates updates are
// available, we want to fire off requests to fetch those updates.
if (status.status() == URLRequestStatus::SUCCESS && response_code == 200) {
- ScopedVector<ParseResult> parsed;
- // TODO(asargent) - We should do the xml parsing in a sandboxed process.
- // (http://crbug.com/12677).
- if (Parse(data, &parsed.get())) {
- std::vector<int> updates = DetermineUpdates(parsed.get());
- for (size_t i = 0; i < updates.size(); i++) {
- ParseResult* update = parsed[updates[i]];
- FetchUpdatedExtension(update->extension_id, update->crx_url,
- update->package_hash, update->version->GetString());
- }
- }
+ scoped_refptr<SafeManifestParser> safe_parser =
+ new SafeManifestParser(data, this, MessageLoop::current(), io_loop_);
+ safe_parser->Start();
} else {
// TODO(asargent) Do exponential backoff here. (http://crbug.com/12546).
LOG(INFO) << "Failed to fetch manifst '" << url.possibly_invalid_spec() <<
@@ -254,6 +316,16 @@ void ExtensionUpdater::OnManifestFetchComplete(const GURL& url,
}
}
+void ExtensionUpdater::HandleManifestResults(
+ const UpdateManifest::ResultList& results) {
+ std::vector<int> updates = DetermineUpdates(results);
+ for (size_t i = 0; i < updates.size(); i++) {
+ const UpdateManifest::Result* update = &(results.at(updates[i]));
+ FetchUpdatedExtension(update->extension_id, update->crx_url,
+ update->package_hash, update->version);
+ }
+}
+
void ExtensionUpdater::ProcessBlacklist(const std::string& data) {
// Verify sha256 hash value.
char sha256_hash_value[base::SHA256_LENGTH];
@@ -365,7 +437,7 @@ void AppendExtensionInfo(std::string* str, const Extension& extension) {
// Creates a blacklist update url.
GURL ExtensionUpdater::GetBlacklistUpdateUrl(const std::wstring& version) {
std::string blklist_info = StringPrintf("id=%s&v=%s&uc", kBlacklistAppID,
- WideToASCII(version).c_str());
+ WideToASCII(version).c_str());
return GURL(StringPrintf("%s?x=%s", kBlacklistUpdateUrl,
EscapeQueryParamValue(blklist_info).c_str()));
}
@@ -435,206 +507,6 @@ void ExtensionUpdater::TimerFired() {
ScheduleNextCheck(TimeDelta::FromSeconds(frequency_seconds_));
}
-static void ManifestParseError(const char* details, ...) {
- va_list args;
- va_start(args, details);
- std::string message("Extension update manifest parse error: ");
- StringAppendV(&message, details, args);
- LOG(WARNING) << message;
-}
-
-// 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 xml parser state when leaving a scope.
-class ScopedXmlParserCleanup {
- public:
- ScopedXmlParserCleanup() : document_(NULL) {}
- ~ScopedXmlParserCleanup() {
- if (document_)
- xmlFreeDoc(document_);
- xmlCleanupParser();
- }
- void set_document(xmlDocPtr document) {
- document_ = 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;
-}
-
-// This is a separate sub-class so that we can have access to the private
-// ParseResult struct, but avoid making the .h file include the xml api headers.
-class ExtensionUpdater::ParseHelper {
- public:
- // Helper function for ExtensionUpdater::Parse that reads in values for a
- // single <app> tag. It returns a boolean indicating success or failure.
- static bool ParseSingleAppTag(xmlNode* app_node, xmlNs* xml_namespace,
- ParseResult* result) {
- // Read the extension id.
- result->extension_id = GetAttribute(app_node, "appid");
- if (result->extension_id.length() == 0) {
- ManifestParseError("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) {
- ManifestParseError("Too many updatecheck tags on app (expecting only 1)");
- return false;
- }
- if (updates.size() == 0) {
- ManifestParseError("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()) {
- ManifestParseError("Invalid codebase url");
- return false;
- }
-
- // Get the version.
- std::string tmp = GetAttribute(updatecheck, "version");
- if (tmp.length() == 0) {
- ManifestParseError("Missing version for updatecheck");
- return false;
- }
- result->version.reset(Version::GetVersionFromString(tmp));
- if (!result->version.get()) {
- ManifestParseError("Invalid version");
- return false;
- }
-
- // Get the minimum browser version (not required).
- tmp = GetAttribute(updatecheck, "prodversionmin");
- if (tmp.length()) {
- result->browser_min_version.reset(Version::GetVersionFromString(tmp));
- if (!result->browser_min_version.get()) {
- ManifestParseError("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 ExtensionUpdater::Parse(const std::string& manifest_xml,
- ParseResultList* results) {
- std::string xml_errors;
- ScopedXmlErrorFunc error_func(&xml_errors, &XmlErrorFunc);
- ScopedXmlParserCleanup xml_cleanup;
-
- xmlDocPtr document = xmlParseDoc(
- reinterpret_cast<const xmlChar*>(manifest_xml.c_str()));
- if (!document) {
- ManifestParseError(xml_errors.c_str());
- return false;
- }
- xml_cleanup.set_document(document);
-
- xmlNode *root = xmlDocGetRootElement(document);
- if (!root) {
- ManifestParseError("Missing root node");
- return false;
- }
-
- // Look for the required namespace declaration.
- xmlNs* gupdate_ns = GetNamespace(root, kExpectedGupdateXmlns);
- if (!gupdate_ns) {
- ManifestParseError("Missing or incorrect xmlns on gupdate tag");
- return false;
- }
-
- if (!TagNameEquals(root, "gupdate", gupdate_ns)) {
- ManifestParseError("Missing gupdate tag");
- return false;
- }
-
- // Check for the gupdate "protocol" attribute.
- if (GetAttribute(root, "protocol") != kExpectedGupdateProtocol) {
- ManifestParseError("Missing/incorrect protocol on gupdate tag "
- "(expected '%s')", kExpectedGupdateProtocol);
- return false;
- }
-
- // Parse each of the <app> tags.
- ScopedVector<ParseResult> tmp_results;
- std::vector<xmlNode*> apps = GetChildren(root, gupdate_ns, "app");
- for (unsigned int i = 0; i < apps.size(); i++) {
- ParseResult* current = new ParseResult();
- tmp_results.push_back(current);
- if (!ParseHelper::ParseSingleAppTag(apps[i], gupdate_ns, current)) {
- return false;
- }
- }
- results->insert(results->end(), tmp_results.begin(), tmp_results.end());
- tmp_results.get().clear();
-
- return true;
-}
bool ExtensionUpdater::GetExistingVersion(const std::string& id,
std::string* version) {
@@ -652,7 +524,7 @@ bool ExtensionUpdater::GetExistingVersion(const std::string& id,
}
std::vector<int> ExtensionUpdater::DetermineUpdates(
- const ParseResultList& possible_updates) {
+ const std::vector<UpdateManifest::Result>& possible_updates) {
std::vector<int> result;
@@ -661,22 +533,27 @@ std::vector<int> ExtensionUpdater::DetermineUpdates(
scoped_ptr<Version> browser_version;
for (size_t i = 0; i < possible_updates.size(); i++) {
- ParseResult* update = possible_updates[i];
+ const UpdateManifest::Result* update = &possible_updates[i];
std::string version;
if (!GetExistingVersion(update->extension_id, &version)) {
continue;
}
+
// If the update version is the same or older than what's already installed,
// we don't want it.
scoped_ptr<Version> existing_version(
- Version::GetVersionFromString(version));
- if (update->version.get()->CompareTo(*(existing_version.get())) <= 0) {
+ Version::GetVersionFromString(version));
+ scoped_ptr<Version> update_version(
+ Version::GetVersionFromString(update->version));
+
+ if (!update_version.get() ||
+ update_version->CompareTo(*(existing_version.get())) <= 0) {
continue;
}
// If the update specifies a browser minimum version, do we qualify?
- if (update->browser_min_version.get()) {
+ if (update->browser_min_version.length() > 0) {
// First determine the browser version if we haven't already.
if (!browser_version.get()) {
scoped_ptr<FileVersionInfo> version_info(
@@ -686,13 +563,16 @@ std::vector<int> ExtensionUpdater::DetermineUpdates(
version_info->product_version()));
}
}
- if (browser_version.get() &&
- update->browser_min_version->CompareTo(*browser_version.get()) > 0) {
+ scoped_ptr<Version> browser_min_version(
+ Version::GetVersionFromString(update->browser_min_version));
+ if (browser_version.get() && browser_min_version.get() &&
+ browser_min_version->CompareTo(*browser_version.get()) > 0) {
// TODO(asargent) - We may want this to show up in the extensions UI
// eventually. (http://crbug.com/12547).
LOG(WARNING) << "Updated version of extension " << update->extension_id
<< " available, but requires chrome version "
- << update->browser_min_version->GetString();
+ << update->browser_min_version;
+
continue;
}
}
diff --git a/chrome/browser/extensions/extension_updater.h b/chrome/browser/extensions/extension_updater.h
index c450e8e..c82d1c4 100644
--- a/chrome/browser/extensions/extension_updater.h
+++ b/chrome/browser/extensions/extension_updater.h
@@ -12,13 +12,12 @@
#include "base/ref_counted.h"
#include "base/scoped_ptr.h"
#include "base/scoped_temp_dir.h"
-#include "base/scoped_vector.h"
#include "base/task.h"
#include "base/time.h"
#include "base/timer.h"
-#include "base/version.h"
#include "chrome/browser/extensions/extensions_service.h"
#include "chrome/browser/net/url_fetcher.h"
+#include "chrome/common/extensions/update_manifest.h"
#include "googleurl/src/gurl.h"
class Extension;
@@ -32,7 +31,8 @@ class PrefService;
// ExtensionUpdater* updater = new ExtensionUpdater(my_extensions_service,
// pref_service,
// update_frequency_secs,
-// file_io_loop);
+// file_io_loop,
+// io_loop);
// updater.Start();
// ....
// updater.Stop();
@@ -46,7 +46,8 @@ class ExtensionUpdater
ExtensionUpdater(ExtensionUpdateService* service,
PrefService* prefs,
int frequency_seconds,
- MessageLoop* file_io_loop);
+ MessageLoop* file_io_loop,
+ MessageLoop* io_loop);
virtual ~ExtensionUpdater();
@@ -60,34 +61,8 @@ class ExtensionUpdater
private:
friend class ExtensionUpdaterTest;
friend class ExtensionUpdaterFileHandler;
- class ParseHelper;
-
- // 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 hash is only required for blacklist. It is a sha256 hash value against
- // the payload in hex format.
-
- // The result of parsing one <app> tag in an xml update check manifest.
- struct ParseResult {
- std::string extension_id;
- scoped_ptr<Version> version;
- scoped_ptr<Version> browser_min_version;
- std::string package_hash;
- GURL crx_url;
- };
+ friend class SafeManifestParser;
+
// We need to keep track of some information associated with a url
// when doing a fetch.
@@ -107,10 +82,6 @@ class ExtensionUpdater
static const int kManifestFetcherId = 1;
static const int kExtensionFetcherId = 1;
- // Constants for the update manifest.
- static const char* kExpectedGupdateProtocol;
- static const char* kExpectedGupdateXmlns;
-
static const char* kBlacklistUpdateUrl;
static const char* kBlacklistAppID;
@@ -165,20 +136,17 @@ class ExtensionUpdater
void FetchUpdatedExtension(const std::string& id, const GURL& url,
const std::string& hash, const std::string& version);
+ // Once a manifest is parsed, this starts fetches of any relevant crx files.
+ void HandleManifestResults(const UpdateManifest::ResultList& results);
+
// Determines the version of an existing extension.
// Returns true on success and false on failures.
bool GetExistingVersion(const std::string& id, std::string* version);
- typedef std::vector<ParseResult*> ParseResultList;
-
// Given a list of potential updates, returns the indices of the ones that are
// applicable (are actually a new version, etc.) in |result|.
- std::vector<int> DetermineUpdates(const ParseResultList& possible_updates);
-
- // Parses an update manifest xml string into ParseResult data. On success, it
- // inserts new ParseResult items into |result| and returns true. On failure,
- // it returns false and puts nothing into |result|.
- static bool Parse(const std::string& manifest_xml, ParseResultList* result);
+ std::vector<int> DetermineUpdates(
+ const std::vector<UpdateManifest::Result>& possible_updates);
// Creates a blacklist update url.
static GURL GetBlacklistUpdateUrl(const std::wstring& version);
@@ -204,6 +172,9 @@ class ExtensionUpdater
// The MessageLoop where we should do file I/O.
MessageLoop* file_io_loop_;
+ // The IO loop for IPC.
+ MessageLoop* io_loop_;
+
PrefService* prefs_;
scoped_refptr<ExtensionUpdaterFileHandler> file_handler_;
diff --git a/chrome/browser/extensions/extension_updater_unittest.cc b/chrome/browser/extensions/extension_updater_unittest.cc
index 9a55af1..724d736 100644
--- a/chrome/browser/extensions/extension_updater_unittest.cc
+++ b/chrome/browser/extensions/extension_updater_unittest.cc
@@ -6,7 +6,9 @@
#include "base/file_util.h"
#include "base/rand_util.h"
+#include "base/stl_util-inl.h"
#include "base/string_util.h"
+#include "base/thread.h"
#include "chrome/browser/extensions/extension_updater.h"
#include "chrome/browser/extensions/extensions_service.h"
#include "chrome/browser/net/test_url_fetcher_factory.h"
@@ -18,83 +20,8 @@
#include "net/base/escape.h"
#include "net/url_request/url_request_status.h"
#include "testing/gtest/include/gtest/gtest.h"
+#include "libxml/globals.h"
-const char *valid_xml =
-"<?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>";
-
-const char* missing_appid =
-"<?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>";
-
-const char* invalid_codebase =
-"<?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>";
-
-const char* missing_version =
-"<?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>";
-
-const char* invalid_version =
-"<?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>";
-
-const char *uses_namespace_prefix =
-"<?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.
-const char *similar_tagnames =
-"<?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>";
// Do-nothing base class for further specialized test classes.
@@ -205,12 +132,22 @@ class ServiceForDownloadTests : public MockService {
install_path_ = extension_path;
}
+ virtual Extension* GetExtensionById(const std::string& id) {
+ last_inquired_extension_id_ = id;
+ return NULL;
+ }
+
const std::string& extension_id() { return extension_id_; }
const FilePath& install_path() { return install_path_; }
+ const std::string& last_inquired_extension_id() {
+ return last_inquired_extension_id_;
+ }
private:
std::string extension_id_;
FilePath install_path_;
+ // The last extension_id that GetExtensionById was called with.
+ std::string last_inquired_extension_id_;
};
class ServiceForBlacklistTests : public MockService {
@@ -261,10 +198,7 @@ static void ExtractParameters(const std::string& params,
// inside this class (which is a friend to ExtensionUpdater).
class ExtensionUpdaterTest : public testing::Test {
public:
- static void expectParseFailure(const char *xml) {
- ScopedVector<ExtensionUpdater::ParseResult> result;
- EXPECT_FALSE(ExtensionUpdater::Parse(xml, &result.get()));
- }
+
static void SimulateTimerFired(ExtensionUpdater* updater) {
EXPECT_TRUE(updater->timer_.IsRunning());
@@ -272,52 +206,17 @@ class ExtensionUpdaterTest : public testing::Test {
updater->TimerFired();
}
- // Make a test ParseResult
- static ExtensionUpdater::ParseResult* MakeParseResult(
+ // Adds a Result with the given data to results.
+ static void AddParseResult(
const std::string& id,
const std::string& version,
- const std::string& url) {
- ExtensionUpdater::ParseResult *result = new ExtensionUpdater::ParseResult;
- result->extension_id = id;
- result->version.reset(Version::GetVersionFromString(version));
- result->crx_url = GURL(url);
- return result;
- }
-
- static void TestXmlParsing() {
- ExtensionErrorReporter::Init(false);
-
- // Test parsing of a number of invalid xml cases
- expectParseFailure("");
- expectParseFailure(missing_appid);
- expectParseFailure(invalid_codebase);
- expectParseFailure(missing_version);
- expectParseFailure(invalid_version);
-
- // Parse some valid XML, and check that all params came out as expected
- ScopedVector<ExtensionUpdater::ParseResult> results;
- EXPECT_TRUE(ExtensionUpdater::Parse(valid_xml, &results.get()));
- EXPECT_FALSE(results->empty());
- ExtensionUpdater::ParseResult* firstResult = results->at(0);
- EXPECT_EQ(GURL("http://example.com/extension_1.2.3.4.crx"),
- firstResult->crx_url);
- EXPECT_TRUE(firstResult->package_hash.empty());
- scoped_ptr<Version> version(Version::GetVersionFromString("1.2.3.4"));
- EXPECT_EQ(0, version->CompareTo(*firstResult->version.get()));
- version.reset(Version::GetVersionFromString("2.0.143.0"));
- EXPECT_EQ(0, version->CompareTo(*firstResult->browser_min_version.get()));
-
- // Parse some xml that uses namespace prefixes.
- results.reset();
- EXPECT_TRUE(ExtensionUpdater::Parse(uses_namespace_prefix, &results.get()));
- EXPECT_TRUE(ExtensionUpdater::Parse(similar_tagnames, &results.get()));
-
- // Parse xml with hash value
- results.reset();
- EXPECT_TRUE(ExtensionUpdater::Parse(valid_xml_with_hash, &results.get()));
- EXPECT_FALSE(results->empty());
- firstResult = results->at(0);
- EXPECT_EQ("1234", firstResult->package_hash);
+ const std::string& url,
+ std::vector<UpdateManifest::Result>* results) {
+ UpdateManifest::Result result;
+ result.extension_id = id;
+ result.version = version;
+ result.crx_url = GURL(url);
+ results->push_back(result);
}
static void TestExtensionUpdateCheckRequests() {
@@ -334,7 +233,7 @@ class ExtensionUpdaterTest : public testing::Test {
MessageLoop message_loop;
ScopedTempPrefService prefs;
scoped_refptr<ExtensionUpdater> updater =
- new ExtensionUpdater(&service, prefs.get(), 60*60*24, &message_loop);
+ new ExtensionUpdater(&service, prefs.get(), 60*60*24, NULL, NULL);
updater->Start();
// Tell the update that it's time to do update checks.
@@ -377,7 +276,7 @@ class ExtensionUpdaterTest : public testing::Test {
MessageLoop message_loop;
ScopedTempPrefService prefs;
scoped_refptr<ExtensionUpdater> updater =
- new ExtensionUpdater(&service, prefs.get(), 60*60*24, &message_loop);
+ new ExtensionUpdater(&service, prefs.get(), 60*60*24, NULL, NULL);
updater->Start();
// Tell the updater that it's time to do update checks.
@@ -421,10 +320,10 @@ class ExtensionUpdaterTest : public testing::Test {
ScopedTempPrefService prefs;
scoped_refptr<ExtensionUpdater> updater =
new ExtensionUpdater(&service, prefs.get(), kUpdateFrequencySecs,
- &message_loop);
+ NULL, NULL);
// Check passing an empty list of parse results to DetermineUpdates
- ExtensionUpdater::ParseResultList updates;
+ std::vector<UpdateManifest::Result> updates;
std::vector<int> updateable = updater->DetermineUpdates(updates);
EXPECT_TRUE(updateable.empty());
@@ -433,14 +332,13 @@ class ExtensionUpdaterTest : public testing::Test {
// installed and available at v2.0).
scoped_ptr<Version> one(Version::GetVersionFromString("1.0"));
EXPECT_TRUE(tmp[0]->version()->Equals(*one));
- updates.push_back(MakeParseResult(tmp[0]->id(),
- "1.1", "http://localhost/e1_1.1.crx"));
- updates.push_back(MakeParseResult(tmp[1]->id(),
- tmp[1]->VersionString(), "http://localhost/e2_2.0.crx"));
+ AddParseResult(tmp[0]->id(),
+ "1.1", "http://localhost/e1_1.1.crx", &updates);
+ AddParseResult(tmp[1]->id(),
+ tmp[1]->VersionString(), "http://localhost/e2_2.0.crx", &updates);
updateable = updater->DetermineUpdates(updates);
EXPECT_EQ(1u, updateable.size());
EXPECT_EQ(0, updateable[0]);
- STLDeleteElements(&updates);
STLDeleteElements(&tmp);
}
@@ -449,11 +347,16 @@ class ExtensionUpdaterTest : public testing::Test {
TestURLFetcher* fetcher = NULL;
URLFetcher::set_factory(&factory);
ServiceForDownloadTests service;
- MessageLoop message_loop;
+ MessageLoop ui_loop;
+ base::Thread file_thread("File Thread");
+ file_thread.Start();
+ base::Thread io_thread("IO Thread");
+ io_thread.Start();
ScopedTempPrefService prefs;
scoped_refptr<ExtensionUpdater> updater =
new ExtensionUpdater(&service, prefs.get(), kUpdateFrequencySecs,
- &message_loop);
+ file_thread.message_loop(),
+ io_thread.message_loop());
GURL url1("http://localhost/manifest1");
GURL url2("http://localhost/manifest2");
@@ -463,24 +366,45 @@ class ExtensionUpdaterTest : public testing::Test {
updater->StartUpdateCheck(url1);
updater->StartUpdateCheck(url2);
- std::string manifest_data("invalid xml");
+ std::string invalid_xml = "invalid xml";
fetcher = factory.GetFetcherByID(ExtensionUpdater::kManifestFetcherId);
EXPECT_TRUE(fetcher != NULL && fetcher->delegate() != NULL);
fetcher->delegate()->OnURLFetchComplete(
fetcher, url1, URLRequestStatus(), 200, ResponseCookies(),
- manifest_data);
+ invalid_xml);
// Now that the first request is complete, make sure the second one has
// been started.
+ const std::string 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>";
fetcher = factory.GetFetcherByID(ExtensionUpdater::kManifestFetcherId);
EXPECT_TRUE(fetcher != NULL && fetcher->delegate() != NULL);
fetcher->delegate()->OnURLFetchComplete(
fetcher, url2, URLRequestStatus(), 200, ResponseCookies(),
- manifest_data);
+ kValidXml);
+
+ // This should run the manifest parsing, then we want to make sure that our
+ // service was called with GetExtensionById with the matching id from
+ // kValidXml.
+ file_thread.Stop();
+ io_thread.Stop();
+ ui_loop.RunAllPending();
+ EXPECT_EQ("12345", service.last_inquired_extension_id());
+ xmlCleanupGlobals();
}
static void TestSingleExtensionDownloading() {
- MessageLoop message_loop;
+ MessageLoop ui_loop;
+ base::Thread file_thread("File Thread");
+ file_thread.Start();
+
TestURLFetcherFactory factory;
TestURLFetcher* fetcher = NULL;
URLFetcher::set_factory(&factory);
@@ -488,14 +412,12 @@ class ExtensionUpdaterTest : public testing::Test {
ScopedTempPrefService prefs;
scoped_refptr<ExtensionUpdater> updater =
new ExtensionUpdater(&service, prefs.get(), kUpdateFrequencySecs,
- &message_loop);
+ file_thread.message_loop(), NULL);
GURL test_url("http://localhost/extension.crx");
std::string id = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
-
std::string hash = "";
-
std::string version = "0.0.1";
updater->FetchUpdatedExtension(id, test_url, hash, version);
@@ -508,7 +430,8 @@ class ExtensionUpdaterTest : public testing::Test {
fetcher, test_url, URLRequestStatus(), 200, ResponseCookies(),
extension_data);
- message_loop.RunAllPending();
+ file_thread.Stop();
+ ui_loop.RunAllPending();
// Expect that ExtensionUpdater asked the mock extensions service to install
// a file with the test data for the right id.
@@ -531,7 +454,7 @@ class ExtensionUpdaterTest : public testing::Test {
ScopedTempPrefService prefs;
scoped_refptr<ExtensionUpdater> updater =
new ExtensionUpdater(&service, prefs.get(), kUpdateFrequencySecs,
- &message_loop);
+ NULL, NULL);
prefs.get()->
RegisterStringPref(prefs::kExtensionBlacklistUpdateVersion, L"0");
GURL test_url("http://localhost/extension.crx");
@@ -574,7 +497,7 @@ class ExtensionUpdaterTest : public testing::Test {
ScopedTempPrefService prefs;
scoped_refptr<ExtensionUpdater> updater =
new ExtensionUpdater(&service, prefs.get(), kUpdateFrequencySecs,
- &message_loop);
+ &message_loop, NULL);
GURL url1("http://localhost/extension1.crx");
GURL url2("http://localhost/extension2.crx");
@@ -630,10 +553,6 @@ class ExtensionUpdaterTest : public testing::Test {
// actual test code to live in ExtenionUpdaterTest methods instead of TEST_F
// subclasses where friendship with ExtenionUpdater is not inherited.
-TEST(ExtensionUpdaterTest, TestXmlParsing) {
- ExtensionUpdaterTest::TestXmlParsing();
-}
-
TEST(ExtensionUpdaterTest, TestExtensionUpdateCheckRequests) {
ExtensionUpdaterTest::TestExtensionUpdateCheckRequests();
}
diff --git a/chrome/browser/extensions/extensions_service.cc b/chrome/browser/extensions/extensions_service.cc
index 89ef1fc..f571c08 100644
--- a/chrome/browser/extensions/extensions_service.cc
+++ b/chrome/browser/extensions/extensions_service.cc
@@ -8,6 +8,7 @@
#include "base/file_util.h"
#include "base/string_util.h"
#include "base/values.h"
+#include "chrome/browser/chrome_thread.h"
#include "chrome/browser/extensions/crx_installer.h"
#include "chrome/browser/extensions/extension_browser_event_router.h"
#include "chrome/browser/extensions/extension_file_util.h"
@@ -77,7 +78,7 @@ ExtensionsService::ExtensionsService(Profile* profile,
switches::kExtensionsUpdateFrequency)));
}
updater_ = new ExtensionUpdater(this, prefs, update_frequency,
- backend_loop_);
+ backend_loop_, ChromeThread::GetMessageLoop(ChromeThread::IO));
}
backend_ = new ExtensionsServiceBackend(install_directory_, frontend_loop);
diff --git a/chrome/browser/extensions/sandboxed_extension_unpacker.cc b/chrome/browser/extensions/sandboxed_extension_unpacker.cc
index 7f6e5ae..e1d600c 100644
--- a/chrome/browser/extensions/sandboxed_extension_unpacker.cc
+++ b/chrome/browser/extensions/sandboxed_extension_unpacker.cc
@@ -60,6 +60,9 @@ void SandboxedExtensionUnpacker::Start() {
// If we are supposed to use a subprocess, copy the crx to the temp directory
// and kick off the subprocess.
+ //
+ // TODO(asargent) we shouldn't need to do this branch here - instead
+ // UtilityProcessHost should handle it for us. (http://crbug.com/19192)
if (rdh_ &&
!CommandLine::ForCurrentProcess()->HasSwitch(switches::kSingleProcess)) {
ChromeThread::GetMessageLoop(ChromeThread::IO)->PostTask(FROM_HERE,
diff --git a/chrome/browser/utility_process_host.cc b/chrome/browser/utility_process_host.cc
index 285197a..32ef600 100644
--- a/chrome/browser/utility_process_host.cc
+++ b/chrome/browser/utility_process_host.cc
@@ -51,6 +51,14 @@ bool UtilityProcessHost::StartWebResourceUnpacker(const std::string& data) {
return true;
}
+bool UtilityProcessHost::StartUpdateManifestParse(const std::string& xml) {
+ if (!StartProcess(FilePath()))
+ return false;
+
+ Send(new UtilityMsg_ParseUpdateManifest(xml));
+ return true;
+}
+
std::wstring UtilityProcessHost::GetUtilityProcessCmd() {
return GetChildPath();
}
@@ -142,5 +150,9 @@ void UtilityProcessHost::Client::OnMessageReceived(
Client::OnUnpackWebResourceSucceeded)
IPC_MESSAGE_HANDLER(UtilityHostMsg_UnpackWebResource_Failed,
Client::OnUnpackWebResourceFailed)
+ IPC_MESSAGE_HANDLER(UtilityHostMsg_ParseUpdateManifest_Succeeded,
+ Client::OnParseUpdateManifestSucceeded)
+ IPC_MESSAGE_HANDLER(UtilityHostMsg_ParseUpdateManifest_Failed,
+ Client::OnParseUpdateManifestFailed)
IPC_END_MESSAGE_MAP_EX()
}
diff --git a/chrome/browser/utility_process_host.h b/chrome/browser/utility_process_host.h
index 329f856..c52c424 100644
--- a/chrome/browser/utility_process_host.h
+++ b/chrome/browser/utility_process_host.h
@@ -11,6 +11,7 @@
#include "base/ref_counted.h"
#include "base/task.h"
#include "chrome/common/child_process_host.h"
+#include "chrome/common/extensions/update_manifest.h"
#include "ipc/ipc_channel.h"
class CommandLine;
@@ -54,6 +55,15 @@ class UtilityProcessHost : public ChildProcessHost {
virtual void OnUnpackWebResourceFailed(
const std::string& error_message) {}
+ // Called when an update manifest xml file was successfully parsed.
+ virtual void OnParseUpdateManifestSucceeded(
+ const UpdateManifest::ResultList& list) {}
+
+ // Called when an update manifest xml file failed parsing. |error_message|
+ // contains details suitable for logging.
+ virtual void OnParseUpdateManifestFailed(
+ const std::string& error_message) {}
+
private:
friend class UtilityProcessHost;
void OnMessageReceived(const IPC::Message& message);
@@ -79,6 +89,9 @@ class UtilityProcessHost : public ChildProcessHost {
// web resource server format(s).
bool StartWebResourceUnpacker(const std::string& data);
+ // Start parsing an extensions auto-update manifest xml file.
+ bool StartUpdateManifestParse(const std::string& xml);
+
protected:
// Allow these methods to be overridden for tests.
virtual std::wstring GetUtilityProcessCmd();
diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp
index f73c1bc..1d39eca 100644
--- a/chrome/chrome.gyp
+++ b/chrome/chrome.gyp
@@ -379,6 +379,8 @@
'common/extensions/extension_error_utils.h',
'common/extensions/extension_unpacker.cc',
'common/extensions/extension_unpacker.h',
+ 'common/extensions/update_manifest.cc',
+ 'common/extensions/update_manifest.h',
'common/extensions/url_pattern.cc',
'common/extensions/url_pattern.h',
'common/extensions/user_script.cc',
@@ -3988,6 +3990,7 @@
'common/chrome_plugin_unittest.cc',
'common/common_param_traits_unittest.cc',
'common/extensions/extension_unittest.cc',
+ 'common/extensions/update_manifest_unittest.cc',
'common/extensions/url_pattern_unittest.cc',
'common/extensions/user_script_unittest.cc',
'common/important_file_writer_unittest.cc',
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)
diff --git a/chrome/utility/utility_thread.cc b/chrome/utility/utility_thread.cc
index dc9f965..9c6912c 100644
--- a/chrome/utility/utility_thread.cc
+++ b/chrome/utility/utility_thread.cc
@@ -6,10 +6,11 @@
#include "base/file_util.h"
#include "base/values.h"
-#include "chrome/common/web_resource/web_resource_unpacker.h"
#include "chrome/common/child_process.h"
#include "chrome/common/extensions/extension_unpacker.h"
+#include "chrome/common/extensions/update_manifest.h"
#include "chrome/common/render_messages.h"
+#include "chrome/common/web_resource/web_resource_unpacker.h"
UtilityThread::UtilityThread() {
ChildProcess::current()->AddRefProcess();
@@ -22,6 +23,7 @@ void UtilityThread::OnControlMessageReceived(const IPC::Message& msg) {
IPC_BEGIN_MESSAGE_MAP(UtilityThread, msg)
IPC_MESSAGE_HANDLER(UtilityMsg_UnpackExtension, OnUnpackExtension)
IPC_MESSAGE_HANDLER(UtilityMsg_UnpackWebResource, OnUnpackWebResource)
+ IPC_MESSAGE_HANDLER(UtilityMsg_ParseUpdateManifest, OnParseUpdateManifest)
IPC_END_MESSAGE_MAP()
}
@@ -53,3 +55,12 @@ void UtilityThread::OnUnpackWebResource(const std::string& resource_data) {
ChildProcess::current()->ReleaseProcess();
}
+void UtilityThread::OnParseUpdateManifest(const std::string& xml) {
+ UpdateManifest manifest;
+ if (!manifest.Parse(xml)) {
+ Send(new UtilityHostMsg_ParseUpdateManifest_Failed(manifest.errors()));
+ } else {
+ Send(new UtilityHostMsg_ParseUpdateManifest_Succeeded(manifest.results()));
+ }
+ ChildProcess::current()->ReleaseProcess();
+}
diff --git a/chrome/utility/utility_thread.h b/chrome/utility/utility_thread.h
index 4fad53e..b5e2ae2 100644
--- a/chrome/utility/utility_thread.h
+++ b/chrome/utility/utility_thread.h
@@ -30,6 +30,9 @@ class UtilityThread : public ChildThread {
// IPC messages for web resource service.
void OnUnpackWebResource(const std::string& resource_data);
+ // IPC for parsing an extensions auto-update manifest xml file.
+ void OnParseUpdateManifest(const std::string& xml);
+
DISALLOW_COPY_AND_ASSIGN(UtilityThread);
};