diff options
author | phajdan.jr@chromium.org <phajdan.jr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-02-24 09:54:13 +0000 |
---|---|---|
committer | phajdan.jr@chromium.org <phajdan.jr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-02-24 09:54:13 +0000 |
commit | 7c927b60bffa117129ba9214fcb6174d753351d9 (patch) | |
tree | 785de404571d36e57c8c9b916426805fc43ee476 /chrome/common/extensions | |
parent | af2ae52b8f5ec53e1ba1b4593368cd66f59c3a4d (diff) | |
download | chromium_src-7c927b60bffa117129ba9214fcb6174d753351d9.zip chromium_src-7c927b60bffa117129ba9214fcb6174d753351d9.tar.gz chromium_src-7c927b60bffa117129ba9214fcb6174d753351d9.tar.bz2 |
Further reduce the bad dependency of chrome/common on chrome/browser.
TEST=none
BUG=none
Review URL: http://codereview.chromium.org/656011
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@39877 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/common/extensions')
-rw-r--r-- | chrome/common/extensions/extension_file_util.cc | 521 | ||||
-rw-r--r-- | chrome/common/extensions/extension_file_util.h | 108 | ||||
-rw-r--r-- | chrome/common/extensions/extension_file_util_unittest.cc | 258 | ||||
-rw-r--r-- | chrome/common/extensions/extension_l10n_util.cc | 2 | ||||
-rw-r--r-- | chrome/common/extensions/extension_unpacker.cc | 2 |
5 files changed, 889 insertions, 2 deletions
diff --git a/chrome/common/extensions/extension_file_util.cc b/chrome/common/extensions/extension_file_util.cc new file mode 100644 index 0000000..0192b67 --- /dev/null +++ b/chrome/common/extensions/extension_file_util.cc @@ -0,0 +1,521 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/common/extensions/extension_file_util.h" + +#include "app/l10n_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_l10n_util.h" +#include "chrome/common/extensions/extension_constants.h" +#include "chrome/common/json_value_serializer.h" +#include "net/base/escape.h" +#include "net/base/file_stream.h" + +namespace errors = extension_manifest_errors; +namespace keys = extension_manifest_keys; + +namespace extension_file_util { + +// Validates locale info. Doesn't check if messages.json files are valid. +static bool ValidateLocaleInfo(const Extension& extension, std::string* error); + +const char kInstallDirectoryName[] = "Extensions"; +// TODO(mpcomplete): obsolete. remove after migration period. +// http://code.google.com/p/chromium/issues/detail?id=19733 +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; +} + +Extension::InstallType CompareToInstalledVersion( + const FilePath& extensions_dir, + const std::string& extension_id, + const std::string& current_version_str, + const std::string& new_version_str, + FilePath* version_dir) { + FilePath dest_dir = extensions_dir.AppendASCII(extension_id); + FilePath current_version_dir = dest_dir.AppendASCII(current_version_str); + *version_dir = dest_dir.AppendASCII(new_version_str); + + if (current_version_str.empty()) + 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; + if (comp < 0) + return Extension::DOWNGRADE; + + // Same version. Treat corrupted existing installation as new install case. + if (!SanityCheckExtension(current_version_dir)) + return Extension::NEW_INSTALL; + + return Extension::REINSTALL; +} + +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.Append(Extension::kManifestFilename)); + return file_util::PathExists(dir) && file_util::PathExists(manifest_file); +} + +bool InstallExtension(const FilePath& src_dir, + const FilePath& version_dir, + std::string* error) { + // 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; + } + + scoped_version_dir.Take(); + return true; +} + +Extension* LoadExtension(const FilePath& extension_path, + bool require_key, + std::string* error) { + FilePath manifest_path = + extension_path.Append(Extension::kManifestFilename); + if (!file_util::PathExists(manifest_path)) { + *error = extension_manifest_errors::kManifestUnreadable; + return NULL; + } + + JSONFileValueSerializer serializer(manifest_path); + scoped_ptr<Value> root(serializer.Deserialize(error)); + if (!root.get()) { + if (error->empty()) { + // If |error| is empty, than the file could not be read. + // It would be cleaner to have the JSON reader give a specific error + // in this case, but other code tests for a file error with + // error->empty(). For now, be consistent. + *error = extension_manifest_errors::kManifestUnreadable; + } else { + *error = StringPrintf("%s %s", + extension_manifest_errors::kManifestParseError, + error->c_str()); + } + return NULL; + } + + if (!root->IsType(Value::TYPE_DICTIONARY)) { + *error = extension_manifest_errors::kInvalidManifest; + return NULL; + } + + DictionaryValue* manifest = static_cast<DictionaryValue*>(root.get()); + + scoped_ptr<Extension> extension(new Extension(extension_path)); + + if (!extension_l10n_util::LocalizeExtension(extension.get(), manifest, error)) + return NULL; + + if (!extension->InitFromValue(*manifest, require_key, error)) + return NULL; + + if (!ValidateExtension(extension.get(), error)) + return NULL; + + return extension.release(); +} + +bool ValidateExtension(Extension* extension, std::string* error) { + // Validate icons exist. + for (std::map<int, std::string>::const_iterator iter = + extension->icons().begin(); iter != extension->icons().end(); ++iter) { + const FilePath path = extension->GetResource(iter->second).GetFilePath(); + if (!file_util::PathExists(path)) { + *error = 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) { + for (DictionaryValue::key_iterator iter = images_value->begin_keys(); + iter != images_value->end_keys(); ++iter) { + std::string val; + if (images_value->GetStringWithoutPathExpansion(*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 false; + } + } + } + } + + // Themes cannot contain other extension types. + return true; + } + + // 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 UserScript::File& js_script = script.js_scripts()[j]; + const FilePath& path = ExtensionResource::GetFilePath( + js_script.extension_root(), js_script.relative_path()); + if (!file_util::PathExists(path)) { + *error = StringPrintf( + "Could not load javascript '%s' for content script.", + WideToUTF8(js_script.relative_path().ToWStringHack()).c_str()); + return false; + } + } + + for (size_t j = 0; j < script.css_scripts().size(); j++) { + const UserScript::File& css_script = script.css_scripts()[j]; + const FilePath& path = ExtensionResource::GetFilePath( + css_script.extension_root(), css_script.relative_path()); + if (!file_util::PathExists(path)) { + *error = StringPrintf( + "Could not load css '%s' for content script.", + WideToUTF8(css_script.relative_path().ToWStringHack()).c_str()); + return false; + } + } + } + + // Validate claimed plugin paths. + 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 false; + } + } + + // Validate icon location for page actions. + ExtensionAction* page_action = extension->page_action(); + if (page_action) { + std::vector<std::string> icon_paths(*page_action->icon_paths()); + if (!page_action->default_icon_path().empty()) + icon_paths.push_back(page_action->default_icon_path()); + for (std::vector<std::string>::iterator iter = icon_paths.begin(); + iter != icon_paths.end(); ++iter) { + if (!file_util::PathExists(extension->GetResource(*iter).GetFilePath())) { + *error = StringPrintf("Could not load icon '%s' for page action.", + iter->c_str()); + return false; + } + } + } + + // Validate icon location for browser actions. + // Note: browser actions don't use the icon_paths(). + ExtensionAction* browser_action = extension->browser_action(); + if (browser_action) { + std::string path = browser_action->default_icon_path(); + if (!path.empty() && + !file_util::PathExists(extension->GetResource(path).GetFilePath())) { + *error = StringPrintf("Could not load icon '%s' for browser action.", + path.c_str()); + return false; + } + } + + // Validate background page location. + if (!extension->background_url().is_empty()) { + FilePath page_path = ExtensionURLToRelativeFilePath( + extension->background_url()); + const FilePath path = extension->GetResource(page_path).GetFilePath(); + if (!file_util::PathExists(path)) { + *error = StringPrintf("Could not load background page '%s'.", + WideToUTF8(path.ToWStringHack()).c_str()); + return false; + } + } + + // Validate locale info. + if (!ValidateLocaleInfo(*extension, error)) + return false; + + // Check children of extension root to see if any of them start with _ and is + // not on the reserved list. + if (!CheckForIllegalFilenames(extension->path(), error)) { + return false; + } + + return true; +} + +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)) { + // This is OK, since we're phasing out the 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, + const std::set<std::string>& installed_ids, + const std::map<std::string, std::string>& installed_versions) { + // 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 entry in the prefs 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. + if (installed_ids.count(extension_id) == 0) { + LOG(INFO) << "Deleting unreferenced install for directory " + << WideToASCII(extension_path.ToWStringHack()) << "."; + file_util::Delete(extension_path, true); // Recursive. + continue; + } + + // Delete 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; + } + + // Clean up old version directories. + file_util::FileEnumerator versions_enumerator( + extension_path, + false, // Not recursive. + file_util::FileEnumerator::DIRECTORIES); + for (FilePath version_dir = versions_enumerator.Next(); + !version_dir.value().empty(); + version_dir = versions_enumerator.Next()) { + std::map<std::string, std::string>::const_iterator installed_version = + installed_versions.find(extension_id); + if (installed_version == installed_versions.end()) { + NOTREACHED() << "No installed version found for " << extension_id; + continue; + } + + std::string version = WideToASCII(version_dir.BaseName().ToWStringHack()); + if (version != installed_version->second) { + LOG(INFO) << "Deleting old version for directory " + << WideToASCII(version_dir.ToWStringHack()) << "."; + file_util::Delete(version_dir, true); // Recursive. + } + } + } +} + +ExtensionMessageBundle* LoadExtensionMessageBundle( + const FilePath& extension_path, + const std::string& default_locale, + std::string* error) { + error->clear(); + // Load locale information if available. + FilePath locale_path = extension_path.Append(Extension::kLocaleFolder); + if (!file_util::PathExists(locale_path)) + return NULL; + + std::set<std::string> locales; + if (!extension_l10n_util::GetValidLocales(locale_path, &locales, error)) + return NULL; + + if (default_locale.empty() || + locales.find(default_locale) == locales.end()) { + *error = extension_manifest_errors::kLocalesNoDefaultLocaleSpecified; + return NULL; + } + + ExtensionMessageBundle* message_bundle = + extension_l10n_util::LoadMessageCatalogs( + locale_path, + default_locale, + extension_l10n_util::CurrentLocaleOrDefault(), + locales, + error); + + return message_bundle; +} + +static bool ValidateLocaleInfo(const Extension& extension, std::string* error) { + // default_locale and _locales have to be both present or both missing. + const FilePath path = extension.path().Append(Extension::kLocaleFolder); + bool path_exists = file_util::PathExists(path); + std::string default_locale = extension.default_locale(); + + // If both default locale and _locales folder are empty, skip verification. + if (!default_locale.empty() || path_exists) { + if (default_locale.empty() && path_exists) { + *error = errors::kLocalesNoDefaultLocaleSpecified; + return false; + } else if (!default_locale.empty() && !path_exists) { + *error = errors::kLocalesTreeMissing; + return false; + } + + // Treat all folders under _locales as valid locales. + file_util::FileEnumerator locales(path, + false, + file_util::FileEnumerator::DIRECTORIES); + + FilePath locale_path = locales.Next(); + if (locale_path.empty()) { + *error = errors::kLocalesTreeMissing; + return false; + } + + const FilePath default_locale_path = path.AppendASCII(default_locale); + bool has_default_locale_message_file = false; + do { + // Skip any strings with '.'. This happens sometimes, for example with + // '.svn' directories. + FilePath relative_path; + if (!extension.path().AppendRelativePath(locale_path, &relative_path)) + NOTREACHED(); + std::wstring subdir(relative_path.ToWStringHack()); + if (std::find(subdir.begin(), subdir.end(), L'.') != subdir.end()) + continue; + + FilePath messages_path = + locale_path.Append(Extension::kMessagesFilename); + + if (!file_util::PathExists(messages_path)) { + *error = StringPrintf( + "%s %s", errors::kLocalesMessagesFileMissing, + WideToUTF8(messages_path.ToWStringHack()).c_str()); + return false; + } + + if (locale_path == default_locale_path) + has_default_locale_message_file = true; + } while (!(locale_path = locales.Next()).empty()); + + // Only message file for default locale has to exist. + if (!has_default_locale_message_file) { + *error = errors::kLocalesNoDefaultMessages; + return false; + } + } + + return true; +} + +bool CheckForIllegalFilenames(const FilePath& extension_path, + std::string* error) { + // Reserved underscore names. + static const FilePath::CharType* reserved_names[] = { + Extension::kLocaleFolder, + FILE_PATH_LITERAL("__MACOSX"), + }; + static std::set<FilePath::StringType> reserved_underscore_names( + reserved_names, reserved_names + arraysize(reserved_names)); + + // Enumerate all files and directories in the extension root. + // There is a problem when using pattern "_*" with FileEnumerator, so we have + // to cheat with find_first_of and match all. + file_util::FileEnumerator all_files( + extension_path, + false, + static_cast<file_util::FileEnumerator::FILE_TYPE>( + file_util::FileEnumerator::DIRECTORIES | + file_util::FileEnumerator::FILES)); + + FilePath file; + while (!(file = all_files.Next()).empty()) { + FilePath::StringType filename = file.BaseName().value(); + // Skip all that don't start with "_". + if (filename.find_first_of(FILE_PATH_LITERAL("_")) != 0) continue; + if (reserved_underscore_names.find(filename) == + reserved_underscore_names.end()) { + *error = StringPrintf( + "Cannot load extension with file or directory name %s. " + "Filenames starting with \"_\" are reserved for use by the system.", + filename.c_str()); + return false; + } + } + + return true; +} + +FilePath ExtensionURLToRelativeFilePath(const GURL& url) { + std::string url_path = url.path(); + if (url_path.empty() || url_path[0] != '/') + return FilePath(); + + // Drop the leading slash and convert %-encoded UTF8 to regular UTF8. + std::string file_path = UnescapeURLComponent(url_path.substr(1), + UnescapeRule::SPACES | UnescapeRule::URL_SPECIAL_CHARS); + +#if defined(OS_POSIX) + return FilePath(file_path); +#elif defined(OS_WIN) + return FilePath(UTF8ToWide(file_path)); +#endif +} + +} // namespace extension_file_util diff --git a/chrome/common/extensions/extension_file_util.h b/chrome/common/extensions/extension_file_util.h new file mode 100644 index 0000000..b711df0 --- /dev/null +++ b/chrome/common/extensions/extension_file_util.h @@ -0,0 +1,108 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_COMMON_EXTENSIONS_EXTENSION_FILE_UTIL_H_ +#define CHROME_COMMON_EXTENSIONS_EXTENSION_FILE_UTIL_H_ + +#include <set> +#include <string> + +#include "base/file_path.h" +#include "chrome/common/extensions/extension.h" + +class ExtensionMessageBundle; + +// 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 it is (new, upgrade, overinstall, downgrade) +// given the current version and a newly installing version. |extensions_dir| is +// the root directory containing all extensions in the user profile. +// |extension_id| and |current_version_str| are the id and version of the +// extension contained in |src_dir|, if any. +// +// Returns the full path to the destination version directory and the type of +// install that was attempted. +Extension::InstallType CompareToInstalledVersion( + const FilePath& extensions_dir, + const std::string& extension_id, + const std::string& current_version_str, + const std::string& new_version_str, + FilePath* version_dir); + +// 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|. +// +// 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& version_dir, + std::string* error); + +// Loads and validates 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); + +// Returns true if the given extension object is valid and consistent. +// Otherwise, a description of the error is returned in |error|. +bool ValidateExtension(Extension* extension, 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. +void GarbageCollectExtensions( + const FilePath& extensions_dir, + const std::set<std::string>& installed_ids, + const std::map<std::string, std::string>& installed_versions); + +// Loads extension message catalogs and returns message bundle. +// Returns NULL on error, or if extension is not localized. +ExtensionMessageBundle* LoadExtensionMessageBundle( + const FilePath& extension_path, + const std::string& default_locale, + std::string* error); + +// We need to reserve the namespace of entries that start with "_" for future +// use by Chrome. +// If any files or directories are found using "_" prefix and are not on +// reserved list we return false, and set error message. +bool CheckForIllegalFilenames(const FilePath& extension_path, + std::string* error); + +// Get a relative file path from a chrome-extension:// URL. +FilePath ExtensionURLToRelativeFilePath(const GURL& url); + +} // extension_file_util + +#endif // CHROME_COMMON_EXTENSIONS_EXTENSION_FILE_UTIL_H_ diff --git a/chrome/common/extensions/extension_file_util_unittest.cc b/chrome/common/extensions/extension_file_util_unittest.cc new file mode 100644 index 0000000..9856e42 --- /dev/null +++ b/chrome/common/extensions/extension_file_util_unittest.cc @@ -0,0 +1,258 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/common/extensions/extension_file_util.h" + +#include "base/file_util.h" +#include "base/scoped_temp_dir.h" +#include "base/string_util.h" +#include "base/path_service.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/extensions/extension.h" +#include "chrome/common/extensions/extension_constants.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace keys = extension_manifest_keys; + +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, CompareToInstalledVersion) { + // Compare to an existing extension. + FilePath install_dir; + ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &install_dir)); + install_dir = install_dir.AppendASCII("extensions") + .AppendASCII("good") + .AppendASCII("Extensions"); + + const std::string kId = "behllobkkfkfnphdnhnkndlbkcpglgmj"; + const std::string kCurrentVersion = "1.0.0.0"; + + FilePath version_dir; + + ASSERT_EQ(Extension::UPGRADE, + extension_file_util::CompareToInstalledVersion( + install_dir, kId, kCurrentVersion, "1.0.0.1", &version_dir)); + + ASSERT_EQ(Extension::REINSTALL, + extension_file_util::CompareToInstalledVersion( + install_dir, kId, kCurrentVersion, "1.0.0.0", &version_dir)); + + ASSERT_EQ(Extension::REINSTALL, + extension_file_util::CompareToInstalledVersion( + install_dir, kId, kCurrentVersion, "1.0.0", &version_dir)); + + ASSERT_EQ(Extension::DOWNGRADE, + extension_file_util::CompareToInstalledVersion( + install_dir, kId, kCurrentVersion, "0.0.1.0", &version_dir)); + + // Compare to an extension that is missing its manifest file. + ScopedTempDir temp; + ASSERT_TRUE(temp.CreateUniqueTempDir()); + FilePath src = install_dir.AppendASCII(kId).AppendASCII(kCurrentVersion); + FilePath dest = temp.path().AppendASCII(kId).AppendASCII(kCurrentVersion); + ASSERT_TRUE(file_util::CreateDirectory(dest.DirName())); + ASSERT_TRUE(file_util::CopyDirectory(src, dest, true)); + ASSERT_TRUE(file_util::Delete(dest.AppendASCII("manifest.json"), false)); + + ASSERT_EQ(Extension::NEW_INSTALL, + extension_file_util::CompareToInstalledVersion( + temp.path(), kId, kCurrentVersion, "1.0.0", &version_dir)); + + // Compare to a non-existent extension. + const std::string kMissingVersion = ""; + const std::string kBadId = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; + ASSERT_EQ(Extension::NEW_INSTALL, + extension_file_util::CompareToInstalledVersion( + temp.path(), kBadId, kMissingVersion, "1.0.0", &version_dir)); +} + +TEST(ExtensionFileUtil, LoadExtensionWithValidLocales) { + FilePath install_dir; + ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &install_dir)); + install_dir = install_dir.AppendASCII("extensions") + .AppendASCII("good") + .AppendASCII("Extensions") + .AppendASCII("behllobkkfkfnphdnhnkndlbkcpglgmj") + .AppendASCII("1.0.0.0"); + + std::string error; + scoped_ptr<Extension> extension( + extension_file_util::LoadExtension(install_dir, false, &error)); + ASSERT_TRUE(extension != NULL); + EXPECT_EQ("The first extension that I made.", extension->description()); +} + +TEST(ExtensionFileUtil, LoadExtensionWithoutLocalesFolder) { + FilePath install_dir; + ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &install_dir)); + install_dir = install_dir.AppendASCII("extensions") + .AppendASCII("good") + .AppendASCII("Extensions") + .AppendASCII("bjafgdebaacbbbecmhlhpofkepfkgcpa") + .AppendASCII("1.0"); + + std::string error; + scoped_ptr<Extension> extension( + extension_file_util::LoadExtension(install_dir, false, &error)); + ASSERT_FALSE(extension == NULL); + EXPECT_TRUE(error.empty()); +} + +TEST(ExtensionFileUtil, CheckIllegalFilenamesNoUnderscores) { + ScopedTempDir temp; + ASSERT_TRUE(temp.CreateUniqueTempDir()); + + FilePath src_path = temp.path().AppendASCII("some_dir"); + ASSERT_TRUE(file_util::CreateDirectory(src_path)); + + std::string data = "{ \"name\": { \"message\": \"foobar\" } }"; + ASSERT_TRUE(file_util::WriteFile(src_path.AppendASCII("some_file.txt"), + data.c_str(), data.length())); + std::string error; + EXPECT_TRUE(extension_file_util::CheckForIllegalFilenames(temp.path(), + &error)); +} + +TEST(ExtensionFileUtil, CheckIllegalFilenamesOnlyReserved) { + ScopedTempDir temp; + ASSERT_TRUE(temp.CreateUniqueTempDir()); + + FilePath src_path = temp.path().Append(Extension::kLocaleFolder); + ASSERT_TRUE(file_util::CreateDirectory(src_path)); + + std::string error; + EXPECT_TRUE(extension_file_util::CheckForIllegalFilenames(temp.path(), + &error)); +} + +TEST(ExtensionFileUtil, CheckIllegalFilenamesReservedAndIllegal) { + ScopedTempDir temp; + ASSERT_TRUE(temp.CreateUniqueTempDir()); + + FilePath src_path = temp.path().Append(Extension::kLocaleFolder); + ASSERT_TRUE(file_util::CreateDirectory(src_path)); + + src_path = temp.path().AppendASCII("_some_dir"); + ASSERT_TRUE(file_util::CreateDirectory(src_path)); + + std::string error; + EXPECT_FALSE(extension_file_util::CheckForIllegalFilenames(temp.path(), + &error)); +} + +TEST(ExtensionFileUtil, LoadExtensionGivesHelpfullErrorOnMissingManifest) { + FilePath install_dir; + ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &install_dir)); + install_dir = install_dir.AppendASCII("extensions") + .AppendASCII("bad") + .AppendASCII("Extensions") + .AppendASCII("dddddddddddddddddddddddddddddddd") + .AppendASCII("1.0"); + + std::string error; + scoped_ptr<Extension> extension( + extension_file_util::LoadExtension(install_dir, false, &error)); + ASSERT_TRUE(extension == NULL); + ASSERT_FALSE(error.empty()); + ASSERT_STREQ("Manifest file is missing or unreadable.", error.c_str()); +} + +TEST(ExtensionFileUtil, LoadExtensionGivesHelpfullErrorOnBadManifest) { + FilePath install_dir; + ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &install_dir)); + install_dir = install_dir.AppendASCII("extensions") + .AppendASCII("bad") + .AppendASCII("Extensions") + .AppendASCII("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee") + .AppendASCII("1.0"); + + std::string error; + scoped_ptr<Extension> extension( + extension_file_util::LoadExtension(install_dir, false, &error)); + ASSERT_TRUE(extension == NULL); + ASSERT_FALSE(error.empty()); + ASSERT_STREQ("Manifest is not valid JSON. " + "Line: 2, column: 16, Syntax error.", error.c_str()); +} + +#define URL_PREFIX "chrome-extension://extension-id/" + +TEST(ExtensionFileUtil, ExtensionURLToRelativeFilePath) { + struct TestCase { + const char* url; + const char* expected_relative_path; + } test_cases[] = { + { URL_PREFIX "simple.html", + "simple.html" }, + { URL_PREFIX "directory/to/file.html", + "directory/to/file.html" }, + { URL_PREFIX "escape%20spaces.html", + "escape spaces.html" }, + { URL_PREFIX "%C3%9Cber.html", + "\xC3\x9C" "ber.html" }, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) { + GURL url(test_cases[i].url); +#if defined(OS_POSIX) + FilePath expected_path(test_cases[i].expected_relative_path); +#elif defined(OS_WIN) + FilePath expected_path(UTF8ToWide(test_cases[i].expected_relative_path)); +#endif + + FilePath actual_path = + extension_file_util::ExtensionURLToRelativeFilePath(url); + EXPECT_FALSE(actual_path.IsAbsolute()) << + " For the path " << actual_path.value(); + EXPECT_EQ(expected_path.value(), actual_path.value()); + } +} + +// 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/common/extensions/extension_l10n_util.cc b/chrome/common/extensions/extension_l10n_util.cc index 2fd3f92..3f329fb 100644 --- a/chrome/common/extensions/extension_l10n_util.cc +++ b/chrome/common/extensions/extension_l10n_util.cc @@ -14,10 +14,10 @@ #include "base/string_util.h" #include "base/values.h" #include "chrome/browser/browser_process.h" -#include "chrome/browser/extensions/extension_file_util.h" #include "chrome/browser/renderer_host/resource_dispatcher_host_request_info.h" #include "chrome/common/extensions/extension.h" #include "chrome/common/extensions/extension_constants.h" +#include "chrome/common/extensions/extension_file_util.h" #include "chrome/common/extensions/extension_message_bundle.h" #include "chrome/common/json_value_serializer.h" #include "chrome/common/url_constants.h" diff --git a/chrome/common/extensions/extension_unpacker.cc b/chrome/common/extensions/extension_unpacker.cc index 425105f..ac643df 100644 --- a/chrome/common/extensions/extension_unpacker.cc +++ b/chrome/common/extensions/extension_unpacker.cc @@ -11,10 +11,10 @@ #include "base/thread.h" #include "base/values.h" #include "net/base/file_stream.h" -#include "chrome/browser/extensions/extension_file_util.h" #include "chrome/common/common_param_traits.h" #include "chrome/common/extensions/extension.h" #include "chrome/common/extensions/extension_constants.h" +#include "chrome/common/extensions/extension_file_util.h" #include "chrome/common/json_value_serializer.h" #include "chrome/common/notification_service.h" #include "chrome/common/url_constants.h" |