diff options
author | cira@chromium.org <cira@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-11-18 18:02:47 +0000 |
---|---|---|
committer | cira@chromium.org <cira@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-11-18 18:02:47 +0000 |
commit | 9428edc493bf5097cc74fca5fcaf4506c4068668 (patch) | |
tree | a5db8e196a4e71c71d54ded4c552b2a4d8c50361 /chrome/common/extensions | |
parent | ecc73b2f562a20611804cc07dcff59922dbd09b4 (diff) | |
download | chromium_src-9428edc493bf5097cc74fca5fcaf4506c4068668.zip chromium_src-9428edc493bf5097cc74fca5fcaf4506c4068668.tar.gz chromium_src-9428edc493bf5097cc74fca5fcaf4506c4068668.tar.bz2 |
Parse messages.json in ExtensionUnpacker (like we do for manifest) and pass them to sandboxed_extension_unpacker.
Added unittest files for unpacker and sandboxed unpacker.
TEST=Try loading any of the unpacker samples added in this CL. They should either pass, or show error and fail.
BUG=27362
Review URL: http://codereview.chromium.org/390019
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@32345 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/common/extensions')
-rw-r--r-- | chrome/common/extensions/extension.cc | 8 | ||||
-rw-r--r-- | chrome/common/extensions/extension.h | 5 | ||||
-rw-r--r-- | chrome/common/extensions/extension_constants.cc | 6 | ||||
-rw-r--r-- | chrome/common/extensions/extension_constants.h | 3 | ||||
-rw-r--r-- | chrome/common/extensions/extension_unittest.cc | 11 | ||||
-rw-r--r-- | chrome/common/extensions/extension_unpacker.cc | 74 | ||||
-rw-r--r-- | chrome/common/extensions/extension_unpacker.h | 12 | ||||
-rw-r--r-- | chrome/common/extensions/extension_unpacker_unittest.cc | 120 |
8 files changed, 237 insertions, 2 deletions
diff --git a/chrome/common/extensions/extension.cc b/chrome/common/extensions/extension.cc index f09fba8..d07ba80 100644 --- a/chrome/common/extensions/extension.cc +++ b/chrome/common/extensions/extension.cc @@ -1113,6 +1113,14 @@ bool Extension::InitFromValue(const DictionaryValue& source, bool require_id, } } + if (source.HasKey(keys::kDefaultLocale)) { + if (!source.GetString(keys::kDefaultLocale, &default_locale_) || + default_locale_.empty()) { + *error = errors::kInvalidDefaultLocale; + return false; + } + } + // Chrome URL overrides (optional) if (source.HasKey(keys::kChromeURLOverrides)) { DictionaryValue* overrides; diff --git a/chrome/common/extensions/extension.h b/chrome/common/extensions/extension.h index 8cd5da3..7738acf 100644 --- a/chrome/common/extensions/extension.h +++ b/chrome/common/extensions/extension.h @@ -274,6 +274,8 @@ class Extension { message_bundle_.reset(message_bundle); } + const std::string default_locale() const { return default_locale_; } + // Chrome URL overrides (see ExtensionOverrideUI). const URLOverrideMap& GetChromeURLOverrides() const { return chrome_url_overrides_; @@ -406,6 +408,9 @@ class Extension { // Handles the l10n messages replacement and parsing. scoped_ptr<ExtensionMessageBundle> message_bundle_; + // Default locale for fall back. Can be empty if extension is not localized. + std::string default_locale_; + // A map of chrome:// hostnames (newtab, downloads, etc.) to Extension URLs // which override the handling of those URLs. URLOverrideMap chrome_url_overrides_; diff --git a/chrome/common/extensions/extension_constants.cc b/chrome/common/extensions/extension_constants.cc index c2700dc..fc16fea 100644 --- a/chrome/common/extensions/extension_constants.cc +++ b/chrome/common/extensions/extension_constants.cc @@ -186,8 +186,14 @@ const char* kThemesCannotContainExtensions = "A theme cannot contain extensions code."; const char* kLocalesNoDefaultLocaleSpecified = "Localization used, but default_locale wasn't specified in the manifest."; +const char* kLocalesNoDefaultMessages = + "Default locale is defined but default data couldn't be loaded."; const char* kLocalesNoValidLocaleNamesListed = "No valid locale name could be found in _locales directory."; +const char* kLocalesTreeMissing = + "Default locale was specified, but _locales subtree is missing."; +const char* kLocalesMessagesFileMissing = + "Messages file is missing for locale."; const char* kInvalidOptionsPage = "Invalid value for 'options_page'."; } // namespace extension_manifest_errors diff --git a/chrome/common/extensions/extension_constants.h b/chrome/common/extensions/extension_constants.h index ff9836f..b0765b4 100644 --- a/chrome/common/extensions/extension_constants.h +++ b/chrome/common/extensions/extension_constants.h @@ -124,7 +124,10 @@ namespace extension_manifest_errors { extern const char* kInvalidUpdateURL; extern const char* kInvalidDefaultLocale; extern const char* kLocalesNoDefaultLocaleSpecified; + extern const char* kLocalesNoDefaultMessages; extern const char* kLocalesNoValidLocaleNamesListed; + extern const char* kLocalesTreeMissing; + extern const char* kLocalesMessagesFileMissing; extern const char* kInvalidOptionsPage; } // namespace extension_manifest_errors diff --git a/chrome/common/extensions/extension_unittest.cc b/chrome/common/extensions/extension_unittest.cc index 6e1ad5e..db54776 100644 --- a/chrome/common/extensions/extension_unittest.cc +++ b/chrome/common/extensions/extension_unittest.cc @@ -49,6 +49,7 @@ TEST(ExtensionTest, DISABLED_InitFromValueInvalid) { ASSERT_TRUE(valid_value.get()); ASSERT_TRUE(extension.InitFromValue(*valid_value, true, &error)); ASSERT_EQ("", error); + EXPECT_EQ("en_US", extension.default_locale()); scoped_ptr<DictionaryValue> input_value; @@ -238,6 +239,16 @@ TEST(ExtensionTest, DISABLED_InitFromValueInvalid) { input_value->Set(keys::kOptionsPage, Value::CreateNullValue()); EXPECT_FALSE(extension.InitFromValue(*input_value, true, &error)); EXPECT_TRUE(MatchPattern(error, errors::kInvalidOptionsPage)); + + // Test invalid/empty default locale. + input_value.reset(static_cast<DictionaryValue*>(valid_value->DeepCopy())); + input_value->Set(keys::kDefaultLocale, Value::CreateIntegerValue(5)); + EXPECT_FALSE(extension.InitFromValue(*input_value, true, &error)); + EXPECT_TRUE(MatchPattern(error, errors::kInvalidDefaultLocale)); + + input_value->Set(keys::kDefaultLocale, Value::CreateStringValue("")); + EXPECT_FALSE(extension.InitFromValue(*input_value, true, &error)); + EXPECT_TRUE(MatchPattern(error, errors::kInvalidDefaultLocale)); } TEST(ExtensionTest, InitFromValueValid) { diff --git a/chrome/common/extensions/extension_unpacker.cc b/chrome/common/extensions/extension_unpacker.cc index 95c7e0d..e5c7758 100644 --- a/chrome/common/extensions/extension_unpacker.cc +++ b/chrome/common/extensions/extension_unpacker.cc @@ -23,6 +23,9 @@ #include "third_party/skia/include/core/SkBitmap.h" #include "webkit/glue/image_decoder.h" +namespace errors = extension_manifest_errors; +namespace keys = extension_manifest_keys; + namespace { // The name of a temporary directory to install an extension into for // validation before finalizing install. @@ -87,7 +90,7 @@ DictionaryValue* ExtensionUnpacker::ReadManifest() { FilePath manifest_path = temp_install_dir_.AppendASCII(Extension::kManifestFilename); if (!file_util::PathExists(manifest_path)) { - SetError(extension_manifest_errors::kInvalidManifest); + SetError(errors::kInvalidManifest); return NULL; } @@ -100,13 +103,46 @@ DictionaryValue* ExtensionUnpacker::ReadManifest() { } if (!root->IsType(Value::TYPE_DICTIONARY)) { - SetError(extension_manifest_errors::kInvalidManifest); + SetError(errors::kInvalidManifest); return NULL; } return static_cast<DictionaryValue*>(root.release()); } +bool ExtensionUnpacker::ReadAllMessageCatalogs( + const std::string& default_locale) { + FilePath locales_path = + temp_install_dir_.AppendASCII(Extension::kLocaleFolder); + + // Treat all folders under _locales as valid locales. + file_util::FileEnumerator locales(locales_path, + false, + file_util::FileEnumerator::DIRECTORIES); + + FilePath locale_path = locales.Next(); + do { + // Since we use this string as a key in a DictionaryValue, be paranoid about + // skipping any strings with '.'. This happens sometimes, for example with + // '.svn' directories. + FilePath relative_path; + // message_path was created from temp_install_dir. This should never fail. + if (!temp_install_dir_.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.AppendASCII(Extension::kMessagesFilename); + + if (!ReadMessageCatalog(messages_path)) + return false; + } while (!(locale_path = locales.Next()).empty()); + + return true; +} + bool ExtensionUnpacker::Run() { LOG(INFO) << "Installing extension " << extension_path_.value(); @@ -154,6 +190,13 @@ bool ExtensionUnpacker::Run() { return false; // Error was already reported. } + // Parse all message catalogs (if any). + parsed_catalogs_.reset(new DictionaryValue); + if (!extension.default_locale().empty()) { + if (!ReadAllMessageCatalogs(extension.default_locale())) + return false; // Error was already reported. + } + return true; } @@ -201,6 +244,33 @@ bool ExtensionUnpacker::AddDecodedImage(const FilePath& path) { return true; } +bool ExtensionUnpacker::ReadMessageCatalog(const FilePath& message_path) { + std::string error; + JSONFileValueSerializer serializer(message_path); + DictionaryValue* root = + static_cast<DictionaryValue*>(serializer.Deserialize(&error)); + if (!root) { + std::string messages_file = WideToASCII(message_path.ToWStringHack()); + if (error.empty()) { + // If file is missing, Deserialize will fail with empty error. + SetError(StringPrintf("%s %s", errors::kLocalesMessagesFileMissing, + messages_file.c_str())); + } else { + SetError(StringPrintf("%s: %s", messages_file.c_str(), error.c_str())); + } + return false; + } + + FilePath relative_path; + // message_path was created from temp_install_dir. This should never fail. + if (!temp_install_dir_.AppendRelativePath(message_path, &relative_path)) + NOTREACHED(); + + parsed_catalogs_->Set(relative_path.DirName().ToWStringHack(), root); + + return true; +} + void ExtensionUnpacker::SetError(const std::string &error) { error_message_ = error; } diff --git a/chrome/common/extensions/extension_unpacker.h b/chrome/common/extensions/extension_unpacker.h index aeb72d9..c59262d 100644 --- a/chrome/common/extensions/extension_unpacker.h +++ b/chrome/common/extensions/extension_unpacker.h @@ -46,16 +46,24 @@ class ExtensionUnpacker { return parsed_manifest_.get(); } const DecodedImages& decoded_images() { return decoded_images_; } + DictionaryValue* parsed_catalogs() { return parsed_catalogs_.get(); } private: // Parse the manifest.json file inside the extension (not in the header). // Caller takes ownership of return value. DictionaryValue* ReadManifest(); + // Parse all _locales/*/messages.json files inside the extension. + bool ReadAllMessageCatalogs(const std::string& default_locale); + // Decodes the image at the given path and puts it in our list of decoded // images. bool AddDecodedImage(const FilePath& path); + // Parses the catalog at the given path and puts it in our list of parsed + // catalogs. + bool ReadMessageCatalog(const FilePath& message_path); + // Set the error message. void SetError(const std::string& error); @@ -72,6 +80,10 @@ class ExtensionUnpacker { // are relative to the manifest file. DecodedImages decoded_images_; + // Dictionary of relative paths and catalogs per path. Paths are in the form + // of _locales/locale, without messages.json base part. + scoped_ptr<DictionaryValue> parsed_catalogs_; + // The last error message that was set. Empty if there were no errors. std::string error_message_; diff --git a/chrome/common/extensions/extension_unpacker_unittest.cc b/chrome/common/extensions/extension_unpacker_unittest.cc new file mode 100644 index 0000000..f7679cf --- /dev/null +++ b/chrome/common/extensions/extension_unpacker_unittest.cc @@ -0,0 +1,120 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/file_util.h" +#include "base/path_service.h" +#include "base/string_util.h" +#include "base/values.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/extensions/extension_constants.h" +#include "chrome/common/extensions/extension_unpacker.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkBitmap.h" + +namespace errors = extension_manifest_errors; +namespace keys = extension_manifest_keys; + +class ExtensionUnpackerTest : public testing::Test { +public: + void SetupUnpacker(const std::string& crx_name) { + FilePath original_path; + ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &original_path)); + original_path = original_path.AppendASCII("extensions") + .AppendASCII("unpacker") + .AppendASCII(crx_name); + ASSERT_TRUE(file_util::PathExists(original_path)) << original_path.value(); + + // Try bots won't let us write into DIR_TEST_DATA, so we have to create + // a temp folder to play in. + ASSERT_TRUE(PathService::Get(base::DIR_TEMP, &install_dir_)); + install_dir_ = install_dir_.AppendASCII("extension_unpacker_test"); + ASSERT_TRUE(file_util::Delete(install_dir_, true)) << + install_dir_.value(); + ASSERT_TRUE(file_util::CreateDirectory(install_dir_)) << + install_dir_.value(); + + FilePath crx_path = install_dir_.AppendASCII(crx_name); + ASSERT_TRUE(file_util::CopyFile(original_path, crx_path)) << + "Original path " << original_path.value() << + ", Crx path " << crx_path.value(); + + unpacker_.reset(new ExtensionUnpacker(crx_path)); + } + + virtual void TearDown() { + ASSERT_TRUE(file_util::Delete(install_dir_, true)) << + install_dir_.value(); + } + + protected: + FilePath install_dir_; + scoped_ptr<ExtensionUnpacker> unpacker_; +}; + +TEST_F(ExtensionUnpackerTest, EmptyDefaultLocale) { + SetupUnpacker("empty_default_locale.crx"); + EXPECT_FALSE(unpacker_->Run()); + EXPECT_EQ(errors::kInvalidDefaultLocale, unpacker_->error_message()); +} + +TEST_F(ExtensionUnpackerTest, HasDefaultLocaleMissingLocalesFolder) { + SetupUnpacker("has_default_missing_locales.crx"); + EXPECT_FALSE(unpacker_->Run()); + EXPECT_EQ(errors::kLocalesTreeMissing, unpacker_->error_message()); +} + +TEST_F(ExtensionUnpackerTest, InvalidDefaultLocale) { + SetupUnpacker("invalid_default_locale.crx"); + EXPECT_FALSE(unpacker_->Run()); + EXPECT_EQ(errors::kInvalidDefaultLocale, unpacker_->error_message()); +} + +TEST_F(ExtensionUnpackerTest, InvalidMessagesFile) { + SetupUnpacker("invalid_messages_file.crx"); + EXPECT_FALSE(unpacker_->Run()); + EXPECT_TRUE(MatchPattern(unpacker_->error_message(), + std::string("*_locales?en_US?messages.json: Line: 2, column: 3," + " Dictionary keys must be quoted."))); +} + +TEST_F(ExtensionUnpackerTest, MissingDefaultData) { + SetupUnpacker("missing_default_data.crx"); + EXPECT_FALSE(unpacker_->Run()); + EXPECT_EQ(errors::kLocalesNoDefaultMessages, unpacker_->error_message()); +} + +TEST_F(ExtensionUnpackerTest, MissingDefaultLocaleHasLocalesFolder) { + SetupUnpacker("missing_default_has_locales.crx"); + EXPECT_FALSE(unpacker_->Run()); + EXPECT_EQ(errors::kLocalesNoDefaultLocaleSpecified, + unpacker_->error_message()); +} + +TEST_F(ExtensionUnpackerTest, MissingMessagesFile) { + SetupUnpacker("missing_messages_file.crx"); + EXPECT_FALSE(unpacker_->Run()); + EXPECT_TRUE(MatchPattern(unpacker_->error_message(), + errors::kLocalesMessagesFileMissing + + std::string("*_locales?en_US?messages.json"))); +} + +TEST_F(ExtensionUnpackerTest, NoLocaleData) { + SetupUnpacker("no_locale_data.crx"); + EXPECT_FALSE(unpacker_->Run()); + EXPECT_EQ(errors::kLocalesTreeMissing, unpacker_->error_message()); +} + +TEST_F(ExtensionUnpackerTest, GoodL10n) { + SetupUnpacker("good_l10n.crx"); + EXPECT_TRUE(unpacker_->Run()); + EXPECT_TRUE(unpacker_->error_message().empty()); + ASSERT_EQ(2U, unpacker_->parsed_catalogs()->GetSize()); +} + +TEST_F(ExtensionUnpackerTest, NoL10n) { + SetupUnpacker("no_l10n.crx"); + EXPECT_TRUE(unpacker_->Run()); + EXPECT_TRUE(unpacker_->error_message().empty()); + EXPECT_EQ(0U, unpacker_->parsed_catalogs()->GetSize()); +} |