diff options
-rw-r--r-- | chrome/browser/extensions/extension_updater.cc | 560 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_updater.h | 181 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_updater_unittest.cc | 448 | ||||
-rw-r--r-- | chrome/browser/extensions/extensions_service.cc | 58 | ||||
-rw-r--r-- | chrome/browser/extensions/extensions_service.h | 23 | ||||
-rw-r--r-- | chrome/browser/extensions/extensions_service_unittest.cc | 31 | ||||
-rw-r--r-- | chrome/browser/profile.cc | 3 | ||||
-rw-r--r-- | chrome/chrome.gyp | 3 | ||||
-rw-r--r-- | chrome/common/chrome_switches.cc | 3 | ||||
-rw-r--r-- | chrome/common/chrome_switches.h | 1 |
10 files changed, 1273 insertions, 38 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); + } +} diff --git a/chrome/browser/extensions/extension_updater.h b/chrome/browser/extensions/extension_updater.h new file mode 100644 index 0000000..5af2c03 --- /dev/null +++ b/chrome/browser/extensions/extension_updater.h @@ -0,0 +1,181 @@ +// 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_BROWSER_EXTENSIONS_EXTENSION_UPDATER_H_ +#define CHROME_BROWSER_EXTENSIONS_EXTENSION_UPDATER_H_ + +#include <deque> +#include <string> +#include <vector> + +#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 "googleurl/src/gurl.h" + +class Extension; +class ExtensionUpdaterTest; +class MessageLoop; +class ExtensionUpdaterFileHandler; + +// A class for doing auto-updates of installed Extensions. Used like this: +// +// ExtensionUpdater* updater = new ExtensionUpdater(my_extensions_service, +// update_frequency_secs, +// file_io_loop); +// updater.Start(); +// .... +// updater.Stop(); +class ExtensionUpdater + : public URLFetcher::Delegate, + public base::RefCountedThreadSafe<ExtensionUpdater> { + public: + // Holds a pointer to the passed |service|, using it for querying installed + // extensions and installing updated ones. The |frequency_seconds| parameter + // controls how often update checks are scheduled. + ExtensionUpdater(ExtensionUpdateService* service, + int frequency_seconds, + MessageLoop* file_io_loop); + + virtual ~ExtensionUpdater(); + + // Starts the updater running, with the first check scheduled for + // |frequency_seconds| from now. Eventually ExtensionUpdater will persist the + // time the last check happened, and do the first check |frequency_seconds_| + // from then (potentially adding a short wait if the browser just started). + // (http://crbug.com/12545). + void Start(); + + // Stops the updater running, cancelling any outstanding update manifest and + // crx downloads. Does not cancel any in-progress installs. + void Stop(); + + 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' /> + // </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 ParseResult { + std::string extension_id; + scoped_ptr<Version> version; + scoped_ptr<Version> browser_min_version; + GURL crx_url; + }; + + // We need to keep track of the extension id associated with a url when + // doing a fetch. + struct ExtensionFetch { + std::string id; + GURL url; + ExtensionFetch() : id(""), url() {} + ExtensionFetch(std::string i, GURL u) : id(i), url(u) {} + }; + + // These are needed for unit testing, to help identify the correct mock + // URLFetcher objects. + static const int kManifestFetcherId = 1; + static const int kExtensionFetcherId = 1; + + // Constants for the update manifest. + static const char* kExpectedGupdateProtocol; + static const char* kExpectedGupdateXmlns; + + // Does common work from constructors. + void Init(); + + // URLFetcher::Delegate interface. + virtual void OnURLFetchComplete(const URLFetcher* source, + const GURL& url, + const URLRequestStatus& status, + int response_code, + const ResponseCookies& cookies, + const std::string& data); + + // These do the actual work when a URL fetch completes. + virtual void OnManifestFetchComplete(const GURL& url, + const URLRequestStatus& status, + int response_code, + const std::string& data); + virtual void OnCRXFetchComplete(const GURL& url, + const URLRequestStatus& status, + int response_code, + const std::string& data); + + // Called when a crx file has been written into a temp file, and is ready + // to be installed. + void OnCRXFileWritten(const std::string& id, const FilePath& path); + + // Callback for when ExtensionsService::Install is finished. + void OnExtensionInstallFinished(const FilePath& path, Extension* extension); + + // BaseTimer::ReceiverMethod callback. + void TimerFired(); + + // Begins an update check - called with url to fetch an update manifest. + void StartUpdateCheck(const GURL& url); + + // Begins (or queues up) download of an updated extension. + void FetchUpdatedExtension(const std::string& id, GURL url); + + 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); + + // Outstanding url fetch requests for manifests and updates. + scoped_ptr<URLFetcher> manifest_fetcher_; + scoped_ptr<URLFetcher> extension_fetcher_; + + // Pending manifests and extensions to be fetched when the appropriate fetcher + // is available. + std::deque<GURL> manifests_pending_; + std::deque<ExtensionFetch> extensions_pending_; + + // The extension currently being fetched (if any). + ExtensionFetch current_extension_fetch_; + + // Pointer back to the service that owns this ExtensionUpdater. + ExtensionUpdateService* service_; + + base::RepeatingTimer<ExtensionUpdater> timer_; + int frequency_seconds_; + + // The MessageLoop where we should do file I/O. + MessageLoop* file_io_loop_; + + scoped_refptr<ExtensionUpdaterFileHandler> file_handler_; + + DISALLOW_COPY_AND_ASSIGN(ExtensionUpdater); +}; + +#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_UPDATER_H_ diff --git a/chrome/browser/extensions/extension_updater_unittest.cc b/chrome/browser/extensions/extension_updater_unittest.cc new file mode 100644 index 0000000..6b22617 --- /dev/null +++ b/chrome/browser/extensions/extension_updater_unittest.cc @@ -0,0 +1,448 @@ +// 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/file_util.h" +#include "base/string_util.h" +#include "chrome/browser/extensions/extension_updater.h" +#include "chrome/browser/extensions/extensions_service.h" +#include "chrome/browser/net/test_url_fetcher_factory.h" +#include "chrome/common/extensions/extension.h" +#include "chrome/common/extensions/extension_error_reporter.h" +#include "net/url_request/url_request_status.h" +#include "testing/gtest/include/gtest/gtest.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* 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. +class MockService : public ExtensionUpdateService { + public: + MockService() {} + virtual ~MockService() {} + + virtual const ExtensionList* extensions() const { + EXPECT_TRUE(false); + return NULL; + } + + virtual void UpdateExtension(const std::string& id, + const FilePath& extension_path, + bool alert_on_error, + ExtensionInstallCallback* callback) { + EXPECT_TRUE(false); + } + + virtual Extension* GetExtensionById(const std::string& id) { + EXPECT_TRUE(false); + return NULL; + } + + private: + DISALLOW_COPY_AND_ASSIGN(MockService); +}; + + +const char* kIdPrefix = "000000000000000000000000000000000000000"; + +// Creates test extensions and inserts them into list. The name and +// version are all based on their index. +void CreateTestExtensions(int count, ExtensionList *list) { + for (int i = 1; i <= count; i++) { + DictionaryValue input; + Extension* e = new Extension(); + input.SetString(Extension::kVersionKey, StringPrintf("%d.0.0.0", i)); + input.SetString(Extension::kNameKey, StringPrintf("Extension %d", i)); + std::string error; + EXPECT_TRUE(e->InitFromValue(input, false, &error)); + list->push_back(e); + } +} + +class ServiceForManifestTests : public MockService { + public: + ServiceForManifestTests() {} + + virtual ~ServiceForManifestTests() {} + + virtual Extension* GetExtensionById(const std::string& id) { + for (ExtensionList::iterator iter = extensions_.begin(); + iter != extensions_.end(); ++iter) { + if ((*iter)->id() == id) { + return *iter; + } + } + return NULL; + } + + virtual const ExtensionList* extensions() const { return &extensions_; } + + void set_extensions(ExtensionList extensions) { + extensions_ = extensions; + } + + private: + ExtensionList extensions_; +}; + +class ServiceForDownloadTests : public MockService { + public: + explicit ServiceForDownloadTests() : callback_(NULL) {} + virtual ~ServiceForDownloadTests() { + // expect that cleanup happened via FireInstallCallback + EXPECT_EQ(NULL, callback_); + EXPECT_TRUE(install_path_.empty()); + } + + virtual void UpdateExtension(const std::string& id, + const FilePath& extension_path, + bool alert_on_error, + ExtensionInstallCallback* callback) { + // Since this mock only has support for one oustanding update, ensure + // that the callback doesn't need to be run. + EXPECT_TRUE(install_path_.empty()); + EXPECT_EQ(NULL, callback_); + + extension_id_ = id; + install_path_ = extension_path; + callback_ = callback; + } + + void FireInstallCallback() { + EXPECT_TRUE(callback_ != NULL); + callback_->Run(install_path_, static_cast<Extension*>(NULL)); + delete callback_; + callback_ = NULL; + install_path_ = FilePath(); + } + + const std::string& extension_id() { return extension_id_; } + const FilePath& install_path() { return install_path_; } + + private: + std::string extension_id_; + FilePath install_path_; + ExtensionInstallCallback* callback_; +}; + +static const int kUpdateFrequencySecs = 15; + +// All of our tests that need to use private APIs of ExtensionUpdater live +// 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())); + } + + // Make a test ParseResult + static ExtensionUpdater::ParseResult* MakeParseResult(std::string id, + std::string version, + 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); + 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())); + } + + static void TestDetermineUpdates() { + // Create a set of test extensions + ServiceForManifestTests service; + ExtensionList tmp; + CreateTestExtensions(3, &tmp); + service.set_extensions(tmp); + + MessageLoop message_loop; + scoped_refptr<ExtensionUpdater> updater = + new ExtensionUpdater(&service, kUpdateFrequencySecs, &message_loop); + + // Check passing an empty list of parse results to DetermineUpdates + ExtensionUpdater::ParseResultList updates; + std::vector<int> updateable = updater->DetermineUpdates(updates); + EXPECT_TRUE(updateable.empty()); + + // Create two updates - expect that DetermineUpdates will return the first + // one (v1.0 installed, v1.1 available) but not the second one (both + // 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")); + updateable = updater->DetermineUpdates(updates); + EXPECT_EQ(1u, updateable.size()); + EXPECT_EQ(0, updateable[0]); + STLDeleteElements(&updates); + } + + static void TestMultipleManifestDownloading() { + TestURLFetcherFactory factory; + TestURLFetcher* fetcher = NULL; + URLFetcher::set_factory(&factory); + ServiceForDownloadTests service; + MessageLoop message_loop; + scoped_refptr<ExtensionUpdater> updater = + new ExtensionUpdater(&service, kUpdateFrequencySecs, &message_loop); + + GURL url1("http://localhost/manifest1"); + GURL url2("http://localhost/manifest2"); + + // Request 2 update checks - the first should begin immediately and the + // second one should be queued up. + updater->StartUpdateCheck(url1); + updater->StartUpdateCheck(url2); + + std::string manifest_data("invalid xml"); + fetcher = factory.GetFetcherByID(ExtensionUpdater::kManifestFetcherId); + EXPECT_TRUE(fetcher != NULL && fetcher->delegate() != NULL); + fetcher->delegate()->OnURLFetchComplete( + fetcher, url1, URLRequestStatus(), 200, ResponseCookies(), + manifest_data); + + // Now that the first request is complete, make sure the second one has + // been started. + fetcher = factory.GetFetcherByID(ExtensionUpdater::kManifestFetcherId); + EXPECT_TRUE(fetcher != NULL && fetcher->delegate() != NULL); + fetcher->delegate()->OnURLFetchComplete( + fetcher, url2, URLRequestStatus(), 200, ResponseCookies(), + manifest_data); + } + + static void TestSingleExtensionDownloading() { + MessageLoop message_loop; + TestURLFetcherFactory factory; + TestURLFetcher* fetcher = NULL; + URLFetcher::set_factory(&factory); + ServiceForDownloadTests service; + scoped_refptr<ExtensionUpdater> updater = + new ExtensionUpdater(&service, kUpdateFrequencySecs, &message_loop); + + GURL test_url("http://localhost/extension.crx"); + + std::string id = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + + updater->FetchUpdatedExtension(id, test_url); + + // Call back the ExtensionUpdater with a 200 response and some test data + std::string extension_data("whatever"); + fetcher = factory.GetFetcherByID(ExtensionUpdater::kExtensionFetcherId); + EXPECT_TRUE(fetcher != NULL && fetcher->delegate() != NULL); + fetcher->delegate()->OnURLFetchComplete( + fetcher, test_url, URLRequestStatus(), 200, ResponseCookies(), + extension_data); + + message_loop.RunAllPending(); + + // Expect that ExtensionUpdater asked the mock extensions service to install + // a file with the test data for the right id. + EXPECT_EQ(id, service.extension_id()); + FilePath tmpfile_path = service.install_path(); + EXPECT_FALSE(tmpfile_path.empty()); + std::string file_contents; + EXPECT_TRUE(file_util::ReadFileToString(tmpfile_path, &file_contents)); + EXPECT_TRUE(extension_data == file_contents); + + service.FireInstallCallback(); + + message_loop.RunAllPending(); + + // Make sure the temp file is cleaned up + EXPECT_FALSE(file_util::PathExists(tmpfile_path)); + + URLFetcher::set_factory(NULL); + } + + static void TestMultipleExtensionDownloading() { + MessageLoopForUI message_loop; + TestURLFetcherFactory factory; + TestURLFetcher* fetcher = NULL; + URLFetcher::set_factory(&factory); + ServiceForDownloadTests service; + scoped_refptr<ExtensionUpdater> updater = + new ExtensionUpdater(&service, kUpdateFrequencySecs, &message_loop); + + GURL url1("http://localhost/extension1.crx"); + GURL url2("http://localhost/extension2.crx"); + + std::string id1 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + std::string id2 = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; + + // Start two fetches + updater->FetchUpdatedExtension(id1, url1); + updater->FetchUpdatedExtension(id2, url2); + + // Make the first fetch complete. + std::string extension_data1("whatever"); + fetcher = factory.GetFetcherByID(ExtensionUpdater::kExtensionFetcherId); + EXPECT_TRUE(fetcher != NULL && fetcher->delegate() != NULL); + fetcher->delegate()->OnURLFetchComplete( + fetcher, url1, URLRequestStatus(), 200, ResponseCookies(), + extension_data1); + message_loop.RunAllPending(); + + // Expect that the service was asked to do an install with the right data, + // and fire the callback indicating the install finished. + FilePath tmpfile_path = service.install_path(); + EXPECT_FALSE(tmpfile_path.empty()); + EXPECT_EQ(id1, service.extension_id()); + service.FireInstallCallback(); + + // Make sure the tempfile got cleaned up. + message_loop.RunAllPending(); + EXPECT_FALSE(tmpfile_path.empty()); + EXPECT_FALSE(file_util::PathExists(tmpfile_path)); + + // Make sure the second fetch finished and asked the service to do an + // update. + std::string extension_data2("whatever2"); + fetcher = factory.GetFetcherByID(ExtensionUpdater::kExtensionFetcherId); + EXPECT_TRUE(fetcher != NULL && fetcher->delegate() != NULL); + fetcher->delegate()->OnURLFetchComplete( + fetcher, url2, URLRequestStatus(), 200, ResponseCookies(), + extension_data2); + message_loop.RunAllPending(); + EXPECT_EQ(id2, service.extension_id()); + EXPECT_FALSE(service.install_path().empty()); + + // Make sure the correct crx contents were passed for the update call. + std::string file_contents; + EXPECT_TRUE(file_util::ReadFileToString(service.install_path(), + &file_contents)); + EXPECT_TRUE(extension_data2 == file_contents); + service.FireInstallCallback(); + message_loop.RunAllPending(); + } +}; + +// Because we test some private methods of ExtensionUpdater, it's easer for the +// 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, TestDetermineUpdates) { + ExtensionUpdaterTest::TestDetermineUpdates(); +} + +TEST(ExtensionUpdaterTest, TestMultipleManifestDownloading) { + ExtensionUpdaterTest::TestMultipleManifestDownloading(); +} + +TEST(ExtensionUpdaterTest, TestSingleExtensionDownloading) { + ExtensionUpdaterTest::TestSingleExtensionDownloading(); +} + +TEST(ExtensionUpdaterTest, TestMultipleExtensionDownloading) { + ExtensionUpdaterTest::TestMultipleExtensionDownloading(); +} + + +// TODO(asargent) - (http://crbug.com/12780) add tests for: +// -prodversionmin (shouldn't update if browser version too old) +// -manifests & updates arriving out of order / interleaved +// -Profile::GetDefaultRequestContext() returning null +// (should not crash, but just do check later) +// -malformed update url (empty, file://, has query, has a # fragment, etc.) +// -An extension gets uninstalled while updates are in progress (so it doesn't +// "come back from the dead") +// -An extension gets manually updated to v3 while we're downloading v2 (ie +// you don't get downgraded accidentally) +// -An update manifest mentions multiple updates diff --git a/chrome/browser/extensions/extensions_service.cc b/chrome/browser/extensions/extensions_service.cc index 4094ebf..57247cb 100644 --- a/chrome/browser/extensions/extensions_service.cc +++ b/chrome/browser/extensions/extensions_service.cc @@ -25,6 +25,7 @@ #include "chrome/browser/extensions/extension_creator.h" #include "chrome/browser/extensions/extension_browser_event_router.h" #include "chrome/browser/extensions/extension_process_manager.h" +#include "chrome/browser/extensions/extension_updater.h" #include "chrome/browser/extensions/external_extension_provider.h" #include "chrome/browser/extensions/external_pref_extension_provider.h" #include "chrome/browser/profile.h" @@ -88,9 +89,11 @@ class ExtensionsServiceBackend::UnpackerClient UnpackerClient(ExtensionsServiceBackend* backend, const FilePath& extension_path, const std::string& public_key, - const std::string& expected_id) + const std::string& expected_id, + bool silent) : backend_(backend), extension_path_(extension_path), - public_key_(public_key), expected_id_(expected_id), got_response_(false) { + public_key_(public_key), expected_id_(expected_id), got_response_(false), + silent_(silent) { } // Starts the unpack task. We call back to the backend when the task is done, @@ -167,7 +170,7 @@ class ExtensionsServiceBackend::UnpackerClient FilePath extension_dir = temp_extension_path_.DirName().AppendASCII( ExtensionsServiceBackend::kTempExtensionName); backend_->OnExtensionUnpacked(extension_path_, extension_dir, - expected_id_, manifest, images); + expected_id_, manifest, images, silent_); Cleanup(); } @@ -211,6 +214,9 @@ class ExtensionsServiceBackend::UnpackerClient // True if we got a response from the utility process and have cleaned up // already. bool got_response_; + + // True if the install should be done with no confirmation dialog. + bool silent_; }; ExtensionsService::ExtensionsService(Profile* profile, @@ -218,7 +224,8 @@ ExtensionsService::ExtensionsService(Profile* profile, PrefService* prefs, const FilePath& install_directory, MessageLoop* frontend_loop, - MessageLoop* backend_loop) + MessageLoop* backend_loop, + bool autoupdate_enabled) : extension_prefs_(new ExtensionPrefs(prefs, install_directory)), backend_loop_(backend_loop), install_directory_(install_directory), @@ -231,6 +238,16 @@ ExtensionsService::ExtensionsService(Profile* profile, else if (profile->GetPrefs()->GetBoolean(prefs::kEnableExtensions)) extensions_enabled_ = true; + // Set up the ExtensionUpdater + if (autoupdate_enabled) { + int update_frequency = kDefaultUpdateFrequencySeconds; + if (command_line->HasSwitch(switches::kExtensionsUpdateFrequency)) { + update_frequency = StringToInt(WideToASCII(command_line->GetSwitchValue( + switches::kExtensionsUpdateFrequency))); + } + updater_ = new ExtensionUpdater(this, update_frequency, backend_loop_); + } + backend_ = new ExtensionsServiceBackend( install_directory_, g_browser_process->resource_dispatcher_host(), frontend_loop, extensions_enabled()); @@ -238,6 +255,9 @@ ExtensionsService::ExtensionsService(Profile* profile, ExtensionsService::~ExtensionsService() { UnloadAllExtensions(); + if (updater_.get()) { + updater_->Stop(); + } } void ExtensionsService::SetExtensionsEnabled(bool enabled) { @@ -248,7 +268,7 @@ void ExtensionsService::SetExtensionsEnabled(bool enabled) { void ExtensionsService::Init() { DCHECK(!ready_); - DCHECK(extensions_.size() == 0); + DCHECK_EQ(extensions_.size(), 0u); // Start up the extension event routers. ExtensionBrowserEventRouter::GetInstance()->Init(); @@ -257,7 +277,7 @@ void ExtensionsService::Init() { // TODO(erikkay) this should probably be deferred to a future point // rather than running immediately at startup. - CheckForUpdates(); + CheckForExternalUpdates(); // TODO(erikkay) this should probably be deferred as well. GarbageCollectExtensions(); @@ -332,7 +352,7 @@ void ExtensionsService::LoadAllExtensions() { new InstalledExtensions(extension_prefs_.get()))); } -void ExtensionsService::CheckForUpdates() { +void ExtensionsService::CheckForExternalUpdates() { // This installs or updates externally provided extensions. std::set<std::string> killed_extensions; extension_prefs_->GetKilledExtensionIds(&killed_extensions); @@ -390,6 +410,9 @@ void ExtensionsService::GarbageCollectExtensions() { void ExtensionsService::OnLoadedInstalledExtensions() { ready_ = true; + if (updater_.get()) { + updater_->Start(); + } NotificationService::current()->Notify( NotificationType::EXTENSIONS_READY, Source<ExtensionsService>(this), @@ -559,7 +582,7 @@ void ExtensionsServiceBackend::GarbageCollectExtensions( // Nothing to clean up if it doesn't exist. if (!file_util::DirectoryExists(install_directory_)) return; - + FilePath install_directory_absolute(install_directory_); file_util::AbsolutePath(&install_directory_absolute); @@ -909,7 +932,7 @@ void ExtensionsServiceBackend::InstallExtension( frontend_ = frontend; alert_on_error_ = true; - InstallOrUpdateExtension(extension_path, std::string()); + InstallOrUpdateExtension(extension_path, std::string(), false); } void ExtensionsServiceBackend::UpdateExtension(const std::string& id, @@ -920,17 +943,18 @@ void ExtensionsServiceBackend::UpdateExtension(const std::string& id, frontend_ = frontend; alert_on_error_ = alert_on_error; - InstallOrUpdateExtension(extension_path, id); + InstallOrUpdateExtension(extension_path, id, true); } void ExtensionsServiceBackend::InstallOrUpdateExtension( - const FilePath& extension_path, const std::string& expected_id) { + const FilePath& extension_path, const std::string& expected_id, + bool silent) { std::string actual_public_key; if (!ValidateSignature(extension_path, &actual_public_key)) return; // Failures reported within ValidateSignature(). UnpackerClient* client = new UnpackerClient( - this, extension_path, actual_public_key, expected_id); + this, extension_path, actual_public_key, expected_id, silent); client->Start(); } @@ -1028,7 +1052,8 @@ void ExtensionsServiceBackend::OnExtensionUnpacked( const FilePath& temp_extension_dir, const std::string expected_id, const DictionaryValue& manifest, - const std::vector< Tuple2<SkBitmap, FilePath> >& images) { + const std::vector< Tuple2<SkBitmap, FilePath> >& images, + bool silent) { Extension extension; std::string error; if (!extension.InitFromValue(manifest, @@ -1056,8 +1081,9 @@ void ExtensionsServiceBackend::OnExtensionUnpacked( // TODO(extensions): Make better extensions UI. http://crbug.com/12116 - // We don't show the install dialog for themes or external extensions. - if (!extension.IsTheme() && frontend_->show_extensions_prompts()) { + // We don't show the install dialog for themes, updates, or external + // extensions. + if (!extension.IsTheme() && !silent && frontend_->show_extensions_prompts()) { #if defined(OS_WIN) if (!Extension::IsExternalLocation(location) && win_util::MessageBox(GetForegroundWindow(), @@ -1260,7 +1286,7 @@ void ExtensionsServiceBackend::CheckVersionAndInstallExtension( const std::string& id, const Version* extension_version, const FilePath& extension_path) { if (ShouldInstall(id, extension_version)) - InstallOrUpdateExtension(FilePath(extension_path), id); + InstallOrUpdateExtension(FilePath(extension_path), id, false); } bool ExtensionsServiceBackend::LookupExternalExtension( diff --git a/chrome/browser/extensions/extensions_service.h b/chrome/browser/extensions/extensions_service.h index 5afa547..8ae2759 100644 --- a/chrome/browser/extensions/extensions_service.h +++ b/chrome/browser/extensions/extensions_service.h @@ -26,6 +26,7 @@ class Browser; class DictionaryValue; class Extension; class ExtensionsServiceBackend; +class ExtensionUpdater; class GURL; class MessageLoop; class PrefService; @@ -95,12 +96,16 @@ class ExtensionsService // installed to. static const char* kInstallDirectoryName; + // If auto-updates are turned on, default to running every 5 hours. + static const int kDefaultUpdateFrequencySeconds = 60 * 60 * 5; + ExtensionsService(Profile* profile, const CommandLine* command_line, PrefService* prefs, const FilePath& install_directory, MessageLoop* frontend_loop, - MessageLoop* backend_loop); + MessageLoop* backend_loop, + bool autoupdate_enabled); virtual ~ExtensionsService(); // Gets the list of currently installed extensions. @@ -144,7 +149,7 @@ class ExtensionsService void LoadAllExtensions(); // Check for updates (or potentially new extensions from external providers) - void CheckForUpdates(); + void CheckForExternalUpdates(); // Unload the specified extension. void UnloadExtension(const std::string& extension_id); @@ -245,6 +250,9 @@ class ExtensionsService // Is the service ready to go? bool ready_; + // Our extension updater, if updates are turned on. + scoped_refptr<ExtensionUpdater> updater_; + DISALLOW_COPY_AND_ASSIGN(ExtensionsService); }; @@ -348,9 +356,10 @@ class ExtensionsServiceBackend // Install a crx file at |extension_path|. If |expected_id| is not empty, it's // verified against the extension's manifest before installation. If the // extension is already installed, install the new version only if its version - // number is greater than the current installed version. + // number is greater than the current installed version. If |silent| is true, + // the confirmation dialog will not pop up. void InstallOrUpdateExtension(const FilePath& extension_path, - const std::string& expected_id); + const std::string& expected_id, bool silent); // Validates the signature of the extension in |extension_path|. Returns true // and the public key (in |key|) if the signature validates, false otherwise. @@ -360,13 +369,15 @@ class ExtensionsServiceBackend // |temp_extension_dir| by our utility process. If |expected_id| is not // empty, it's verified against the extension's manifest before installation. // |manifest| and |images| are parsed information from the extension that - // we want to write to disk in the browser process. + // we want to write to disk in the browser process. If |silent| is true, there + // will be no install confirmation dialog. void OnExtensionUnpacked( const FilePath& extension_path, const FilePath& temp_extension_dir, const std::string expected_id, const DictionaryValue& manifest, - const std::vector< Tuple2<SkBitmap, FilePath> >& images); + const std::vector< Tuple2<SkBitmap, FilePath> >& images, + bool silent); // Notify the frontend that there was an error loading an extension. void ReportExtensionLoadError(const FilePath& extension_path, diff --git a/chrome/browser/extensions/extensions_service_unittest.cc b/chrome/browser/extensions/extensions_service_unittest.cc index 555655c..ac668e1f 100644 --- a/chrome/browser/extensions/extensions_service_unittest.cc +++ b/chrome/browser/extensions/extensions_service_unittest.cc @@ -213,7 +213,8 @@ class ExtensionsServiceTest prefs_.get(), extensions_install_dir, &loop_, - &loop_); + &loop_, + false); service_->SetExtensionsEnabled(true); service_->set_show_extensions_prompts(false); @@ -1063,7 +1064,7 @@ TEST_F(ExtensionsServiceTest, LoadExtension) { FilePath no_manifest = extensions_path .AppendASCII("bad") - //.AppendASCII("Extensions") + // .AppendASCII("Extensions") .AppendASCII("cccccccccccccccccccccccccccccccc") .AppendASCII("1"); service_->LoadExtension(no_manifest); @@ -1145,7 +1146,7 @@ TEST_F(ExtensionsServiceTest, ExternalInstallRegistry) { // Reloading extensions should find our externally registered extension // and install it. - service_->CheckForUpdates(); + service_->CheckForExternalUpdates(); loop_.RunAllPending(); ASSERT_EQ(0u, GetErrors().size()); @@ -1172,7 +1173,7 @@ TEST_F(ExtensionsServiceTest, ExternalInstallRegistry) { reg_provider->UpdateOrAddExtension(good_crx, "1.0.0.1", source_path); loaded_.clear(); - service_->CheckForUpdates(); + service_->CheckForExternalUpdates(); loop_.RunAllPending(); ASSERT_EQ(0u, GetErrors().size()); ASSERT_EQ(1u, loaded_.size()); @@ -1192,7 +1193,7 @@ TEST_F(ExtensionsServiceTest, ExternalInstallRegistry) { ASSERT_FALSE(file_util::PathExists(install_path)); loaded_.clear(); - service_->CheckForUpdates(); + service_->CheckForExternalUpdates(); loop_.RunAllPending(); ASSERT_EQ(0u, loaded_.size()); ValidatePrefKeyCount(1); @@ -1205,7 +1206,7 @@ TEST_F(ExtensionsServiceTest, ExternalInstallRegistry) { prefs_->ScheduleSavePersistentPrefs(); loaded_.clear(); - service_->CheckForUpdates(); + service_->CheckForExternalUpdates(); loop_.RunAllPending(); ASSERT_EQ(1u, loaded_.size()); ValidatePrefKeyCount(1); @@ -1249,7 +1250,7 @@ TEST_F(ExtensionsServiceTest, ExternalInstallPref) { // Checking for updates should find our externally registered extension // and install it. - service_->CheckForUpdates(); + service_->CheckForExternalUpdates(); loop_.RunAllPending(); ASSERT_EQ(0u, GetErrors().size()); @@ -1276,7 +1277,7 @@ TEST_F(ExtensionsServiceTest, ExternalInstallPref) { pref_provider->UpdateOrAddExtension(good_crx, "1.0.0.1", source_path); loaded_.clear(); - service_->CheckForUpdates(); + service_->CheckForExternalUpdates(); loop_.RunAllPending(); ASSERT_EQ(0u, GetErrors().size()); ASSERT_EQ(1u, loaded_.size()); @@ -1296,7 +1297,7 @@ TEST_F(ExtensionsServiceTest, ExternalInstallPref) { ASSERT_FALSE(file_util::PathExists(install_path)); loaded_.clear(); - service_->CheckForUpdates(); + service_->CheckForExternalUpdates(); loop_.RunAllPending(); ASSERT_EQ(0u, loaded_.size()); ValidatePrefKeyCount(1); @@ -1308,7 +1309,7 @@ TEST_F(ExtensionsServiceTest, ExternalInstallPref) { prefs_->ScheduleSavePersistentPrefs(); loaded_.clear(); - service_->CheckForUpdates(); + service_->CheckForExternalUpdates(); loop_.RunAllPending(); ASSERT_EQ(1u, loaded_.size()); ValidatePrefKeyCount(1); @@ -1331,7 +1332,7 @@ TEST_F(ExtensionsServiceTest, ExternalInstallPref) { SetExtensionsEnabled(false); pref_provider->UpdateOrAddExtension(good_crx, "1.0", source_path); - service_->CheckForUpdates(); + service_->CheckForExternalUpdates(); loop_.RunAllPending(); ASSERT_EQ(0u, loaded_.size()); @@ -1433,7 +1434,7 @@ TEST(ExtensionsServiceTestSimple, Enabledness) { // By default, we are disabled. command_line.reset(new CommandLine(L"")); service = new ExtensionsService(&profile, command_line.get(), - profile.GetPrefs(), install_dir, &loop, &loop); + profile.GetPrefs(), install_dir, &loop, &loop, false); EXPECT_FALSE(service->extensions_enabled()); service->Init(); loop.RunAllPending(); @@ -1443,7 +1444,7 @@ TEST(ExtensionsServiceTestSimple, Enabledness) { recorder.set_ready(false); command_line->AppendSwitch(switches::kEnableExtensions); service = new ExtensionsService(&profile, command_line.get(), - profile.GetPrefs(), install_dir, &loop, &loop); + profile.GetPrefs(), install_dir, &loop, &loop, false); EXPECT_TRUE(service->extensions_enabled()); service->Init(); loop.RunAllPending(); @@ -1452,7 +1453,7 @@ TEST(ExtensionsServiceTestSimple, Enabledness) { recorder.set_ready(false); profile.GetPrefs()->SetBoolean(prefs::kEnableExtensions, true); service = new ExtensionsService(&profile, command_line.get(), - profile.GetPrefs(), install_dir, &loop, &loop); + profile.GetPrefs(), install_dir, &loop, &loop, false); EXPECT_TRUE(service->extensions_enabled()); service->Init(); loop.RunAllPending(); @@ -1461,7 +1462,7 @@ TEST(ExtensionsServiceTestSimple, Enabledness) { recorder.set_ready(false); command_line.reset(new CommandLine(L"")); service = new ExtensionsService(&profile, command_line.get(), - profile.GetPrefs(), install_dir, &loop, &loop); + profile.GetPrefs(), install_dir, &loop, &loop, false); EXPECT_TRUE(service->extensions_enabled()); service->Init(); loop.RunAllPending(); diff --git a/chrome/browser/profile.cc b/chrome/browser/profile.cc index 085f814..df95acf 100644 --- a/chrome/browser/profile.cc +++ b/chrome/browser/profile.cc @@ -565,7 +565,8 @@ void ProfileImpl::InitExtensions() { GetPrefs(), GetPath().AppendASCII(ExtensionsService::kInstallDirectoryName), MessageLoop::current(), - g_browser_process->file_thread()->message_loop()); + g_browser_process->file_thread()->message_loop(), + true); extensions_service_->Init(); } diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp index ae6c902..13e7e95 100644 --- a/chrome/chrome.gyp +++ b/chrome/chrome.gyp @@ -935,6 +935,8 @@ 'browser/extensions/extension_tabs_module.h', 'browser/extensions/extension_tabs_module_constants.cc', 'browser/extensions/extension_tabs_module_constants.h', + 'browser/extensions/extension_updater.cc', + 'browser/extensions/extension_updater.h', 'browser/extensions/extensions_service.cc', 'browser/extensions/extensions_service.h', 'browser/extensions/extensions_ui.cc', @@ -3508,6 +3510,7 @@ 'browser/extensions/extension_messages_unittest.cc', 'browser/extensions/extension_process_manager_unittest.cc', 'browser/extensions/extension_ui_unittest.cc', + 'browser/extensions/extension_updater_unittest.cc', 'browser/extensions/extensions_service_unittest.cc', 'browser/extensions/user_script_master_unittest.cc', 'browser/find_backend_unittest.cc', diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc index 9ba11a1..cef4dad 100644 --- a/chrome/common/chrome_switches.cc +++ b/chrome/common/chrome_switches.cc @@ -392,6 +392,9 @@ const wchar_t kEnableUserScripts[] = L"enable-user-scripts"; // Enable extensions. const wchar_t kEnableExtensions[] = L"enable-extensions"; +// Frequency in seconds for Extensions auto-update. +const wchar_t kExtensionsUpdateFrequency[] = L"extensions-update-frequency"; + // Install the extension specified in the argument. This is for MIME type // handling so that users can double-click on an extension. const wchar_t kInstallExtension[] = L"install-extension"; diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h index f8f74d3..ef46c6c 100644 --- a/chrome/common/chrome_switches.h +++ b/chrome/common/chrome_switches.h @@ -142,6 +142,7 @@ extern const wchar_t kSdchFilter[]; extern const wchar_t kEnableUserScripts[]; extern const wchar_t kEnableExtensions[]; +extern const wchar_t kExtensionsUpdateFrequency[]; extern const wchar_t kInstallExtension[]; extern const wchar_t kLoadExtension[]; extern const wchar_t kPackExtension[]; |