summaryrefslogtreecommitdiffstats
path: root/chrome/common/extensions/extension_file_util.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/common/extensions/extension_file_util.cc')
-rw-r--r--chrome/common/extensions/extension_file_util.cc521
1 files changed, 521 insertions, 0 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