summaryrefslogtreecommitdiffstats
path: root/chrome/common/extensions
diff options
context:
space:
mode:
authorcira@chromium.org <cira@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-11-18 18:02:47 +0000
committercira@chromium.org <cira@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-11-18 18:02:47 +0000
commit9428edc493bf5097cc74fca5fcaf4506c4068668 (patch)
treea5db8e196a4e71c71d54ded4c552b2a4d8c50361 /chrome/common/extensions
parentecc73b2f562a20611804cc07dcff59922dbd09b4 (diff)
downloadchromium_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.cc8
-rw-r--r--chrome/common/extensions/extension.h5
-rw-r--r--chrome/common/extensions/extension_constants.cc6
-rw-r--r--chrome/common/extensions/extension_constants.h3
-rw-r--r--chrome/common/extensions/extension_unittest.cc11
-rw-r--r--chrome/common/extensions/extension_unpacker.cc74
-rw-r--r--chrome/common/extensions/extension_unpacker.h12
-rw-r--r--chrome/common/extensions/extension_unpacker_unittest.cc120
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());
+}