diff options
author | erikkay@google.com <erikkay@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-02-26 00:04:15 +0000 |
---|---|---|
committer | erikkay@google.com <erikkay@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-02-26 00:04:15 +0000 |
commit | b0beaa66afc6a6127440a65b56ce3967c8852e40 (patch) | |
tree | c1d7b3e24dcdaa82d9893e7c71432b9b110d3530 /chrome/browser/extensions | |
parent | 15fda7175c8ba31974d87099f0cf01eb0281bd1d (diff) | |
download | chromium_src-b0beaa66afc6a6127440a65b56ce3967c8852e40.zip chromium_src-b0beaa66afc6a6127440a65b56ce3967c8852e40.tar.gz chromium_src-b0beaa66afc6a6127440a65b56ce3967c8852e40.tar.bz2 |
Auto install and update extensions from a registry defined location. This allows developers to install a chrome extension and manage its update process completely indepdendently of Chrome's built-in mechanism.
Review URL: http://codereview.chromium.org/28040
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@10412 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/extensions')
-rw-r--r-- | chrome/browser/extensions/extensions_service.cc | 592 | ||||
-rw-r--r-- | chrome/browser/extensions/extensions_service.h | 135 | ||||
-rw-r--r-- | chrome/browser/extensions/extensions_service_unittest.cc | 31 |
3 files changed, 488 insertions, 270 deletions
diff --git a/chrome/browser/extensions/extensions_service.cc b/chrome/browser/extensions/extensions_service.cc index 17c5afe..1446e7b 100644 --- a/chrome/browser/extensions/extensions_service.cc +++ b/chrome/browser/extensions/extensions_service.cc @@ -19,7 +19,9 @@ #include "chrome/common/json_value_serializer.h" #include "chrome/common/notification_service.h" #include "chrome/common/unzip.h" + #if defined(OS_WIN) +#include "base/registry.h" #include "chrome/common/win_util.h" #endif @@ -28,8 +30,10 @@ const char* ExtensionsService::kInstallDirectoryName = "Extensions"; const char* ExtensionsService::kCurrentVersionFileName = "Current Version"; const char* ExtensionsServiceBackend::kTempExtensionName = "TEMP_INSTALL"; + +namespace { // Chromium Extension magic number -static const char kExtensionFileMagic[] = "Cr24"; +const char kExtensionFileMagic[] = "Cr24"; struct ExtensionHeader { char magic[sizeof(kExtensionFileMagic) - 1]; @@ -41,6 +45,25 @@ struct ExtensionHeader { const size_t kZipHashBytes = 32; // SHA-256 const size_t kZipHashHexBytes = kZipHashBytes * 2; // Hex string is 2x size. +#if defined(OS_WIN) + +// Registry key where registry defined extension installers live. +const wchar_t kRegistryExtensions[] = L"Software\\Google\\Chrome\\Extensions"; + +// Registry value of of that key that defines the path to the .crx file. +const wchar_t kRegistryExtensionPath[] = L"path"; + +// Registry value of that key that defines the current version of the .crx file. +const wchar_t kRegistryExtensionVersion[] = L"version"; + +#endif + +// A marker file to indicate that an extension was installed from an external +// source. +const char kExternalInstallFile[] = "EXTERNAL_INSTALL"; +} + + ExtensionsService::ExtensionsService(const FilePath& profile_directory, UserScriptMaster* user_script_master) : message_loop_(MessageLoop::current()), @@ -57,6 +80,18 @@ ExtensionsService::~ExtensionsService() { } bool ExtensionsService::Init() { +#if defined(OS_WIN) + + // TODO(port): ExtensionsServiceBackend::CheckForExternalUpdates depends on + // the Windows registry. + // TODO(erikkay): Should we monitor the registry during run as well? + g_browser_process->file_thread()->message_loop()->PostTask(FROM_HERE, + NewRunnableMethod(backend_.get(), + &ExtensionsServiceBackend::CheckForExternalUpdates, + install_directory_, + scoped_refptr<ExtensionsServiceFrontendInterface>(this))); +#endif + // TODO(aa): This message loop should probably come from a backend // interface, similar to how the message loop for the frontend comes // from the frontend interface. @@ -65,7 +100,6 @@ bool ExtensionsService::Init() { &ExtensionsServiceBackend::LoadExtensionsFromDirectory, install_directory_, scoped_refptr<ExtensionsServiceFrontendInterface>(this))); - // TODO(aa): Load extensions from other registered directories. return true; } @@ -83,6 +117,7 @@ void ExtensionsService::InstallExtension(const FilePath& extension_path) { &ExtensionsServiceBackend::InstallExtension, extension_path, install_directory_, + true, // alert_on_error scoped_refptr<ExtensionsServiceFrontendInterface>(this))); } @@ -135,115 +170,125 @@ void ExtensionsService::OnExtensionsLoadedFromDirectory( delete new_extensions; } -void ExtensionsService::OnExtensionLoadError(const std::string& error) { +void ExtensionsService::OnExtensionLoadError(bool alert_on_error, + const std::string& error) { // TODO(aa): Print the error message out somewhere better. I think we are // going to need some sort of 'extension inspector'. LOG(WARNING) << error; + if (alert_on_error) { #if defined(OS_WIN) - win_util::MessageBox(NULL, UTF8ToWide(error), - L"Extension load error", MB_OK | MB_SETFOREGROUND); + win_util::MessageBox(NULL, UTF8ToWide(error), + L"Extension load error", MB_OK | MB_SETFOREGROUND); #endif + } } -void ExtensionsService::OnExtensionInstallError(const std::string& error) { +void ExtensionsService::OnExtensionInstallError(bool alert_on_error, + const std::string& error) { // TODO(erikkay): Print the error message out somewhere better. LOG(WARNING) << error; + if (alert_on_error) { #if defined(OS_WIN) - win_util::MessageBox(NULL, UTF8ToWide(error), - L"Extension load error", MB_OK | MB_SETFOREGROUND); + win_util::MessageBox(NULL, UTF8ToWide(error), + L"Extension load error", MB_OK | MB_SETFOREGROUND); #endif + } } -void ExtensionsService::OnExtensionInstalled(FilePath path) { +void ExtensionsService::OnExtensionInstalled(FilePath path, bool update) { NotificationService::current()->Notify( NotificationType::EXTENSION_INSTALLED, NotificationService::AllSources(), Details<FilePath>(&path)); - // Immediately try to load the extension. - g_browser_process->file_thread()->message_loop()->PostTask(FROM_HERE, - NewRunnableMethod(backend_.get(), - &ExtensionsServiceBackend::LoadSingleExtension, - path, - scoped_refptr<ExtensionsServiceFrontendInterface>(this))); + // TODO(erikkay): Update UI if appropriate. } // ExtensionsServicesBackend -bool ExtensionsServiceBackend::LoadExtensionsFromDirectory( +void ExtensionsServiceBackend::LoadExtensionsFromDirectory( const FilePath& path_in, scoped_refptr<ExtensionsServiceFrontendInterface> frontend) { FilePath path = path_in; - - // Create the <Profile>/Extensions directory if it doesn't exist. - if (!file_util::DirectoryExists(path)) - file_util::CreateDirectory(path); + frontend_ = frontend; + alert_on_error_ = false; if (!file_util::AbsolutePath(&path)) NOTREACHED(); + scoped_ptr<ExtensionList> extensions(new ExtensionList); + + // Create the <Profile>/Extensions directory if it doesn't exist. + if (!file_util::DirectoryExists(path)) { + file_util::CreateDirectory(path); + LOG(INFO) << "Created Extensions directory. No extensions to install."; + ReportExtensionsLoaded(extensions.release()); + return; + } + LOG(INFO) << "Loading installed extensions..."; // Find all child directories in the install directory and load their // manifests. Post errors and results to the frontend. - scoped_ptr<ExtensionList> extensions(new ExtensionList); file_util::FileEnumerator enumerator(path, false, // not recursive file_util::FileEnumerator::DIRECTORIES); - for (FilePath child_path = enumerator.Next(); !child_path.value().empty(); - child_path = enumerator.Next()) { - std::string version_str; - if (!ReadCurrentVersion(child_path, &version_str)) { - ReportExtensionLoadError(frontend.get(), child_path, StringPrintf( - "Could not read '%s' file.", - ExtensionsService::kCurrentVersionFileName)); - continue; - } - - LOG(INFO) << " " << WideToASCII(child_path.BaseName().ToWStringHack()) << - " version: " << version_str; - - child_path = child_path.AppendASCII(version_str); - Extension* extension = LoadExtension(child_path, frontend); + for (extension_path_ = enumerator.Next(); !extension_path_.value().empty(); + extension_path_ = enumerator.Next()) { + Extension* extension = LoadExtensionCurrentVersion(); if (extension) extensions->push_back(extension); } LOG(INFO) << "Done."; - - ReportExtensionsLoaded(frontend.get(), extensions.release()); - return true; + ReportExtensionsLoaded(extensions.release()); } -bool ExtensionsServiceBackend::LoadSingleExtension( +void ExtensionsServiceBackend::LoadSingleExtension( const FilePath& path_in, scoped_refptr<ExtensionsServiceFrontendInterface> frontend) { - FilePath path = path_in; - if (!file_util::AbsolutePath(&path)) + frontend_ = frontend; + + // Explicit UI loads are always noisy. + alert_on_error_ = true; + + extension_path_ = path_in; + if (!file_util::AbsolutePath(&extension_path_)) NOTREACHED(); LOG(INFO) << "Loading single extension from " << - WideToASCII(path.BaseName().ToWStringHack()); + WideToASCII(extension_path_.BaseName().ToWStringHack()); - Extension* extension = LoadExtension(path, frontend); + Extension* extension = LoadExtension(); if (extension) { ExtensionList* extensions = new ExtensionList; extensions->push_back(extension); - ReportExtensionsLoaded(frontend.get(), extensions); - return true; + ReportExtensionsLoaded(extensions); } - return false; } -Extension* ExtensionsServiceBackend::LoadExtension( - const FilePath& path, - scoped_refptr<ExtensionsServiceFrontendInterface> frontend) { +Extension* ExtensionsServiceBackend::LoadExtensionCurrentVersion() { + std::string version_str; + if (!ReadCurrentVersion(extension_path_, &version_str)) { + ReportExtensionLoadError(StringPrintf("Could not read '%s' file.", + ExtensionsService::kCurrentVersionFileName)); + return NULL; + } + + LOG(INFO) << " " << + WideToASCII(extension_path_.BaseName().ToWStringHack()) << + " version: " << version_str; + + extension_path_ = extension_path_.AppendASCII(version_str); + return LoadExtension(); +} + +Extension* ExtensionsServiceBackend::LoadExtension() { FilePath manifest_path = - path.AppendASCII(Extension::kManifestFilename); + extension_path_.AppendASCII(Extension::kManifestFilename); if (!file_util::PathExists(manifest_path)) { - ReportExtensionLoadError(frontend.get(), path, - Extension::kInvalidManifestError); + ReportExtensionLoadError(Extension::kInvalidManifestError); return NULL; } @@ -251,21 +296,29 @@ Extension* ExtensionsServiceBackend::LoadExtension( std::string error; scoped_ptr<Value> root(serializer.Deserialize(&error)); if (!root.get()) { - ReportExtensionLoadError(frontend.get(), path, - error); + ReportExtensionLoadError(error); return NULL; } if (!root->IsType(Value::TYPE_DICTIONARY)) { - ReportExtensionLoadError(frontend.get(), path, - Extension::kInvalidManifestError); + ReportExtensionLoadError(Extension::kInvalidManifestError); return NULL; } - scoped_ptr<Extension> extension(new Extension(path)); + scoped_ptr<Extension> extension(new Extension(extension_path_)); if (!extension->InitFromValue(*static_cast<DictionaryValue*>(root.get()), &error)) { - ReportExtensionLoadError(frontend.get(), path, error); + ReportExtensionLoadError(error); + return NULL; + } + + if (CheckExternalUninstall(extension_path_, extension->id())) { + + // TODO(erikkay): Possibly defer this operation to avoid slowing initial + // load of extensions. + UninstallExtension(extension_path_); + + // No error needs to be reported. The extension effectively doesn't exist. return NULL; } @@ -274,9 +327,9 @@ Extension* ExtensionsServiceBackend::LoadExtension( extension->content_scripts().begin(); iter != extension->content_scripts().end(); ++iter) { if (!file_util::PathExists(iter->path())) { - ReportExtensionLoadError(frontend.get(), path, StringPrintf( - "Could not load content script '%s'.", - WideToUTF8(iter->path().ToWStringHack()).c_str())); + ReportExtensionLoadError( + StringPrintf("Could not load content script '%s'.", + WideToUTF8(iter->path().ToWStringHack()).c_str())); return NULL; } } @@ -285,21 +338,21 @@ Extension* ExtensionsServiceBackend::LoadExtension( } void ExtensionsServiceBackend::ReportExtensionLoadError( - ExtensionsServiceFrontendInterface *frontend, const FilePath& path, const std::string &error) { - // TODO(erikkay): note that this isn't guaranteed to work properly on Linux. - std::string path_str = WideToASCII(path.ToWStringHack()); + + // TODO(port): note that this isn't guaranteed to work properly on Linux. + std::string path_str = WideToASCII(extension_path_.ToWStringHack()); std::string message = StringPrintf("Could not load extension from '%s'. %s", path_str.c_str(), error.c_str()); - frontend->GetMessageLoop()->PostTask(FROM_HERE, NewRunnableMethod( - frontend, &ExtensionsServiceFrontendInterface::OnExtensionLoadError, - message)); + frontend_->GetMessageLoop()->PostTask(FROM_HERE, NewRunnableMethod( + frontend_, &ExtensionsServiceFrontendInterface::OnExtensionLoadError, + alert_on_error_, message)); } void ExtensionsServiceBackend::ReportExtensionsLoaded( - ExtensionsServiceFrontendInterface *frontend, ExtensionList* extensions) { - frontend->GetMessageLoop()->PostTask(FROM_HERE, NewRunnableMethod( - frontend, + ExtensionList* extensions) { + frontend_->GetMessageLoop()->PostTask(FROM_HERE, NewRunnableMethod( + frontend_, &ExtensionsServiceFrontendInterface::OnExtensionsLoadedFromDirectory, extensions)); } @@ -307,37 +360,32 @@ void ExtensionsServiceBackend::ReportExtensionsLoaded( // The extension file format is a header, followed by the manifest, followed // by the zip file. The header is a magic number, a version, the size of the // header, and the size of the manifest. These ints are 4 byte little endian. -DictionaryValue* ExtensionsServiceBackend::ReadManifest( - const FilePath& extension_path, - scoped_refptr<ExtensionsServiceFrontendInterface> frontend) { - ScopedStdioHandle file(file_util::OpenFile(extension_path, "rb")); +DictionaryValue* ExtensionsServiceBackend::ReadManifest() { + ScopedStdioHandle file(file_util::OpenFile(extension_path_, "rb")); if (!file.get()) { - ReportExtensionInstallError(frontend, extension_path, - "no such extension file"); + ReportExtensionInstallError("no such extension file"); return NULL; } // Read and verify the header. ExtensionHeader header; size_t len; + // TODO(erikkay): Yuck. I'm not a big fan of this kind of code, but it // appears that we don't have any endian/alignment aware serialization // code in the code base. So for now, this assumes that we're running // on a little endian machine with 4 byte alignment. len = fread(&header, 1, sizeof(ExtensionHeader), file.get()); if (len < sizeof(ExtensionHeader)) { - ReportExtensionInstallError(frontend, extension_path, - "invalid extension header"); + ReportExtensionInstallError("invalid extension header"); return NULL; } if (strncmp(kExtensionFileMagic, header.magic, sizeof(header.magic))) { - ReportExtensionInstallError(frontend, extension_path, - "bad magic number"); + ReportExtensionInstallError("bad magic number"); return NULL; } if (header.version != Extension::kExpectedFormatVersion) { - ReportExtensionInstallError(frontend, extension_path, - "bad version number"); + ReportExtensionInstallError("bad version number"); return NULL; } if (header.header_size > sizeof(ExtensionHeader)) @@ -360,24 +408,44 @@ DictionaryValue* ExtensionsServiceBackend::ReadManifest( std::string error; scoped_ptr<Value> val(json.Deserialize(&error)); if (!val.get()) { - ReportExtensionInstallError(frontend, extension_path, error); + ReportExtensionInstallError(error); return NULL; } if (!val->IsType(Value::TYPE_DICTIONARY)) { - ReportExtensionInstallError(frontend, extension_path, - "manifest isn't a JSON dictionary"); + ReportExtensionInstallError("manifest isn't a JSON dictionary"); return NULL; } DictionaryValue* manifest = static_cast<DictionaryValue*>(val.get()); + + // Check the version before proceeding. Although we verify the version + // again later, checking it here allows us to skip some potentially expensive + // work. + std::string id; + if (!manifest->GetString(Extension::kIdKey, &id)) { + ReportExtensionInstallError("missing id key"); + return NULL; + } + FilePath dest_dir = install_directory_.AppendASCII(id.c_str()); + if (file_util::PathExists(dest_dir)) { + std::string version; + if (!manifest->GetString(Extension::kVersionKey, &version)) { + ReportExtensionInstallError("missing version key"); + return NULL; + } + std::string current_version; + if (ReadCurrentVersion(dest_dir, ¤t_version)) { + if (!CheckCurrentVersion(version, current_version, dest_dir)) + return NULL; + } + } + std::string zip_hash; if (!manifest->GetString(Extension::kZipHashKey, &zip_hash)) { - ReportExtensionInstallError(frontend, extension_path, - "missing zip_hash key"); + ReportExtensionInstallError("missing zip_hash key"); return NULL; } if (zip_hash.size() != kZipHashHexBytes) { - ReportExtensionInstallError(frontend, extension_path, - "invalid zip_hash key"); + ReportExtensionInstallError("invalid zip_hash key"); return NULL; } @@ -394,19 +462,16 @@ DictionaryValue* ExtensionsServiceBackend::ReadManifest( std::vector<uint8> zip_hash_bytes; if (!HexStringToBytes(zip_hash, &zip_hash_bytes)) { - ReportExtensionInstallError(frontend, extension_path, - "invalid zip_hash key"); + ReportExtensionInstallError("invalid zip_hash key"); return NULL; } if (zip_hash_bytes.size() != kZipHashBytes) { - ReportExtensionInstallError(frontend, extension_path, - "invalid zip_hash key"); + ReportExtensionInstallError("invalid zip_hash key"); return NULL; } for (size_t i = 0; i < kZipHashBytes; ++i) { if (zip_hash_bytes[i] != hash[i]) { - ReportExtensionInstallError(frontend, extension_path, - "zip_hash key didn't match zip hash"); + ReportExtensionInstallError("zip_hash key didn't match zip hash"); return NULL; } } @@ -419,11 +484,10 @@ DictionaryValue* ExtensionsServiceBackend::ReadManifest( return manifest; } -bool ExtensionsServiceBackend::ReadCurrentVersion( - const FilePath& extension_path, - std::string* version_string) { +bool ExtensionsServiceBackend::ReadCurrentVersion(const FilePath& dir, + std::string* version_string) { FilePath current_version = - extension_path.AppendASCII(ExtensionsService::kCurrentVersionFileName); + dir.AppendASCII(ExtensionsService::kCurrentVersionFileName); if (file_util::PathExists(current_version)) { if (file_util::ReadFileToString(current_version, version_string)) { TrimWhitespace(*version_string, TRIM_ALL, version_string); @@ -434,66 +498,38 @@ bool ExtensionsServiceBackend::ReadCurrentVersion( } bool ExtensionsServiceBackend::CheckCurrentVersion( - const FilePath& extension_path, - const std::string& version, - const FilePath& dest_dir, - scoped_refptr<ExtensionsServiceFrontendInterface> frontend) { - std::string version_str; - if (ReadCurrentVersion(dest_dir, &version_str)) { - if (version_str == version) { - FilePath version_dir = dest_dir.AppendASCII(version_str); - if (file_util::PathExists(version_dir)) { - ReportExtensionInstallError(frontend, extension_path, - "Extension version already installed"); - return false; - } - // If the existing version_dir doesn't exist, then we'll return true - // so that we attempt to repair the broken installation. - } else { - scoped_ptr<Version> cur_version( - Version::GetVersionFromString(version_str)); - scoped_ptr<Version> new_version( - Version::GetVersionFromString(version)); - if (cur_version->CompareTo(*new_version) >= 0) { - ReportExtensionInstallError(frontend, extension_path, - "More recent version of extension already installed"); - return false; - } + const std::string& new_version_str, + const std::string& current_version_str, + const FilePath& dest_dir) { + scoped_ptr<Version> current_version( + Version::GetVersionFromString(current_version_str)); + scoped_ptr<Version> new_version( + Version::GetVersionFromString(new_version_str)); + if (current_version->CompareTo(*new_version) >= 0) { + + // Verify that the directory actually exists. If it doesn't we'll return + // true so that the install code will repair the broken installation. + // TODO(erikkay): A further step would be to verify that the extension + // has actually loaded successfully. + FilePath version_dir = dest_dir.AppendASCII(current_version_str); + if (file_util::PathExists(version_dir)) { + ReportExtensionInstallError( + "Existing version is already up to date."); + return false; } } return true; } -bool ExtensionsServiceBackend::UnzipExtension(const FilePath& extension_path, - const FilePath& temp_dir, - scoped_refptr<ExtensionsServiceFrontendInterface> frontend) { - // <profile>/Extensions/INSTALL_TEMP/<version> - if (!file_util::CreateDirectory(temp_dir)) { - ReportExtensionInstallError(frontend, extension_path, - "Couldn't create version directory."); - return false; - } - if (!Unzip(extension_path, temp_dir, NULL)) { - // Remove what we just installed. - file_util::Delete(temp_dir, true); - ReportExtensionInstallError(frontend, extension_path, - "Couldn't unzip extension."); - return false; - } - return true; -} - -bool ExtensionsServiceBackend::InstallDirSafely( - const FilePath& extension_path, - const FilePath& source_dir, - const FilePath& dest_dir, - scoped_refptr<ExtensionsServiceFrontendInterface> frontend) { +bool ExtensionsServiceBackend::InstallDirSafely(const FilePath& source_dir, + const FilePath& dest_dir) { if (file_util::PathExists(dest_dir)) { + // By the time we get here, it should be safe to assume that this directory // is not currently in use (it's not the current active version). if (!file_util::Delete(dest_dir, true)) { - ReportExtensionInstallError(frontend, extension_path, + ReportExtensionInstallError( "Can't delete existing version directory."); return false; } @@ -501,26 +537,21 @@ bool ExtensionsServiceBackend::InstallDirSafely( FilePath parent = dest_dir.DirName(); if (!file_util::DirectoryExists(parent)) { if (!file_util::CreateDirectory(parent)) { - ReportExtensionInstallError(frontend, extension_path, - "Couldn't create extension directory."); + ReportExtensionInstallError("Couldn't create extension directory."); return false; } } } if (!file_util::Move(source_dir, dest_dir)) { - ReportExtensionInstallError(frontend, extension_path, - "Couldn't move temporary directory."); + ReportExtensionInstallError("Couldn't move temporary directory."); return false; } return true; } -bool ExtensionsServiceBackend::SetCurrentVersion( - const FilePath& extension_path, - const FilePath& dest_dir, - std::string version, - scoped_refptr<ExtensionsServiceFrontendInterface> frontend) { +bool ExtensionsServiceBackend::SetCurrentVersion(const FilePath& dest_dir, + std::string version) { // Write out the new CurrentVersion file. // <profile>/Extension/<name>/CurrentVersion FilePath current_version = @@ -529,15 +560,13 @@ bool ExtensionsServiceBackend::SetCurrentVersion( current_version.InsertBeforeExtension(FILE_PATH_LITERAL("_old")); if (file_util::PathExists(current_version_old)) { if (!file_util::Delete(current_version_old, false)) { - ReportExtensionInstallError(frontend, extension_path, - "Couldn't remove CurrentVersion_old file."); + ReportExtensionInstallError("Couldn't remove CurrentVersion_old file."); return false; } } if (file_util::PathExists(current_version)) { if (!file_util::Move(current_version, current_version_old)) { - ReportExtensionInstallError(frontend, extension_path, - "Couldn't move CurrentVersion file."); + ReportExtensionInstallError("Couldn't move CurrentVersion file."); return false; } } @@ -546,103 +575,250 @@ bool ExtensionsServiceBackend::SetCurrentVersion( if (stream.Open(current_version, flags) != 0) return false; if (stream.Write(version.c_str(), version.size(), NULL) < 0) { + // Restore the old CurrentVersion. if (file_util::PathExists(current_version_old)) { if (!file_util::Move(current_version_old, current_version)) { LOG(WARNING) << "couldn't restore " << current_version_old.value() << " to " << current_version.value(); + // TODO(erikkay): This is an ugly state to be in. Try harder? } } - ReportExtensionInstallError(frontend, extension_path, - "Couldn't create CurrentVersion file."); + ReportExtensionInstallError("Couldn't create CurrentVersion file."); return false; } return true; } -bool ExtensionsServiceBackend::InstallExtension( +void ExtensionsServiceBackend::InstallExtension( const FilePath& extension_path, const FilePath& install_dir, + bool alert_on_error, scoped_refptr<ExtensionsServiceFrontendInterface> frontend) { LOG(INFO) << "Installing extension " << extension_path.value(); - // <profile>/Extensions/INSTALL_TEMP - FilePath temp_dir = install_dir.AppendASCII(kTempExtensionName); - // Ensure we're starting with a clean slate. - if (file_util::PathExists(temp_dir)) { - if (!file_util::Delete(temp_dir, true)) { - ReportExtensionInstallError(frontend, extension_path, - "Couldn't delete existing temporary directory."); - return false; - } - } - ScopedTempDir scoped_temp; - scoped_temp.Set(temp_dir); - if (!scoped_temp.IsValid()) { - ReportExtensionInstallError(frontend, extension_path, - "Couldn't create temporary directory."); - return false; - } + frontend_ = frontend; + alert_on_error_ = alert_on_error; + external_install_ = false; + extension_path_ = extension_path; + install_directory_ = install_dir; + + InstallOrUpdateExtension(std::string()); +} + +void ExtensionsServiceBackend::InstallOrUpdateExtension( + const std::string& expected_id) { + bool update = false; // Read and verify the extension. - scoped_ptr<DictionaryValue> manifest(ReadManifest(extension_path, frontend)); + scoped_ptr<DictionaryValue> manifest(ReadManifest()); if (!manifest.get()) { + // ReadManifest has already reported the extension error. - return false; + return; } DictionaryValue* dict = manifest.get(); Extension extension; std::string error; if (!extension.InitFromValue(*dict, &error)) { - ReportExtensionInstallError(frontend, extension_path, - "Invalid extension manifest."); - return false; + ReportExtensionInstallError("Invalid extension manifest."); + return; + } + + // If an expected id was provided, make sure it matches. + if (expected_id.length() && expected_id != extension.id()) { + ReportExtensionInstallError( + "ID in new extension manifest does not match expected ID."); + return; } // <profile>/Extensions/<id> - FilePath dest_dir = install_dir.AppendASCII(extension.id()); + FilePath dest_dir = install_directory_.AppendASCII(extension.id()); std::string version = extension.VersionString(); - if (!CheckCurrentVersion(extension_path, version, dest_dir, frontend)) - return false; + std::string current_version; + if (ReadCurrentVersion(dest_dir, ¤t_version)) { + if (!CheckCurrentVersion(version, current_version, dest_dir)) + return; + update = true; + } + + // <profile>/Extensions/INSTALL_TEMP + FilePath temp_dir = install_directory_.AppendASCII(kTempExtensionName); + + // Ensure we're starting with a clean slate. + if (file_util::PathExists(temp_dir)) { + if (!file_util::Delete(temp_dir, true)) { + ReportExtensionInstallError( + "Couldn't delete existing temporary directory."); + return; + } + } + ScopedTempDir scoped_temp; + scoped_temp.Set(temp_dir); + if (!scoped_temp.IsValid()) { + ReportExtensionInstallError("Couldn't create temporary directory."); + return; + } // <profile>/Extensions/INSTALL_TEMP/<version> FilePath temp_version = temp_dir.AppendASCII(version); - if (!UnzipExtension(extension_path, temp_version, frontend)) - return false; + file_util::CreateDirectory(temp_version); + if (!Unzip(extension_path_, temp_version, NULL)) { + ReportExtensionInstallError("Couldn't unzip extension."); + return; + } // <profile>/Extensions/<dir_name>/<version> FilePath version_dir = dest_dir.AppendASCII(version); - if (!InstallDirSafely(extension_path, temp_version, version_dir, frontend)) - return false; + if (!InstallDirSafely(temp_version, version_dir)) + return; - if (!SetCurrentVersion(extension_path, dest_dir, version, frontend)) { + if (!SetCurrentVersion(dest_dir, version)) { if (!file_util::Delete(version_dir, true)) LOG(WARNING) << "Can't remove " << dest_dir.value(); - return false; + return; } - ReportExtensionInstalled(frontend, dest_dir); - return true; + if (external_install_) { + + // To mark that this extension was installed from an external source, + // create a zero-length file. At load time, this is used to indicate + // that the extension should be uninstalled. + // TODO(erikkay): move this into per-extension config storage when + // it appears. + FilePath marker = version_dir.AppendASCII(kExternalInstallFile); + file_util::WriteFile(marker, NULL, 0); + } + + ReportExtensionInstalled(dest_dir, update); } void ExtensionsServiceBackend::ReportExtensionInstallError( - ExtensionsServiceFrontendInterface *frontend, const FilePath& path, const std::string &error) { + // TODO(erikkay): note that this isn't guaranteed to work properly on Linux. - std::string path_str = WideToASCII(path.ToWStringHack()); + std::string path_str = WideToASCII(extension_path_.ToWStringHack()); std::string message = StringPrintf("Could not install extension from '%s'. %s", path_str.c_str(), error.c_str()); - frontend->GetMessageLoop()->PostTask(FROM_HERE, NewRunnableMethod( - frontend, &ExtensionsServiceFrontendInterface::OnExtensionInstallError, - message)); + frontend_->GetMessageLoop()->PostTask(FROM_HERE, NewRunnableMethod( + frontend_, &ExtensionsServiceFrontendInterface::OnExtensionInstallError, + alert_on_error_, message)); } void ExtensionsServiceBackend::ReportExtensionInstalled( - ExtensionsServiceFrontendInterface *frontend, FilePath path) { - frontend->GetMessageLoop()->PostTask(FROM_HERE, NewRunnableMethod( - frontend, + FilePath path, bool update) { + frontend_->GetMessageLoop()->PostTask(FROM_HERE, NewRunnableMethod( + frontend_, &ExtensionsServiceFrontendInterface::OnExtensionInstalled, - path)); + path, + update)); + + // After it's installed, load it right away with the same settings. + extension_path_ = path; + LoadExtensionCurrentVersion(); +} + +// Some extensions will autoupdate themselves externally from Chrome. These +// are typically part of some larger client application package. To support +// these, the extension will register its location in the registry on Windows +// (TODO(port): what about on other platforms?) and this code will periodically +// check that location for a .crx file, which it will then install locally if +// a new version is available. +void ExtensionsServiceBackend::CheckForExternalUpdates( + const FilePath& install_dir, + scoped_refptr<ExtensionsServiceFrontendInterface> frontend) { + + // Note that this installation is intentionally silent (since it didn't + // go through the front-end). Extensions that are registered in this + // way are effectively considered 'pre-bundled', and so implicitly + // trusted. In general, if something has HKLM or filesystem access, + // they could install an extension manually themselves anyway. + alert_on_error_ = false; + frontend_ = frontend; + external_install_ = true; + install_directory_ = install_dir; + +#if defined(OS_WIN) + HKEY reg_root = HKEY_LOCAL_MACHINE; + RegistryKeyIterator iterator(reg_root, kRegistryExtensions); + while (iterator.Valid()) { + RegKey key; + std::wstring key_path = kRegistryExtensions; + key_path.append(L"\\"); + key_path.append(iterator.Name()); + if (key.Open(reg_root, key_path.c_str())) { + std::wstring extension_path; + if (key.ReadValue(kRegistryExtensionPath, &extension_path)) { + std::string id = WideToASCII(iterator.Name()); + extension_path_ = FilePath(extension_path); + std::wstring extension_version; + if (key.ReadValue(kRegistryExtensionVersion, &extension_version)) { + if (ShouldInstall(id, WideToASCII(extension_version))) + InstallOrUpdateExtension(id); + } else { + + // TODO(erikkay): find a way to get this into about:extensions + LOG(WARNING) << "Missing value " << kRegistryExtensionVersion << + " for key " << key_path; + } + } else { + + // TODO(erikkay): find a way to get this into about:extensions + LOG(WARNING) << "Missing value " << kRegistryExtensionPath << + " for key " << key_path; + } + } + ++iterator; + } +#else + NOTREACHED(); +#endif +} + +bool ExtensionsServiceBackend::CheckExternalUninstall(const FilePath& path, + const std::string& id) { + FilePath external_file = path.AppendASCII(kExternalInstallFile); + if (file_util::PathExists(external_file)) { +#if defined(OS_WIN) + HKEY reg_root = HKEY_LOCAL_MACHINE; + RegKey key; + std::wstring key_path = kRegistryExtensions; + key_path.append(L"\\"); + key_path.append(ASCIIToWide(id)); + + // If the key doesn't exist, then we should uninstall. + return !key.Open(reg_root, key_path.c_str()); +#else + NOTREACHED(); +#endif + } + return false; +} + +// Assumes that the extension isn't currently loaded or in use. +void ExtensionsServiceBackend::UninstallExtension(const FilePath& path) { + FilePath parent = path.DirName(); + FilePath version = + parent.AppendASCII(ExtensionsService::kCurrentVersionFileName); + bool version_exists = file_util::PathExists(version); + DCHECK(version_exists); + if (!version_exists) { + LOG(WARNING) << "Asked to uninstall bogus extension dir " << parent.value(); + return; + } + if (!file_util::Delete(parent, true)) { + LOG(WARNING) << "Failed to delete " << parent.value(); + } +} + +bool ExtensionsServiceBackend::ShouldInstall(const std::string& id, + const std::string& version) { + FilePath dir(install_directory_.AppendASCII(id.c_str())); + std::string current_version; + if (ReadCurrentVersion(dir, ¤t_version)) { + return !CheckCurrentVersion(version, current_version, dir); + } + return true; } diff --git a/chrome/browser/extensions/extensions_service.h b/chrome/browser/extensions/extensions_service.h index 8382786..c8f04b0 100644 --- a/chrome/browser/extensions/extensions_service.h +++ b/chrome/browser/extensions/extensions_service.h @@ -5,6 +5,7 @@ #ifndef CHROME_BROWSER_EXTENSIONS_EXTENSIONS_SERVICE_H_ #define CHROME_BROWSER_EXTENSIONS_EXTENSIONS_SERVICE_H_ +#include <string> #include <vector> #include "base/file_path.h" @@ -27,24 +28,31 @@ class ExtensionsServiceFrontendInterface // The message loop to invoke the frontend's methods on. virtual MessageLoop* GetMessageLoop() = 0; - // Install the extension file at |extension_path|. + // Install the extension file at |extension_path|. Will install as an + // update if an older version is already installed. + // For fresh installs, this method also causes the extension to be + // immediately loaded. virtual void InstallExtension(const FilePath& extension_path) = 0; // Load the extension from the directory |extension_path|. virtual void LoadExtension(const FilePath& extension_path) = 0; // Called when loading an extension fails. - virtual void OnExtensionLoadError(const std::string& message) = 0; + virtual void OnExtensionLoadError(bool alert_on_error, + const std::string& message) = 0; // Called with results from LoadExtensionsFromDirectory(). The frontend // takes ownership of the list. virtual void OnExtensionsLoadedFromDirectory(ExtensionList* extensions) = 0; // Called when installing an extension fails. - virtual void OnExtensionInstallError(const std::string& message) = 0; + virtual void OnExtensionInstallError(bool alert_on_error, + const std::string& message) = 0; // Called with results from InstallExtension(). - virtual void OnExtensionInstalled(FilePath path) = 0; + // |is_update| is true if the installation was an update to an existing + // installed extension rather than a new installation. + virtual void OnExtensionInstalled(FilePath path, bool is_update) = 0; }; @@ -67,10 +75,12 @@ class ExtensionsService : public ExtensionsServiceFrontendInterface { virtual MessageLoop* GetMessageLoop(); virtual void InstallExtension(const FilePath& extension_path); virtual void LoadExtension(const FilePath& extension_path); - virtual void OnExtensionLoadError(const std::string& message); + virtual void OnExtensionLoadError(bool alert_on_error, + const std::string& message); virtual void OnExtensionsLoadedFromDirectory(ExtensionList* extensions); - virtual void OnExtensionInstallError(const std::string& message); - virtual void OnExtensionInstalled(FilePath path); + virtual void OnExtensionInstallError(bool alert_on_error, + const std::string& message); + virtual void OnExtensionInstalled(FilePath path, bool is_update); // The name of the file that the current active version number is stored in. static const char* kCurrentVersionFileName; @@ -111,7 +121,7 @@ class ExtensionsServiceBackend // Errors are reported through OnExtensionLoadError(). On completion, // OnExtensionsLoadedFromDirectory() is called with any successfully loaded // extensions. - bool LoadExtensionsFromDirectory( + void LoadExtensionsFromDirectory( const FilePath &path, scoped_refptr<ExtensionsServiceFrontendInterface> frontend); @@ -122,79 +132,110 @@ class ExtensionsServiceBackend // extensions. // TODO(erikkay): It might be useful to be able to load a packed extension // (presumably into memory) without installing it. - bool LoadSingleExtension( + void LoadSingleExtension( const FilePath &path, scoped_refptr<ExtensionsServiceFrontendInterface> frontend); // Install the extension file at extension_path to install_dir. // ReportExtensionInstallError is called on error. // ReportExtensionInstalled is called on success. - bool InstallExtension( + void InstallExtension( const FilePath& extension_path, const FilePath& install_dir, + bool alert_on_error, + scoped_refptr<ExtensionsServiceFrontendInterface> frontend); + + // Check externally updated extensions for updates and install if necessary. + // ReportExtensionInstallError is called on error. + // ReportExtensionInstalled is called on success. + void CheckForExternalUpdates( + const FilePath& install_dir, scoped_refptr<ExtensionsServiceFrontendInterface> frontend); private: - // Load a single extension from |path| where |path| is the top directory of + // Load a single extension from |extension_path_|, the top directory of // a specific extension where its manifest file lives. - Extension* LoadExtension( - const FilePath &path, - scoped_refptr<ExtensionsServiceFrontendInterface> frontend); + Extension* LoadExtension(); + + // Load a single extension from |extension_path_|, the top directory of + // a versioned extension where its Current Version file lives. + Extension* LoadExtensionCurrentVersion(); + + // Install a crx file at |extension_path_| into |install_directory_|. + // If |expected_id| is not empty, it's verified against the extension's + // manifest before installationl. If the extension is already installed, + // install the new version only if its version number is greater than the + // current installed version. + void InstallOrUpdateExtension(const std::string& expected_id); // Notify a frontend that there was an error loading an extension. - void ReportExtensionLoadError(ExtensionsServiceFrontendInterface* frontend, - const FilePath& path, - const std::string& error); + void ReportExtensionLoadError(const std::string& error); // Notify a frontend that extensions were loaded. - void ReportExtensionsLoaded(ExtensionsServiceFrontendInterface* frontend, - ExtensionList* extensions); + void ReportExtensionsLoaded(ExtensionList* extensions); // Notify a frontend that there was an error installing an extension. - void ReportExtensionInstallError(ExtensionsServiceFrontendInterface* frontend, - const FilePath& path, - const std::string& error); + void ReportExtensionInstallError(const std::string& error); // Notify a frontend that extensions were installed. - void ReportExtensionInstalled(ExtensionsServiceFrontendInterface* frontend, - FilePath path); + // |is_update| is true if this was an update to an existing extension. + void ReportExtensionInstalled(FilePath path, bool is_update); // Read the manifest from the front of the extension file. // Caller takes ownership of return value. - DictionaryValue* ReadManifest(const FilePath& extension_path, - scoped_refptr<ExtensionsServiceFrontendInterface> frontend); - - // Reads the Current Version file from |extension_path|. - bool ReadCurrentVersion(const FilePath& extension_path, - std::string* version_string); + DictionaryValue* ReadManifest(); - // Check that the version to be installed is > the current installed - // extension. - bool CheckCurrentVersion(const FilePath& extension_path, - const std::string& version, - const FilePath& dest_dir, - scoped_refptr<ExtensionsServiceFrontendInterface> frontend); + // Reads the Current Version file from |dir| into |version_string|. + bool ReadCurrentVersion(const FilePath& dir, std::string* version_string); - // Unzip the extension into |dest_dir|. - bool UnzipExtension(const FilePath& extension_path, - const FilePath& dest_dir, - scoped_refptr<ExtensionsServiceFrontendInterface> frontend); + // Check that the version to be installed is greater than the current + // installed extension. + bool CheckCurrentVersion(const std::string& version, + const std::string& current_version, + const FilePath& dest_dir); // Install the extension dir by moving it from |source| to |dest| safely. - bool InstallDirSafely(const FilePath& extension_path, - const FilePath& source, const FilePath& dest, - scoped_refptr<ExtensionsServiceFrontendInterface> frontend); + bool InstallDirSafely(const FilePath& source, + const FilePath& dest); // Update the CurrentVersion file in |dest_dir| to |version|. - bool SetCurrentVersion(const FilePath& extension_path, - const FilePath& dest_dir, - std::string version, - scoped_refptr<ExtensionsServiceFrontendInterface> frontend); + bool SetCurrentVersion(const FilePath& dest_dir, + std::string version); + + // For the extension at |path| with |id|, check to see if it's an + // externally managed extension. If so return true if it should be + // uninstalled. + bool CheckExternalUninstall(const FilePath& path, const std::string& id); + + // Deletes all versions of the extension from the filesystem. + // |path| points at a specific extension version dir. + void UninstallExtension(const FilePath& path); + + // Should an extension of |id| and |version| be installed? + // Returns true if no extension of type |id| is installed or if |version| + // is greater than the current installed version. + bool ShouldInstall(const std::string& id, const std::string& version); // The name of a temporary directory to install an extension into for // validation before finalizing install. static const char* kTempExtensionName; + // This is a naked pointer which is set by each entry point. + // The entry point is responsible for ensuring lifetime. + ExtensionsServiceFrontendInterface* frontend_; + + // The extension path being loaded or installed. + FilePath extension_path_; + + // The top-level extensions directory being installed to. + FilePath install_directory_; + + // Whether errors result in noisy alerts. + bool alert_on_error_; + + // Whether the current install is from an external source. + bool external_install_; + DISALLOW_COPY_AND_ASSIGN(ExtensionsServiceBackend); }; diff --git a/chrome/browser/extensions/extensions_service_unittest.cc b/chrome/browser/extensions/extensions_service_unittest.cc index 6040823..62df67c 100644 --- a/chrome/browser/extensions/extensions_service_unittest.cc +++ b/chrome/browser/extensions/extensions_service_unittest.cc @@ -73,7 +73,8 @@ class ExtensionsServiceTestFrontend virtual void LoadExtension(const FilePath& extension_path) { } - virtual void OnExtensionLoadError(const std::string& message) { + virtual void OnExtensionLoadError(bool alert_on_error, + const std::string& message) { // In the development environment, we get errors when trying to load // extensions out of the .svn directories. if (message.find(".svn") != std::string::npos) @@ -92,11 +93,12 @@ class ExtensionsServiceTestFrontend std::stable_sort(errors_.begin(), errors_.end()); } - virtual void OnExtensionInstallError(const std::string& message) { + virtual void OnExtensionInstallError(bool alert_on_error, + const std::string& message) { errors_.push_back(message); } - virtual void OnExtensionInstalled(FilePath path) { + virtual void OnExtensionInstalled(FilePath path, bool is_update) { installed_.push_back(path); } @@ -104,13 +106,12 @@ class ExtensionsServiceTestFrontend ExtensionsServiceBackend* backend, bool should_succeed) { ASSERT_TRUE(file_util::PathExists(path)); - EXPECT_EQ(should_succeed, - backend->InstallExtension(path, install_dir_, - scoped_refptr<ExtensionsServiceFrontendInterface>(this))); + backend->InstallExtension(path, install_dir_, false, + scoped_refptr<ExtensionsServiceFrontendInterface>(this)); message_loop_.RunAllPending(); if (should_succeed) { EXPECT_EQ(1u, installed_.size()); - EXPECT_EQ(0u, errors_.size()); + EXPECT_EQ(0u, errors_.size()) << path.value(); } else { EXPECT_EQ(0u, installed_.size()); EXPECT_EQ(1u, errors_.size()); @@ -143,8 +144,8 @@ TEST_F(ExtensionsServiceTest, LoadAllExtensionsFromDirectorySuccess) { new ExtensionsServiceTestFrontend); std::vector<Extension*> extensions; - EXPECT_TRUE(backend->LoadExtensionsFromDirectory(extensions_path, - scoped_refptr<ExtensionsServiceFrontendInterface>(frontend.get()))); + backend->LoadExtensionsFromDirectory(extensions_path, + scoped_refptr<ExtensionsServiceFrontendInterface>(frontend.get())); frontend->GetMessageLoop()->RunAllPending(); ASSERT_EQ(3u, frontend->extensions()->size()); @@ -202,8 +203,8 @@ TEST_F(ExtensionsServiceTest, LoadAllExtensionsFromDirectoryFail) { new ExtensionsServiceTestFrontend); std::vector<Extension*> extensions; - EXPECT_TRUE(backend->LoadExtensionsFromDirectory(extensions_path, - scoped_refptr<ExtensionsServiceFrontendInterface>(frontend.get()))); + backend->LoadExtensionsFromDirectory(extensions_path, + scoped_refptr<ExtensionsServiceFrontendInterface>(frontend.get())); frontend->GetMessageLoop()->RunAllPending(); EXPECT_EQ(4u, frontend->errors()->size()); @@ -275,16 +276,16 @@ TEST_F(ExtensionsServiceTest, LoadExtension) { FilePath ext1 = extensions_path.AppendASCII("good").AppendASCII("extension1") .AppendASCII("1"); - EXPECT_TRUE(backend->LoadSingleExtension(ext1, - scoped_refptr<ExtensionsServiceFrontendInterface>(frontend.get()))); + backend->LoadSingleExtension(ext1, + scoped_refptr<ExtensionsServiceFrontendInterface>(frontend.get())); frontend->GetMessageLoop()->RunAllPending(); EXPECT_EQ(0u, frontend->errors()->size()); ASSERT_EQ(1u, frontend->extensions()->size()); FilePath no_manifest = extensions_path.AppendASCII("bad") .AppendASCII("no_manifest").AppendASCII("1"); - EXPECT_FALSE(backend->LoadSingleExtension(no_manifest, - scoped_refptr<ExtensionsServiceFrontendInterface>(frontend.get()))); + backend->LoadSingleExtension(no_manifest, + scoped_refptr<ExtensionsServiceFrontendInterface>(frontend.get())); frontend->GetMessageLoop()->RunAllPending(); EXPECT_EQ(1u, frontend->errors()->size()); ASSERT_EQ(1u, frontend->extensions()->size()); |