diff options
Diffstat (limited to 'chrome/browser/extensions')
-rw-r--r-- | chrome/browser/extensions/extension_file_util.cc | 342 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_file_util.h | 83 | ||||
-rw-r--r-- | chrome/browser/extensions/extension_file_util_unittest.cc | 167 | ||||
-rw-r--r-- | chrome/browser/extensions/extensions_service.cc | 478 | ||||
-rw-r--r-- | chrome/browser/extensions/extensions_service.h | 69 |
5 files changed, 647 insertions, 492 deletions
diff --git a/chrome/browser/extensions/extension_file_util.cc b/chrome/browser/extensions/extension_file_util.cc new file mode 100644 index 0000000..15e3cf8 --- /dev/null +++ b/chrome/browser/extensions/extension_file_util.cc @@ -0,0 +1,342 @@ +// 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_file_util.h" + +#include "base/file_util.h" +#include "base/logging.h" +#include "base/scoped_temp_dir.h" +#include "base/string_util.h" +#include "chrome/common/extensions/extension.h" +#include "chrome/common/extensions/extension_constants.h" +#include "chrome/common/json_value_serializer.h" +#include "net/base/file_stream.h" + +namespace extension_file_util { + +const char kInstallDirectoryName[] = "Extensions"; +const char kCurrentVersionFileName[] = "Current Version"; + +bool MoveDirSafely(const FilePath& source_dir, const FilePath& dest_dir) { + if (file_util::PathExists(dest_dir)) { + if (!file_util::Delete(dest_dir, true)) + return false; + } else { + FilePath parent = dest_dir.DirName(); + if (!file_util::DirectoryExists(parent)) { + if (!file_util::CreateDirectory(parent)) + return false; + } + } + + if (!file_util::Move(source_dir, dest_dir)) + return false; + + return true; +} + +bool SetCurrentVersion(const FilePath& dest_dir, const std::string& version, + std::string* error) { + // Write out the new CurrentVersion file. + // <profile>/Extension/<name>/CurrentVersion + FilePath current_version = dest_dir.AppendASCII(kCurrentVersionFileName); + FilePath current_version_old = + current_version.InsertBeforeExtension(FILE_PATH_LITERAL("_old")); + if (file_util::PathExists(current_version_old)) { + if (!file_util::Delete(current_version_old, false)) { + *error = "Couldn't remove CurrentVersion_old file."; + return false; + } + } + + if (file_util::PathExists(current_version)) { + if (!file_util::Move(current_version, current_version_old)) { + *error = "Couldn't move CurrentVersion file."; + return false; + } + } + net::FileStream stream; + int flags = base::PLATFORM_FILE_CREATE_ALWAYS | base::PLATFORM_FILE_WRITE; + 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? + } + } + *error = "Couldn't create CurrentVersion file."; + return false; + } + return true; +} + +bool ReadCurrentVersion(const FilePath& dir, std::string* version_string) { + FilePath current_version = dir.AppendASCII(kCurrentVersionFileName); + if (file_util::PathExists(current_version)) { + if (file_util::ReadFileToString(current_version, version_string)) { + TrimWhitespace(*version_string, TRIM_ALL, version_string); + return true; + } + } + return false; +} + +Extension::InstallType CompareToInstalledVersion( + const FilePath& install_directory, const std::string& id, + const std::string& new_version_str, std::string *current_version_str) { + CHECK(current_version_str); + FilePath dir(install_directory.AppendASCII(id.c_str())); + if (!ReadCurrentVersion(dir, current_version_str)) + return Extension::NEW_INSTALL; + + scoped_ptr<Version> current_version( + Version::GetVersionFromString(*current_version_str)); + scoped_ptr<Version> new_version( + Version::GetVersionFromString(new_version_str)); + int comp = new_version->CompareTo(*current_version); + if (comp > 0) + return Extension::UPGRADE; + else if (comp == 0) + return Extension::REINSTALL; + else + return Extension::DOWNGRADE; +} + +bool SanityCheckExtension(const FilePath& dir) { + // Verify that the directory actually exists. + // TODO(erikkay): A further step would be to verify that the extension + // has actually loaded successfully. + FilePath manifest_file(dir.AppendASCII(Extension::kManifestFilename)); + return file_util::PathExists(dir) && file_util::PathExists(manifest_file); +} + +bool InstallExtension(const FilePath& src_dir, + const FilePath& extensions_dir, + const std::string& extension_id, + const std::string& extension_version, + FilePath* version_dir, + Extension::InstallType* install_type, + std::string* error) { + FilePath dest_dir = extensions_dir.AppendASCII(extension_id); + *version_dir = dest_dir.AppendASCII(extension_version); + + std::string current_version; + *install_type = CompareToInstalledVersion( + extensions_dir, extension_id, extension_version, ¤t_version); + + // Do not allow downgrade. + if (*install_type == Extension::DOWNGRADE) + return true; + + if (*install_type == Extension::REINSTALL) { + if (!SanityCheckExtension(*version_dir)) { + // Treat corrupted existing installation as new install case. + *install_type = Extension::NEW_INSTALL; + } else { + return true; + } + } + + // If anything fails after this, we want to delete the extension dir. + ScopedTempDir scoped_version_dir; + scoped_version_dir.Set(*version_dir); + + if (!MoveDirSafely(src_dir, *version_dir)) { + *error = "Could not move extension directory into profile."; + return false; + } + + if (!SetCurrentVersion(dest_dir, extension_version, error)) + return false; + + scoped_version_dir.Take(); + return true; +} + +Extension* LoadExtension(const FilePath& extension_path, bool require_key, + std::string* error) { + FilePath manifest_path = + extension_path.AppendASCII(Extension::kManifestFilename); + if (!file_util::PathExists(manifest_path)) { + *error = extension_manifest_errors::kInvalidManifest; + return NULL; + } + + JSONFileValueSerializer serializer(manifest_path); + scoped_ptr<Value> root(serializer.Deserialize(error)); + if (!root.get()) + return NULL; + + if (!root->IsType(Value::TYPE_DICTIONARY)) { + *error = extension_manifest_errors::kInvalidManifest; + return NULL; + } + + scoped_ptr<Extension> extension(new Extension(extension_path)); + if (!extension->InitFromValue(*static_cast<DictionaryValue*>(root.get()), + require_key, error)) + return NULL; + + // Validate icons exist. + for (std::map<int, std::string>::const_iterator iter = + extension->icons().begin(); iter != extension->icons().end(); ++iter) { + if (!file_util::PathExists(extension->GetResourcePath(iter->second))) { + *error = StringPrintf("Could not load extension icon '%s'.", + iter->second.c_str()); + return NULL; + } + } + + // Theme resource validation. + if (extension->IsTheme()) { + DictionaryValue* images_value = extension->GetThemeImages(); + DictionaryValue::key_iterator iter = images_value->begin_keys(); + if (images_value) { + while (iter != images_value->end_keys()) { + std::string val; + if (images_value->GetString(*iter , &val)) { + FilePath image_path = extension->path().AppendASCII(val); + if (!file_util::PathExists(image_path)) { + *error = StringPrintf( + "Could not load '%s' for theme.", + WideToUTF8(image_path.ToWStringHack()).c_str()); + return NULL; + } + } + ++iter; + } + } + + // Themes cannot contain other extension types. + return extension.release(); + } + + // Validate that claimed script resources actually exist. + for (size_t i = 0; i < extension->content_scripts().size(); ++i) { + const UserScript& script = extension->content_scripts()[i]; + + for (size_t j = 0; j < script.js_scripts().size(); j++) { + const FilePath& path = script.js_scripts()[j].path(); + if (!file_util::PathExists(path)) { + *error = StringPrintf("Could not load '%s' for content script.", + WideToUTF8(path.ToWStringHack()).c_str()); + return NULL; + } + } + + for (size_t j = 0; j < script.css_scripts().size(); j++) { + const FilePath& path = script.css_scripts()[j].path(); + if (!file_util::PathExists(path)) { + *error = StringPrintf("Could not load '%s' for content script.", + WideToUTF8(path.ToWStringHack()).c_str()); + return NULL; + } + } + } + + for (size_t i = 0; i < extension->plugins().size(); ++i) { + const Extension::PluginInfo& plugin = extension->plugins()[i]; + if (!file_util::PathExists(plugin.path)) { + *error = StringPrintf("Could not load '%s' for plugin.", + WideToUTF8(plugin.path.ToWStringHack()).c_str()); + return NULL; + } + } + + // Validate icon location for page actions. + const PageActionMap& page_actions = extension->page_actions(); + for (PageActionMap::const_iterator i(page_actions.begin()); + i != page_actions.end(); ++i) { + PageAction* page_action = i->second; + const std::vector<FilePath>& icon_paths = page_action->icon_paths(); + for (std::vector<FilePath>::const_iterator iter = icon_paths.begin(); + iter != icon_paths.end(); ++iter) { + FilePath path = *iter; + if (!file_util::PathExists(path)) { + *error = StringPrintf("Could not load icon '%s' for page action.", + WideToUTF8(path.ToWStringHack()).c_str()); + return NULL; + } + } + } + + return extension.release(); +} + +void UninstallExtension(const std::string& id, const FilePath& extensions_dir) { + // First, delete the Current Version file. If the directory delete fails, then + // at least the extension won't be loaded again. + FilePath extension_root = extensions_dir.AppendASCII(id); + + if (!file_util::PathExists(extension_root)) { + LOG(WARNING) << "Asked to remove a non-existent extension " << id; + return; + } + + FilePath current_version_file = extension_root.AppendASCII( + kCurrentVersionFileName); + if (!file_util::PathExists(current_version_file)) { + LOG(WARNING) << "Extension " << id + << " does not have a Current Version file."; + } else { + if (!file_util::Delete(current_version_file, false)) { + LOG(WARNING) << "Could not delete Current Version file for extension " + << id; + return; + } + } + + // OK, now try and delete the entire rest of the directory. One major place + // this can fail is if the extension contains a plugin (stupid plugins). It's + // not a big deal though, because we'll notice next time we startup that the + // Current Version file is gone and finish the delete then. + if (!file_util::Delete(extension_root, true)) + LOG(WARNING) << "Could not delete directory for extension " << id; +} + +void GarbageCollectExtensions(const FilePath& install_directory) { + // Nothing to clean up if it doesn't exist. + if (!file_util::DirectoryExists(install_directory)) + return; + + LOG(INFO) << "Loading installed extensions..."; + file_util::FileEnumerator enumerator(install_directory, + false, // Not recursive. + file_util::FileEnumerator::DIRECTORIES); + FilePath extension_path; + for (extension_path = enumerator.Next(); !extension_path.value().empty(); + extension_path = enumerator.Next()) { + std::string extension_id = WideToASCII( + extension_path.BaseName().ToWStringHack()); + + // If there is no Current Version file, just delete the directory and move + // on. This can legitimately happen when an uninstall does not complete, for + // example, when a plugin is in use at uninstall time. + FilePath current_version_path = extension_path.AppendASCII( + kCurrentVersionFileName); + if (!file_util::PathExists(current_version_path)) { + LOG(INFO) << "Deleting incomplete install for directory " + << WideToASCII(extension_path.ToWStringHack()) << "."; + file_util::Delete(extension_path, true); // Recursive. + continue; + } + + // Ignore directories that aren't valid IDs. + if (!Extension::IdIsValid(extension_id)) { + LOG(WARNING) << "Invalid extension ID encountered in extensions " + "directory: " << extension_id; + LOG(INFO) << "Deleting invalid extension directory " + << WideToASCII(extension_path.ToWStringHack()) << "."; + file_util::Delete(extension_path, true); // Recursive. + continue; + } + } +} + +} // extensionfile_util diff --git a/chrome/browser/extensions/extension_file_util.h b/chrome/browser/extensions/extension_file_util.h new file mode 100644 index 0000000..45d0b30 --- /dev/null +++ b/chrome/browser/extensions/extension_file_util.h @@ -0,0 +1,83 @@ +// 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_FILE_UTIL_H_ +#define CHROME_BROWSER_EXTENSIONS_EXTENSION_FILE_UTIL_H_ + +#include <string> + +#include "base/file_path.h" +#include "chrome/common/extensions/extension.h" + +// Utilties for manipulating the on-disk storage of extensions. +namespace extension_file_util { + +// The name of the directory inside the profile that we store installed +// extension in. +extern const char kInstallDirectoryName[]; + +// The name of the file that contains the current version of an installed +// extension. +extern const char kCurrentVersionFileName[]; + +// Move source_dir to dest_dir (it will actually be named dest_dir, not inside +// dest_dir). If the parent path doesn't exixt, create it. If something else is +// already there, remove it. +bool MoveDirSafely(const FilePath& source_dir, const FilePath& dest_dir); + +// Updates the Current Version file inside the installed extension. +bool SetCurrentVersion(const FilePath& dest_dir, const std::string& version, + std::string* error); + +// Reads the Current Version file. +bool ReadCurrentVersion(const FilePath& dir, std::string* version_string); + +// Determine what type of install (new, upgrade, overinstall, downgrade) a given +// id/version combination is. Also returns the current version, if any, in +// current_version_str. +Extension::InstallType CompareToInstalledVersion( + const FilePath& install_directory, const std::string& id, + const std::string& new_version_str, std::string *current_version_str); + +// Sanity check that the directory has the minimum files to be a working +// extension. +bool SanityCheckExtension(const FilePath& extension_root); + +// Installs an extension unpacked in |src_dir|. |extensions_dir| is the root +// directory containing all extensions in the user profile. |extension_id| and +// |extension_version| are the id and version of the extension contained in +// |src_dir|. +// +// Returns the full path to the destination version directory and the type of +// install that was attempted. +// +// On failure, also returns an error message. +// +// NOTE: |src_dir| is not actually copied in the case of downgrades or +// overinstall of previous verisons of the extension. In that case, the function +// returns true and install_type is populated. +bool InstallExtension(const FilePath& src_dir, + const FilePath& extensions_dir, + const std::string& extension_id, + const std::string& extension_version, + FilePath* version_dir, + Extension::InstallType* install_type, + std::string* error); + +// Load an extension from the specified directory. Returns NULL on failure, with +// a description of the error in |error|. +Extension* LoadExtension(const FilePath& extension_root, bool require_key, + std::string* error); + +// Uninstalls the extension |id| from the install directory |extensions_dir|. +void UninstallExtension(const std::string& id, const FilePath& extensions_dir); + +// Clean up directories that aren't valid extensions from the install directory. +// TODO(aa): Also consider passing in a list of known current extensions and +// removing others? +void GarbageCollectExtensions(const FilePath& extensions_dir); + +} // extension_file_util + +#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_FILE_UTIL_H_ diff --git a/chrome/browser/extensions/extension_file_util_unittest.cc b/chrome/browser/extensions/extension_file_util_unittest.cc new file mode 100644 index 0000000..6e9b2dc --- /dev/null +++ b/chrome/browser/extensions/extension_file_util_unittest.cc @@ -0,0 +1,167 @@ +// 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_file_util.h" + +#include "base/file_util.h" +#include "base/scoped_temp_dir.h" +#include "base/path_service.h" +#include "chrome/common/chrome_paths.h" +#include "testing/gtest/include/gtest/gtest.h" + +TEST(ExtensionFileUtil, MoveDirSafely) { + // Create a test directory structure with some data in it. + ScopedTempDir temp; + ASSERT_TRUE(temp.CreateUniqueTempDir()); + + FilePath src_path = temp.path().AppendASCII("src"); + ASSERT_TRUE(file_util::CreateDirectory(src_path)); + + std::string data = "foobar"; + ASSERT_TRUE(file_util::WriteFile(src_path.AppendASCII("data"), + data.c_str(), data.length())); + + // Move it to a path that doesn't exist yet. + FilePath dest_path = temp.path().AppendASCII("dest").AppendASCII("dest"); + ASSERT_TRUE(extension_file_util::MoveDirSafely(src_path, dest_path)); + + // The path should get created. + ASSERT_TRUE(file_util::DirectoryExists(dest_path)); + + // The data should match. + std::string data_out; + ASSERT_TRUE(file_util::ReadFileToString(dest_path.AppendASCII("data"), + &data_out)); + ASSERT_EQ(data, data_out); + + // The src path should be gone. + ASSERT_FALSE(file_util::PathExists(src_path)); + + // Create some new test data. + ASSERT_TRUE(file_util::CopyDirectory(dest_path, src_path, + true)); // recursive + data = "hotdog"; + ASSERT_TRUE(file_util::WriteFile(src_path.AppendASCII("data"), + data.c_str(), data.length())); + + // Test again, overwriting the old path. + ASSERT_TRUE(extension_file_util::MoveDirSafely(src_path, dest_path)); + ASSERT_TRUE(file_util::DirectoryExists(dest_path)); + + data_out.clear(); + ASSERT_TRUE(file_util::ReadFileToString(dest_path.AppendASCII("data"), + &data_out)); + ASSERT_EQ(data, data_out); + ASSERT_FALSE(file_util::PathExists(src_path)); +} + +TEST(ExtensionFileUtil, SetCurrentVersion) { + // Create an empty test directory. + ScopedTempDir temp; + ASSERT_TRUE(temp.CreateUniqueTempDir()); + + // Set its version. + std::string error; + std::string version = "1.0"; + ASSERT_TRUE(extension_file_util::SetCurrentVersion(temp.path(), version, + &error)); + + // Test the result. + std::string version_out; + ASSERT_TRUE(file_util::ReadFileToString( + temp.path().AppendASCII(extension_file_util::kCurrentVersionFileName), + &version_out)); + ASSERT_EQ(version, version_out); + + // Try again, overwriting the old value. + version = "2.0"; + version_out.clear(); + ASSERT_TRUE(extension_file_util::SetCurrentVersion(temp.path(), version, + &error)); + ASSERT_TRUE(file_util::ReadFileToString( + temp.path().AppendASCII(extension_file_util::kCurrentVersionFileName), + &version_out)); + ASSERT_EQ(version, version_out); +} + +TEST(ExtensionFileUtil, ReadCurrentVersion) { + // Read the version from a valid extension. + FilePath extension_path; + ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extension_path)); + extension_path = extension_path.AppendASCII("extensions") + .AppendASCII("good") + .AppendASCII("Extensions") + .AppendASCII("behllobkkfkfnphdnhnkndlbkcpglgmj"); + + std::string version; + ASSERT_TRUE(extension_file_util::ReadCurrentVersion(extension_path, + &version)); + ASSERT_EQ("1.0.0.0", version); + + // Create an invalid extension and read its current version. + ScopedTempDir temp; + ASSERT_TRUE(temp.CreateUniqueTempDir()); + ASSERT_FALSE(extension_file_util::ReadCurrentVersion(temp.path(), &version)); +} + +TEST(ExtensionFileUtil, CompareToInstalledVersion) { + // Compare to an existing extension. + FilePath install_directory; + ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &install_directory)); + install_directory = install_directory.AppendASCII("extensions") + .AppendASCII("good") + .AppendASCII("Extensions"); + + std::string id = "behllobkkfkfnphdnhnkndlbkcpglgmj"; + std::string version = "1.0.0.0"; + std::string version_out; + + ASSERT_EQ(Extension::UPGRADE, extension_file_util::CompareToInstalledVersion( + install_directory, id, "1.0.0.1", &version_out)); + ASSERT_EQ(version, version_out); + + version_out.clear(); + ASSERT_EQ(Extension::REINSTALL, + extension_file_util::CompareToInstalledVersion( + install_directory, id, "1.0.0.0", &version_out)); + ASSERT_EQ(version, version_out); + + version_out.clear(); + ASSERT_EQ(Extension::REINSTALL, + extension_file_util::CompareToInstalledVersion( + install_directory, id, "1.0.0", &version_out)); + ASSERT_EQ(version, version_out); + + version_out.clear(); + ASSERT_EQ(Extension::DOWNGRADE, + extension_file_util::CompareToInstalledVersion( + install_directory, id, "0.0.1.0", &version_out)); + ASSERT_EQ(version, version_out); + + // Compare to an extension that is missing its Current Version file. + ScopedTempDir temp; + ASSERT_TRUE(temp.CreateUniqueTempDir()); + FilePath src = install_directory.AppendASCII(id).AppendASCII(version); + FilePath dest = temp.path().AppendASCII(id).AppendASCII(version); + ASSERT_TRUE(file_util::CreateDirectory(dest.DirName())); + ASSERT_TRUE(file_util::CopyDirectory(src, dest, true)); + + version_out.clear(); + ASSERT_EQ(Extension::NEW_INSTALL, + extension_file_util::CompareToInstalledVersion( + temp.path(), id, "0.0.1.0", &version_out)); + ASSERT_EQ("", version_out); + + // Compare to a completely non-existent extension. + version_out.clear(); + ASSERT_EQ(Extension::NEW_INSTALL, + extension_file_util::CompareToInstalledVersion( + temp.path(), "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "0.0.1.0", + &version_out)); + ASSERT_EQ("", version_out); +} + +// TODO(aa): More tests as motivation allows. Maybe steal some from +// ExtensionsService? Many of them could probably be tested here without the +// MessageLoop shenanigans. diff --git a/chrome/browser/extensions/extensions_service.cc b/chrome/browser/extensions/extensions_service.cc index 1b993e6..0733380 100644 --- a/chrome/browser/extensions/extensions_service.cc +++ b/chrome/browser/extensions/extensions_service.cc @@ -7,16 +7,14 @@ #include "app/l10n_util.h" #include "base/command_line.h" #include "base/file_util.h" -#include "base/scoped_temp_dir.h" -#include "base/stl_util-inl.h" #include "base/string_util.h" #include "base/values.h" -#include "net/base/file_stream.h" #include "chrome/browser/browser.h" #include "chrome/browser/browser_list.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/chrome_thread.h" #include "chrome/browser/extensions/extension_browser_event_router.h" +#include "chrome/browser/extensions/extension_file_util.h" #include "chrome/browser/extensions/extension_process_manager.h" #include "chrome/browser/extensions/extension_updater.h" #include "chrome/browser/extensions/external_extension_provider.h" @@ -26,9 +24,7 @@ #include "chrome/browser/tab_contents/tab_contents.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/extensions/extension.h" -#include "chrome/common/extensions/extension_constants.h" #include "chrome/common/extensions/extension_error_reporter.h" -#include "chrome/common/json_value_serializer.h" #include "chrome/common/notification_service.h" #include "chrome/common/pref_names.h" #include "chrome/common/pref_service.h" @@ -249,8 +245,9 @@ void ExtensionsService::UninstallExtension(const std::string& extension_id, // Tell the backend to start deleting installed extensions on the file thread. if (Extension::LOAD != extension->location()) { - backend_loop_->PostTask(FROM_HERE, NewRunnableMethod(backend_.get(), - &ExtensionsServiceBackend::UninstallExtension, extension_id)); + backend_loop_->PostTask(FROM_HERE, NewRunnableFunction( + &extension_file_util::UninstallExtension, extension_id, + install_directory_)); } UnloadExtension(extension_id); @@ -321,9 +318,8 @@ void ExtensionsService::ReloadExtensions() { } void ExtensionsService::GarbageCollectExtensions() { - backend_loop_->PostTask(FROM_HERE, NewRunnableMethod(backend_.get(), - &ExtensionsServiceBackend::GarbageCollectExtensions, - scoped_refptr<ExtensionsService>(this))); + backend_loop_->PostTask(FROM_HERE, NewRunnableFunction( + &extension_file_util::GarbageCollectExtensions, install_directory_)); } void ExtensionsService::OnLoadedInstalledExtensions() { @@ -524,55 +520,6 @@ void ExtensionsServiceBackend::LoadInstalledExtensions( frontend_, &ExtensionsService::OnLoadedInstalledExtensions)); } -void ExtensionsServiceBackend::GarbageCollectExtensions( - scoped_refptr<ExtensionsService> frontend) { - frontend_ = frontend; - alert_on_error_ = false; - - // 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); - - LOG(INFO) << "Loading installed extensions..."; - - // Find all child directories in the install directory and load their - // manifests. Post errors and results to the frontend. - file_util::FileEnumerator enumerator(install_directory_absolute, - false, // Not recursive. - file_util::FileEnumerator::DIRECTORIES); - FilePath extension_path; - for (extension_path = enumerator.Next(); !extension_path.value().empty(); - extension_path = enumerator.Next()) { - std::string extension_id = WideToASCII( - extension_path.BaseName().ToWStringHack()); - - // If there is no Current Version file, just delete the directory and move - // on. This can legitimately happen when an uninstall does not complete, for - // example, when a plugin is in use at uninstall time. - FilePath current_version_path = extension_path.AppendASCII( - ExtensionsService::kCurrentVersionFileName); - if (!file_util::PathExists(current_version_path)) { - LOG(INFO) << "Deleting incomplete install for directory " - << WideToASCII(extension_path.ToWStringHack()) << "."; - file_util::Delete(extension_path, true); // Recursive. - continue; - } - - // Ignore directories that aren't valid IDs. - if (!Extension::IdIsValid(extension_id)) { - LOG(WARNING) << "Invalid extension ID encountered in extensions " - "directory: " << extension_id; - // TODO(erikkay) delete these eventually too... - continue; - } - - // TODO(erikkay) check for extensions that aren't loaded? - } -} - void ExtensionsServiceBackend::LoadSingleExtension( const FilePath& path_in, scoped_refptr<ExtensionsService> frontend) { frontend_ = frontend; @@ -586,14 +533,21 @@ void ExtensionsServiceBackend::LoadSingleExtension( LOG(INFO) << "Loading single extension from " << WideToASCII(extension_path.BaseName().ToWStringHack()); - Extension* extension = LoadExtension(extension_path, - Extension::LOAD, - false); // Don't require id. - if (extension) { - ExtensionList* extensions = new ExtensionList; - extensions->push_back(extension); - ReportExtensionsLoaded(extensions); + std::string error; + Extension* extension = extension_file_util::LoadExtension( + extension_path, + false, // Don't require id + &error); + + if (!extension) { + ReportExtensionLoadError(extension_path, error); + return; } + + extension->set_location(Extension::LOAD); + ExtensionList* extensions = new ExtensionList; + extensions->push_back(extension); + ReportExtensionsLoaded(extensions); } void ExtensionsServiceBackend::LoadInstalledExtension( @@ -608,150 +562,26 @@ void ExtensionsServiceBackend::LoadInstalledExtension( return; } - Extension* extension = - LoadExtension(FilePath(path), location, true); // Require id. + std::string error; + Extension* extension = extension_file_util::LoadExtension( + path, + true, // Require id + &error); + + if (!extension) { + ReportExtensionLoadError(path, error); + return; + } // TODO(erikkay) now we only report a single extension loaded at a time. // Perhaps we should change the notifications to remove ExtensionList. + extension->set_location(location); ExtensionList* extensions = new ExtensionList; if (extension) extensions->push_back(extension); ReportExtensionsLoaded(extensions); } -DictionaryValue* ExtensionsServiceBackend::ReadManifest( - const FilePath& manifest_path, std::string* error) { - JSONFileValueSerializer serializer(manifest_path); - scoped_ptr<Value> root(serializer.Deserialize(error)); - if (!root.get()) - return NULL; - - if (!root->IsType(Value::TYPE_DICTIONARY)) { - *error = extension_manifest_errors::kInvalidManifest; - return NULL; - } - - return static_cast<DictionaryValue*>(root.release()); -} - -Extension* ExtensionsServiceBackend::LoadExtension( - const FilePath& extension_path, - Extension::Location location, - bool require_id) { - FilePath manifest_path = - extension_path.AppendASCII(Extension::kManifestFilename); - if (!file_util::PathExists(manifest_path)) { - ReportExtensionLoadError(extension_path, - extension_manifest_errors::kInvalidManifest); - return NULL; - } - - std::string error; - scoped_ptr<DictionaryValue> root(ReadManifest(manifest_path, &error)); - if (!root.get()) { - ReportExtensionLoadError(extension_path, error); - return NULL; - } - - scoped_ptr<Extension> extension(new Extension(extension_path)); - if (!extension->InitFromValue(*root.get(), require_id, &error)) { - ReportExtensionLoadError(extension_path, error); - return NULL; - } - - extension->set_location(location); - - // Validate icons exist. - for (std::map<int, std::string>::const_iterator iter = - extension->icons().begin(); iter != extension->icons().end(); ++iter) { - if (!file_util::PathExists(extension->GetResourcePath(iter->second))) { - ReportExtensionLoadError(extension_path, - StringPrintf("Could not load extension icon '%s'.", - iter->second.c_str())); - return false; - } - } - - // Theme resource validation. - if (extension->IsTheme()) { - DictionaryValue* images_value = extension->GetThemeImages(); - if (images_value) { - DictionaryValue::key_iterator iter = images_value->begin_keys(); - while (iter != images_value->end_keys()) { - std::string val; - if (images_value->GetString(*iter , &val)) { - FilePath image_path = extension->path().AppendASCII(val); - if (!file_util::PathExists(image_path)) { - ReportExtensionLoadError(extension_path, - StringPrintf("Could not load '%s' for theme.", - WideToUTF8(image_path.ToWStringHack()).c_str())); - return NULL; - } - } - ++iter; - } - } - - // Themes cannot contain other extension types. - return extension.release(); - } - - // Validate that claimed script resources actually exist. - for (size_t i = 0; i < extension->content_scripts().size(); ++i) { - const UserScript& script = extension->content_scripts()[i]; - - for (size_t j = 0; j < script.js_scripts().size(); j++) { - const FilePath& path = script.js_scripts()[j].path(); - if (!file_util::PathExists(path)) { - ReportExtensionLoadError(extension_path, - StringPrintf("Could not load '%s' for content script.", - WideToUTF8(path.ToWStringHack()).c_str())); - return NULL; - } - } - - for (size_t j = 0; j < script.css_scripts().size(); j++) { - const FilePath& path = script.css_scripts()[j].path(); - if (!file_util::PathExists(path)) { - ReportExtensionLoadError(extension_path, - StringPrintf("Could not load '%s' for content script.", - WideToUTF8(path.ToWStringHack()).c_str())); - return NULL; - } - } - } - - for (size_t i = 0; i < extension->plugins().size(); ++i) { - const Extension::PluginInfo& plugin = extension->plugins()[i]; - if (!file_util::PathExists(plugin.path)) { - ReportExtensionLoadError(extension_path, - StringPrintf("Could not load '%s' for plugin.", - WideToUTF8(plugin.path.ToWStringHack()).c_str())); - return NULL; - } - } - - // Validate icon location for page actions. - const PageActionMap& page_actions = extension->page_actions(); - for (PageActionMap::const_iterator i(page_actions.begin()); - i != page_actions.end(); ++i) { - PageAction* page_action = i->second; - const std::vector<FilePath>& icon_paths = page_action->icon_paths(); - for (std::vector<FilePath>::const_iterator iter = icon_paths.begin(); - iter != icon_paths.end(); ++iter) { - FilePath path = *iter; - if (!file_util::PathExists(path)) { - ReportExtensionLoadError(extension_path, - StringPrintf("Could not load icon '%s' for page action.", - WideToUTF8(path.ToWStringHack()).c_str())); - return NULL; - } - } - } - - return extension.release(); -} - void ExtensionsServiceBackend::ReportExtensionLoadError( const FilePath& extension_path, const std::string &error) { // TODO(port): note that this isn't guaranteed to work properly on Linux. @@ -767,123 +597,6 @@ void ExtensionsServiceBackend::ReportExtensionsLoaded( frontend_, &ExtensionsService::OnExtensionsLoaded, extensions)); } -bool ExtensionsServiceBackend::ReadCurrentVersion(const FilePath& dir, - std::string* version_string) { - FilePath current_version = - 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); - return true; - } - } - return false; -} - -Extension::InstallType ExtensionsServiceBackend::CompareToInstalledVersion( - const std::string& id, - const std::string& new_version_str, - std::string *current_version_str) { - CHECK(current_version_str); - FilePath dir(install_directory_.AppendASCII(id.c_str())); - if (!ReadCurrentVersion(dir, current_version_str)) - return Extension::NEW_INSTALL; - - scoped_ptr<Version> current_version( - Version::GetVersionFromString(*current_version_str)); - scoped_ptr<Version> new_version( - Version::GetVersionFromString(new_version_str)); - int comp = new_version->CompareTo(*current_version); - if (comp > 0) - return Extension::UPGRADE; - else if (comp == 0) - return Extension::REINSTALL; - else - return Extension::DOWNGRADE; -} - -bool ExtensionsServiceBackend::NeedsReinstall(const std::string& id, - const std::string& current_version) { - // Verify that the directory actually exists. - // TODO(erikkay): A further step would be to verify that the extension - // has actually loaded successfully. - FilePath dir(install_directory_.AppendASCII(id.c_str())); - FilePath version_dir(dir.AppendASCII(current_version)); - return !file_util::PathExists(version_dir); -} - -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(source_dir, - "Can't delete existing version directory."); - return false; - } - } else { - FilePath parent = dest_dir.DirName(); - if (!file_util::DirectoryExists(parent)) { - if (!file_util::CreateDirectory(parent)) { - ReportExtensionInstallError(source_dir, - "Couldn't create extension directory."); - return false; - } - } - } - if (!file_util::Move(source_dir, dest_dir)) { - ReportExtensionInstallError(source_dir, - "Couldn't move temporary directory."); - return false; - } - - return true; -} - -bool ExtensionsServiceBackend::SetCurrentVersion(const FilePath& dest_dir, - const std::string& version) { - // Write out the new CurrentVersion file. - // <profile>/Extension/<name>/CurrentVersion - FilePath current_version = - dest_dir.AppendASCII(ExtensionsService::kCurrentVersionFileName); - FilePath current_version_old = - current_version.InsertBeforeExtension(FILE_PATH_LITERAL("_old")); - if (file_util::PathExists(current_version_old)) { - if (!file_util::Delete(current_version_old, false)) { - ReportExtensionInstallError(dest_dir, - "Couldn't remove CurrentVersion_old file."); - return false; - } - } - if (file_util::PathExists(current_version)) { - if (!file_util::Move(current_version, current_version_old)) { - ReportExtensionInstallError(dest_dir, - "Couldn't move CurrentVersion file."); - return false; - } - } - net::FileStream stream; - int flags = base::PLATFORM_FILE_CREATE_ALWAYS | base::PLATFORM_FILE_WRITE; - 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(dest_dir, - "Couldn't create CurrentVersion file."); - return false; - } - return true; -} - void ExtensionsServiceBackend::InstallExtension( const FilePath& extension_path, bool from_gallery, scoped_refptr<ExtensionsService> frontend) { @@ -992,53 +705,37 @@ void ExtensionsServiceBackend::OnExtensionUnpacked( // If an expected id was provided, make sure it matches. if (!expected_id.empty() && expected_id != extension->id()) { - std::string error_msg = "ID in new extension manifest ("; - error_msg += extension->id(); - error_msg += ") does not match expected ID ("; - error_msg += expected_id; - error_msg += ")"; - ReportExtensionInstallError(crx_path, error_msg); + ReportExtensionInstallError(crx_path, + StringPrintf("ID in new extension manifest (%s) does not match " + "expected id (%s)", extension->id().c_str(), + expected_id.c_str())); return; } - // <profile>/Extensions/<id> - FilePath dest_dir = install_directory_.AppendASCII(extension->id()); - std::string version = extension->VersionString(); - std::string current_version; - Extension::InstallType install_type = - CompareToInstalledVersion(extension->id(), version, ¤t_version); - - // Do not allow downgrade. - if (install_type == Extension::DOWNGRADE) { - ReportExtensionInstallError(crx_path, - "Error: Attempt to downgrade extension from more recent version."); + FilePath version_dir; + Extension::InstallType install_type = Extension::INSTALL_ERROR; + std::string error_msg; + if (!extension_file_util::InstallExtension(unpacked_path, install_directory_, + extension->id(), + extension->VersionString(), + &version_dir, + &install_type, &error_msg)) { + ReportExtensionInstallError(crx_path, error_msg); return; } - if (install_type == Extension::REINSTALL) { - if (NeedsReinstall(extension->id(), current_version)) { - // Treat corrupted existing installation as new install case. - install_type = Extension::NEW_INSTALL; - } else { - // The client may use this as a signal (to switch themes, for instance). - ReportExtensionOverinstallAttempted(extension->id(), crx_path); - return; - } + if (install_type == Extension::DOWNGRADE) { + ReportExtensionInstallError(crx_path, "Attempted to downgrade extension."); + return; } - // <profile>/Extensions/<dir_name>/<version> - FilePath version_dir = dest_dir.AppendASCII(version); extension->set_path(version_dir); - // If anything fails after this, we want to delete the extension dir. - ScopedTempDir scoped_version_dir; - scoped_version_dir.Set(version_dir); - - if (!InstallDirSafely(unpacked_path, version_dir)) - return; - - if (!SetCurrentVersion(dest_dir, version)) + if (install_type == Extension::REINSTALL) { + // The client may use this as a signal (to switch themes, for instance). + ReportExtensionOverinstallAttempted(extension->id(), crx_path); return; + } frontend_loop_->PostTask(FROM_HERE, NewRunnableMethod( frontend_, &ExtensionsService::OnExtensionInstalled, crx_path, @@ -1051,8 +748,6 @@ void ExtensionsServiceBackend::OnExtensionUnpacked( // Hand off ownership of the extension to the frontend. extensions->push_back(extension_deleter.release()); ReportExtensionsLoaded(extensions); - - scoped_version_dir.Take(); } void ExtensionsServiceBackend::ReportExtensionInstallError( @@ -1078,23 +773,6 @@ void ExtensionsServiceBackend::ReportExtensionOverinstallAttempted( path)); } -bool ExtensionsServiceBackend::ShouldSkipInstallingExtension( - const std::set<std::string>& ids_to_ignore, - const std::string& id) { - if (ids_to_ignore.find(id) != ids_to_ignore.end()) { - LOG(INFO) << "Skipping uninstalled external extension " << id; - return true; - } - return false; -} - -void ExtensionsServiceBackend::CheckVersionAndInstallExtension( - const std::string& id, const Version* extension_version, - const FilePath& extension_path) { - if (ShouldInstall(id, extension_version)) - InstallOrUpdateExtension(FilePath(extension_path), false, id, false); -} - bool ExtensionsServiceBackend::LookupExternalExtension( const std::string& id, Version** version, Extension::Location* location) { scoped_ptr<Version> extension_version; @@ -1154,41 +832,6 @@ bool ExtensionsServiceBackend::CheckExternalUninstall( return true; // This is not a known extension, uninstall. } -// Assumes that the extension isn't currently loaded or in use. -void ExtensionsServiceBackend::UninstallExtension( - const std::string& extension_id) { - // First, delete the Current Version file. If the directory delete fails, then - // at least the extension won't be loaded again. - FilePath extension_directory = install_directory_.AppendASCII(extension_id); - - if (!file_util::PathExists(extension_directory)) { - LOG(WARNING) << "Asked to remove a non-existent extension " << extension_id; - return; - } - - FilePath current_version_file = extension_directory.AppendASCII( - ExtensionsService::kCurrentVersionFileName); - if (!file_util::PathExists(current_version_file)) { - LOG(WARNING) << "Extension " << extension_id - << " does not have a Current Version file."; - } else { - if (!file_util::Delete(current_version_file, false)) { - LOG(WARNING) << "Could not delete Current Version file for extension " - << extension_id; - return; - } - } - - // OK, now try and delete the entire rest of the directory. One major place - // this can fail is if the extension contains a plugin (stupid plugins). It's - // not a big deal though, because we'll notice next time we startup that the - // Current Version file is gone and finish the delete then. - if (!file_util::Delete(extension_directory, true)) { - LOG(WARNING) << "Could not delete directory for extension " - << extension_id; - } -} - void ExtensionsServiceBackend::ClearProvidersForTesting() { external_extension_providers_.clear(); } @@ -1203,19 +846,8 @@ void ExtensionsServiceBackend::SetProviderForTesting( void ExtensionsServiceBackend::OnExternalExtensionFound( const std::string& id, const Version* version, const FilePath& path) { - CheckVersionAndInstallExtension(id, version, path); -} - -bool ExtensionsServiceBackend::ShouldInstall(const std::string& id, - const Version* version) { - std::string current_version; - Extension::InstallType install_type = - CompareToInstalledVersion(id, version->GetString(), ¤t_version); - - if (install_type == Extension::DOWNGRADE) - return false; - - return (install_type == Extension::UPGRADE || - install_type == Extension::NEW_INSTALL || - NeedsReinstall(id, current_version)); + InstallOrUpdateExtension(path, + false, // not from gallery + id, // expected id + true); // silent } diff --git a/chrome/browser/extensions/extensions_service.h b/chrome/browser/extensions/extensions_service.h index 5fc036b..a62ebee 100644 --- a/chrome/browser/extensions/extensions_service.h +++ b/chrome/browser/extensions/extensions_service.h @@ -281,10 +281,6 @@ class ExtensionsServiceBackend void LoadInstalledExtensions(scoped_refptr<ExtensionsService> frontend, InstalledExtensions* installed); - // Scans the extension installation directory to look for partially installed - // or extensions to uninstall. - void GarbageCollectExtensions(scoped_refptr<ExtensionsService> frontend); - // Loads a single extension from |path| where |path| is the top directory of // a specific extension where its manifest file lives. // Errors are reported through ExtensionErrorReporter. On completion, @@ -314,11 +310,6 @@ class ExtensionsServiceBackend void CheckForExternalUpdates(std::set<std::string> ids_to_ignore, scoped_refptr<ExtensionsService> frontend); - // Deletes all versions of the extension from the filesystem. Note that only - // extensions whose location() == INTERNAL can be uninstalled. Attempting to - // uninstall other extensions will silently fail. - void UninstallExtension(const std::string& extension_id); - // Clear all ExternalExtensionProviders. void ClearProvidersForTesting(); @@ -339,22 +330,6 @@ class ExtensionsServiceBackend void LoadInstalledExtension(const std::string& id, const FilePath& path, Extension::Location location); - // Utility function to read an extension manifest and return it as a - // DictionaryValue. If it fails, NULL is returned and |error| contains an - // appropriate message. - DictionaryValue* ReadManifest(const FilePath& manifest_path, - std::string* error); - - // Load a single extension from |extension_path|, the top directory of - // a specific extension where its manifest file lives. - Extension* LoadExtension(const FilePath& extension_path, - Extension::Location location, - bool require_id); - - // Load a single extension from |extension_path|, the top directory of - // a versioned extension where its Current Version file lives. - Extension* LoadExtensionCurrentVersion(const FilePath& extension_path); - // 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 @@ -395,17 +370,6 @@ class ExtensionsServiceBackend void ReportExtensionOverinstallAttempted(const std::string& id, const FilePath& path); - // Checks a set of strings (containing id's to ignore) in order to determine - // if the extension should be installed. - bool ShouldSkipInstallingExtension(const std::set<std::string>& ids_to_ignore, - const std::string& id); - - // Installs the extension if the extension is a newer version or if the - // extension hasn't been installed before. - void CheckVersionAndInstallExtension(const std::string& id, - const Version* extension_version, - const FilePath& extension_path); - // Lookup an external extension by |id| by going through all registered // external extension providers until we find a provider that contains an // extension that matches. If |version| is not NULL, the extension version @@ -416,45 +380,12 @@ class ExtensionsServiceBackend Version** version, Extension::Location* location); - // Read the manifest from the front of the extension file. - // Caller takes ownership of return value. - DictionaryValue* ReadManifest(const FilePath& extension_path); - - // Reads the Current Version file from |dir| into |version_string|. - bool ReadCurrentVersion(const FilePath& dir, std::string* version_string); - - // Look for an existing installation of the extension |id| & return - // an InstallType that would result from installing |new_version_str|. - Extension::InstallType CompareToInstalledVersion(const std::string& id, - const std::string& new_version_str, std::string* current_version_str); - - // Does an existing installed extension need to be reinstalled. - bool NeedsReinstall(const std::string& id, - const std::string& current_version); - - // Install the extension dir by moving it from |source| to |dest| safely. - bool InstallDirSafely(const FilePath& source, - const FilePath& dest); - - // Update the CurrentVersion file in |dest_dir| to |version|. - bool SetCurrentVersion(const FilePath& dest_dir, - const std::string& version); - // For the extension in |version_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 std::string& id, Extension::Location location); - // 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 Version* 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. ExtensionsService* frontend_; |