summaryrefslogtreecommitdiffstats
path: root/chrome/browser/extensions/extension_updater.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/extensions/extension_updater.cc')
-rw-r--r--chrome/browser/extensions/extension_updater.cc560
1 files changed, 560 insertions, 0 deletions
diff --git a/chrome/browser/extensions/extension_updater.cc b/chrome/browser/extensions/extension_updater.cc
new file mode 100644
index 0000000..c14281c
--- /dev/null
+++ b/chrome/browser/extensions/extension_updater.cc
@@ -0,0 +1,560 @@
+// 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/browser/extensions/extension_updater.h"
+
+#include "base/logging.h"
+#include "base/file_util.h"
+#include "base/file_version_info.h"
+#include "base/string_util.h"
+#include "base/thread.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/extensions/extensions_service.h"
+#include "chrome/browser/profile.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_error_reporter.h"
+#include "chrome/common/libxml_utils.h"
+#include "googleurl/src/gurl.h"
+#include "net/url_request/url_request_status.h"
+#include "libxml/tree.h"
+
+const char* ExtensionUpdater::kExpectedGupdateProtocol = "2.0";
+const char* ExtensionUpdater::kExpectedGupdateXmlns =
+ "http://www.google.com/update2/response";
+
+// For sanity checking on update frequency - enforced in release mode only.
+static const int kMinUpdateFrequencySeconds = 60 * 60; // 1 hour
+static const int kMaxUpdateFrequencySeconds = 60 * 60 * 24 * 7; // 7 days
+
+// A utility class to do file handling on the file I/O thread.
+class ExtensionUpdaterFileHandler
+ : public base::RefCountedThreadSafe<ExtensionUpdaterFileHandler> {
+ public:
+ ExtensionUpdaterFileHandler(MessageLoop* updater_loop,
+ MessageLoop* file_io_loop)
+ : updater_loop_(updater_loop), file_io_loop_(file_io_loop) {}
+
+ // Writes crx file data into a tempfile, and calls back the updater.
+ void WriteTempFile(const std::string& extension_id, const std::string& data,
+ scoped_refptr<ExtensionUpdater> updater) {
+ // Make sure we're running in the right thread.
+ DCHECK(MessageLoop::current() == file_io_loop_);
+
+ FilePath path;
+ if (!file_util::CreateTemporaryFileName(&path)) {
+ LOG(WARNING) << "Failed to create temporary file path";
+ return;
+ }
+ if (file_util::WriteFile(path, data.c_str(), data.length()) !=
+ static_cast<int>(data.length())) {
+ // TODO(asargent) - It would be nice to back off updating alltogether if
+ // the disk is full. (http://crbug.com/12763).
+ LOG(ERROR) << "Failed to write temporary file";
+ file_util::Delete(path, false);
+ return;
+ }
+
+ // The ExtensionUpdater is now responsible for cleaning up the temp file
+ // from disk.
+ updater_loop_->PostTask(FROM_HERE, NewRunnableMethod(
+ updater.get(), &ExtensionUpdater::OnCRXFileWritten, extension_id,
+ path));
+ }
+
+ void DeleteFile(const FilePath& path) {
+ DCHECK(MessageLoop::current() == file_io_loop_);
+ if (!file_util::Delete(path, false)) {
+ LOG(WARNING) << "Failed to delete temp file " << path.value();
+ }
+ }
+
+ private:
+ // The MessageLoop we use to call back the ExtensionUpdater.
+ MessageLoop* updater_loop_;
+
+ // The MessageLoop we should be operating on for file operations.
+ MessageLoop* file_io_loop_;
+};
+
+
+ExtensionUpdater::ExtensionUpdater(ExtensionUpdateService* service,
+ int frequency_seconds,
+ MessageLoop* file_io_loop)
+ : service_(service), frequency_seconds_(frequency_seconds),
+ file_io_loop_(file_io_loop),
+ file_handler_(new ExtensionUpdaterFileHandler(MessageLoop::current(),
+ file_io_loop_)) {
+ Init();
+}
+
+void ExtensionUpdater::Init() {
+ // Unless we're in a unit test, expect that the file_io_loop_ is on the
+ // browser file thread.
+ if (g_browser_process->file_thread() != NULL) {
+ DCHECK(file_io_loop_ == g_browser_process->file_thread()->message_loop());
+ }
+
+ DCHECK_GE(frequency_seconds_, 5);
+ DCHECK(frequency_seconds_ <= kMaxUpdateFrequencySeconds);
+#ifdef NDEBUG
+ // In Release mode we enforce that update checks don't happen too often.
+ frequency_seconds_ = std::max(frequency_seconds_, kMinUpdateFrequencySeconds);
+#endif
+ frequency_seconds_ = std::min(frequency_seconds_, kMaxUpdateFrequencySeconds);
+}
+
+ExtensionUpdater::~ExtensionUpdater() {}
+
+void ExtensionUpdater::Start() {
+ // TODO(asargent) Make sure update schedules work across browser restarts by
+ // reading/writing update frequency in profile/settings. *But*, make sure to
+ // wait a reasonable amount of time after browser startup to do the first
+ // check during a given run of the browser. Also remember to update the header
+ // comments when this is implemented. (http://crbug.com/12545).
+ timer_.Start(base::TimeDelta::FromSeconds(frequency_seconds_), this,
+ &ExtensionUpdater::TimerFired);
+}
+
+void ExtensionUpdater::Stop() {
+ timer_.Stop();
+ manifest_fetcher_.reset();
+ extension_fetcher_.reset();
+ manifests_pending_.clear();
+ extensions_pending_.clear();
+}
+
+void ExtensionUpdater::OnURLFetchComplete(
+ const URLFetcher* source, const GURL& url, const URLRequestStatus& status,
+ int response_code, const ResponseCookies& cookies,
+ const std::string& data) {
+
+ if (source == manifest_fetcher_.get()) {
+ OnManifestFetchComplete(url, status, response_code, data);
+ } else if (source == extension_fetcher_.get()) {
+ OnCRXFetchComplete(url, status, response_code, data);
+ } else {
+ NOTREACHED();
+ }
+}
+
+void ExtensionUpdater::OnManifestFetchComplete(const GURL& url,
+ const URLRequestStatus& status,
+ int response_code,
+ const std::string& data) {
+ // 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);
+ }
+ }
+ } else {
+ // TODO(asargent) Do exponential backoff here. (http://crbug.com/12546).
+ LOG(INFO) << "Failed to fetch manifst '" << url.possibly_invalid_spec() <<
+ "' response code:" << response_code;
+ }
+ manifest_fetcher_.reset();
+
+ // If we have any pending manifest requests, fire off the next one.
+ if (!manifests_pending_.empty()) {
+ GURL url = manifests_pending_.front();
+ manifests_pending_.pop_front();
+ StartUpdateCheck(url);
+ }
+}
+
+void ExtensionUpdater::OnCRXFetchComplete(const GURL& url,
+ const URLRequestStatus& status,
+ int response_code,
+ const std::string& data) {
+ if (url != current_extension_fetch_.url) {
+ LOG(ERROR) << "Called with unexpected url:'" << url.spec()
+ << "' expected:'" << current_extension_fetch_.url.spec() << "'";
+ NOTREACHED();
+ } else if (status.status() == URLRequestStatus::SUCCESS &&
+ response_code == 200) {
+ // Successfully fetched - now write crx to a file so we can have the
+ // ExtensionsService install it.
+ file_io_loop_->PostTask(FROM_HERE, NewRunnableMethod(
+ file_handler_.get(), &ExtensionUpdaterFileHandler::WriteTempFile,
+ current_extension_fetch_.id, data, this));
+ } else {
+ // TODO(asargent) do things like exponential backoff, handling
+ // 503 Service Unavailable / Retry-After headers, etc. here.
+ // (http://crbug.com/12546).
+ LOG(INFO) << "Failed to fetch extension '" <<
+ url.possibly_invalid_spec() << "' response code:" << response_code;
+ }
+ extension_fetcher_.reset();
+ current_extension_fetch_ = ExtensionFetch();
+
+ // If there are any pending downloads left, start one.
+ if (extensions_pending_.size() > 0) {
+ ExtensionFetch next = extensions_pending_.front();
+ extensions_pending_.pop_front();
+ FetchUpdatedExtension(next.id, next.url);
+ }
+}
+
+void ExtensionUpdater::OnCRXFileWritten(const std::string& id,
+ const FilePath& path) {
+ // TODO(asargent) - Instead of calling InstallExtension here, we should have
+ // an UpdateExtension method in ExtensionsService and rely on it to check
+ // that the extension is still installed, and still an older version than
+ // what we're handing it.
+ // (http://crbug.com/12764).
+ ExtensionInstallCallback* callback =
+ NewCallback(this, &ExtensionUpdater::OnExtensionInstallFinished);
+ service_->UpdateExtension(id, path, false, callback);
+}
+
+void ExtensionUpdater::OnExtensionInstallFinished(const FilePath& path,
+ Extension* extension) {
+ // Have the file_handler_ delete the temp file on the file I/O thread.
+ file_io_loop_->PostTask(FROM_HERE, NewRunnableMethod(
+ file_handler_.get(), &ExtensionUpdaterFileHandler::DeleteFile, path));
+}
+
+static const char* kURLEncodedEquals = "%3D"; // '='
+static const char* kURLEncodedAnd = "%26"; // '&'
+
+void ExtensionUpdater::TimerFired() {
+ // Generate a set of update urls for loaded extensions.
+ std::set<GURL> urls;
+ const ExtensionList* extensions = service_->extensions();
+ for (ExtensionList::const_iterator iter = extensions->begin();
+ iter != extensions->end(); ++iter) {
+ Extension* extension = (*iter);
+ const GURL& update_url = extension->update_url();
+ if (update_url.is_empty() || extension->id().empty()) {
+ continue;
+ }
+
+ DCHECK(update_url.is_valid());
+ DCHECK(!update_url.has_ref());
+
+ // Append extension information to the url.
+ std::string full_url_string = update_url.spec();
+ full_url_string.append(update_url.has_query() ? "&" : "?");
+ full_url_string.append("x=");
+
+ full_url_string.append("id");
+ full_url_string.append(kURLEncodedEquals);
+ full_url_string.append(extension->id());
+
+ const Version* version = extension->version();
+ DCHECK(version);
+ full_url_string.append("v");
+ full_url_string.append(kURLEncodedEquals);
+ full_url_string.append(version->GetString());
+ full_url_string.append(kURLEncodedAnd);
+
+ GURL full_url(full_url_string);
+ if (!full_url.is_valid()) {
+ LOG(ERROR) << "invalid url: " << full_url.possibly_invalid_spec();
+ NOTREACHED();
+ } else {
+ urls.insert(full_url);
+ }
+ }
+
+ // Now do an update check for each url we found.
+ for (std::set<GURL>::iterator iter = urls.begin(); iter != urls.end();
+ ++iter) {
+ // StartUpdateCheck makes sure the url isn't already downloading or
+ // scheduled, so we don't need to check before calling it.
+ StartUpdateCheck(*iter);
+ }
+ timer_.Reset();
+}
+
+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);
+ ExtensionErrorReporter::GetInstance()->ReportError(message, false);
+}
+
+// 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;
+ }
+ }
+ 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;
+ }
+ }
+ tmp_results.release(results);
+
+ return true;
+}
+
+std::vector<int> ExtensionUpdater::DetermineUpdates(
+ const ParseResultList& possible_updates) {
+
+ std::vector<int> result;
+
+ // This will only get set if one of possible_updates specifies
+ // browser_min_version.
+ scoped_ptr<Version> browser_version;
+
+ for (size_t i = 0; i < possible_updates.size(); i++) {
+ ParseResult* update = possible_updates[i];
+
+ Extension* extension = service_->GetExtensionById(update->extension_id);
+ if (!extension)
+ continue;
+
+ // If the update version is the same or older than what's already installed,
+ // we don't want it.
+ if (update->version.get()->CompareTo(*(extension->version())) <= 0)
+ continue;
+
+ // If the update specifies a browser minimum version, do we qualify?
+ if (update->browser_min_version.get()) {
+ // First determine the browser version if we haven't already.
+ if (!browser_version.get()) {
+ scoped_ptr<FileVersionInfo> version_info(
+ FileVersionInfo::CreateFileVersionInfoForCurrentModule());
+ if (version_info.get()) {
+ browser_version.reset(Version::GetVersionFromString(
+ version_info->product_version()));
+ }
+ }
+ if (browser_version.get() &&
+ update->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 " << extension->id() <<
+ " available, but requires chrome version " <<
+ update->browser_min_version->GetString();
+ continue;
+ }
+ }
+ result.push_back(i);
+ }
+ return result;
+}
+
+void ExtensionUpdater::StartUpdateCheck(const GURL& url) {
+ if (std::find(manifests_pending_.begin(), manifests_pending_.end(), url) !=
+ manifests_pending_.end()) {
+ return; // already scheduled
+ }
+
+ if (manifest_fetcher_.get() != NULL) {
+ if (manifest_fetcher_->url() != url) {
+ manifests_pending_.push_back(url);
+ }
+ } else {
+ manifest_fetcher_.reset(
+ URLFetcher::Create(kManifestFetcherId, url, URLFetcher::GET, this));
+ manifest_fetcher_->set_request_context(Profile::GetDefaultRequestContext());
+ manifest_fetcher_->Start();
+ }
+}
+
+void ExtensionUpdater::FetchUpdatedExtension(const std::string& id, GURL url) {
+ for (std::deque<ExtensionFetch>::const_iterator iter =
+ extensions_pending_.begin();
+ iter != extensions_pending_.end(); ++iter) {
+ if (iter->id == id || iter->url == url) {
+ return; // already scheduled
+ }
+ }
+
+ if (extension_fetcher_.get() != NULL) {
+ if (extension_fetcher_->url() != url) {
+ extensions_pending_.push_back(ExtensionFetch(id, url));
+ }
+ } else {
+ extension_fetcher_.reset(
+ URLFetcher::Create(kExtensionFetcherId, url, URLFetcher::GET, this));
+ extension_fetcher_->set_request_context(
+ Profile::GetDefaultRequestContext());
+ extension_fetcher_->Start();
+ current_extension_fetch_ = ExtensionFetch(id, url);
+ }
+}