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 | |
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
41 files changed, 719 insertions, 97 deletions
diff --git a/chrome/browser/extensions/extension_file_util.cc b/chrome/browser/extensions/extension_file_util.cc index 52c2c78..e7ecd08 100644 --- a/chrome/browser/extensions/extension_file_util.cc +++ b/chrome/browser/extensions/extension_file_util.cc @@ -16,8 +16,13 @@ #include "chrome/common/json_value_serializer.h" #include "net/base/file_stream.h" +namespace errors = extension_manifest_errors; + 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 @@ -270,6 +275,10 @@ bool ValidateExtension(Extension* extension, std::string* error) { } } + // 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)) { @@ -382,6 +391,69 @@ ExtensionMessageBundle* LoadLocaleInfo(const FilePath& extension_path, 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().AppendASCII(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.AppendASCII(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. diff --git a/chrome/browser/extensions/sandboxed_extension_unpacker.cc b/chrome/browser/extensions/sandboxed_extension_unpacker.cc index 160a0de..65ccded 100644 --- a/chrome/browser/extensions/sandboxed_extension_unpacker.cc +++ b/chrome/browser/extensions/sandboxed_extension_unpacker.cc @@ -21,7 +21,6 @@ #include "chrome/common/extensions/extension_unpacker.h" #include "chrome/common/json_value_serializer.h" #include "net/base/base64.h" - #include "third_party/skia/include/core/SkBitmap.h" const char SandboxedExtensionUnpacker::kExtensionHeaderMagic[] = "Cr24"; @@ -83,7 +82,8 @@ void SandboxedExtensionUnpacker::Start() { // Otherwise, unpack the extension in this process. ExtensionUnpacker unpacker(temp_crx_path); if (unpacker.Run() && unpacker.DumpImagesToFile()) - OnUnpackExtensionSucceeded(*unpacker.parsed_manifest()); + OnUnpackExtensionSucceeded(*unpacker.parsed_manifest(), + *unpacker.parsed_catalogs()); else OnUnpackExtensionFailed(unpacker.error_message()); } @@ -97,38 +97,16 @@ void SandboxedExtensionUnpacker::StartProcessOnIOThread( } void SandboxedExtensionUnpacker::OnUnpackExtensionSucceeded( - const DictionaryValue& manifest) { - DCHECK(ChromeThread::CurrentlyOn(thread_identifier_)); + const DictionaryValue& manifest, + const DictionaryValue& catalogs) { + // Skip check for unittests. + if (thread_identifier_ != ChromeThread::ID_COUNT) + DCHECK(ChromeThread::CurrentlyOn(thread_identifier_)); got_response_ = true; - ExtensionUnpacker::DecodedImages images; - if (!ExtensionUnpacker::ReadImagesFromFile(temp_dir_.path(), &images)) { - ReportFailure("Couldn't read image data from disk."); + scoped_ptr<DictionaryValue> final_manifest(RewriteManifestFile(manifest)); + if (!final_manifest.get()) return; - } - - // Add the public key extracted earlier to the parsed manifest and overwrite - // the original manifest. We do this to ensure the manifest doesn't contain an - // exploitable bug that could be used to compromise the browser. - scoped_ptr<DictionaryValue> final_manifest( - static_cast<DictionaryValue*>(manifest.DeepCopy())); - final_manifest->SetString(extension_manifest_keys::kPublicKey, public_key_); - - std::string manifest_json; - JSONStringValueSerializer serializer(&manifest_json); - serializer.set_pretty_print(true); - if (!serializer.Serialize(*final_manifest)) { - ReportFailure("Error serializing manifest.json."); - return; - } - - FilePath manifest_path = - extension_root_.AppendASCII(Extension::kManifestFilename); - if (!file_util::WriteFile(manifest_path, - manifest_json.data(), manifest_json.size())) { - ReportFailure("Error saving manifest.json."); - return; - } // Create an extension object that refers to the temporary location the // extension was unpacked to. We use this until the extension is finally @@ -144,55 +122,11 @@ void SandboxedExtensionUnpacker::OnUnpackExtensionSucceeded( return; } - // Delete any images that may be used by the browser. We're going to write - // out our own versions of the parsed images, and we want to make sure the - // originals are gone for good. - std::set<FilePath> image_paths = extension_->GetBrowserImages(); - if (image_paths.size() != images.size()) { - ReportFailure("Decoded images don't match what's in the manifest."); + if (!RewriteImageFiles()) return; - } - - for (std::set<FilePath>::iterator it = image_paths.begin(); - it != image_paths.end(); ++it) { - FilePath path = *it; - if (path.IsAbsolute() || path.ReferencesParent()) { - ReportFailure("Invalid path for browser image."); - return; - } - if (!file_util::Delete(extension_root_.Append(path), false)) { - ReportFailure("Error removing old image file."); - return; - } - } - // Write our parsed images back to disk as well. - for (size_t i = 0; i < images.size(); ++i) { - const SkBitmap& image = images[i].a; - FilePath path_suffix = images[i].b; - if (path_suffix.IsAbsolute() || path_suffix.ReferencesParent()) { - ReportFailure("Invalid path for bitmap image."); - return; - } - FilePath path = extension_root_.Append(path_suffix); - - std::vector<unsigned char> image_data; - // TODO(mpcomplete): It's lame that we're encoding all images as PNG, even - // though they may originally be .jpg, etc. Figure something out. - // http://code.google.com/p/chromium/issues/detail?id=12459 - if (!gfx::PNGCodec::EncodeBGRASkBitmap(image, false, &image_data)) { - ReportFailure("Error re-encoding theme image."); - return; - } - - // Note: we're overwriting existing files that the utility process wrote, - // so we can be sure the directory exists. - const char* image_data_ptr = reinterpret_cast<const char*>(&image_data[0]); - if (!file_util::WriteFile(path, image_data_ptr, image_data.size())) { - ReportFailure("Error saving theme image."); - return; - } - } + if (!RewriteCatalogFiles(catalogs)) + return; ReportSuccess(); } @@ -310,3 +244,131 @@ void SandboxedExtensionUnpacker::ReportSuccess() { client_->OnUnpackSuccess(temp_dir_.Take(), extension_root_, extension_.release()); } + +DictionaryValue* SandboxedExtensionUnpacker::RewriteManifestFile( + const DictionaryValue& manifest) { + // Add the public key extracted earlier to the parsed manifest and overwrite + // the original manifest. We do this to ensure the manifest doesn't contain an + // exploitable bug that could be used to compromise the browser. + scoped_ptr<DictionaryValue> final_manifest( + static_cast<DictionaryValue*>(manifest.DeepCopy())); + final_manifest->SetString(extension_manifest_keys::kPublicKey, public_key_); + + std::string manifest_json; + JSONStringValueSerializer serializer(&manifest_json); + serializer.set_pretty_print(true); + if (!serializer.Serialize(*final_manifest)) { + ReportFailure("Error serializing manifest.json."); + return NULL; + } + + FilePath manifest_path = + extension_root_.AppendASCII(Extension::kManifestFilename); + if (!file_util::WriteFile(manifest_path, + manifest_json.data(), manifest_json.size())) { + ReportFailure("Error saving manifest.json."); + return NULL; + } + + return final_manifest.release(); +} + +bool SandboxedExtensionUnpacker::RewriteImageFiles() { + ExtensionUnpacker::DecodedImages images; + if (!ExtensionUnpacker::ReadImagesFromFile(temp_dir_.path(), &images)) { + ReportFailure("Couldn't read image data from disk."); + return false; + } + + // Delete any images that may be used by the browser. We're going to write + // out our own versions of the parsed images, and we want to make sure the + // originals are gone for good. + std::set<FilePath> image_paths = extension_->GetBrowserImages(); + if (image_paths.size() != images.size()) { + ReportFailure("Decoded images don't match what's in the manifest."); + return false; + } + + for (std::set<FilePath>::iterator it = image_paths.begin(); + it != image_paths.end(); ++it) { + FilePath path = *it; + if (path.IsAbsolute() || path.ReferencesParent()) { + ReportFailure("Invalid path for browser image."); + return false; + } + if (!file_util::Delete(extension_root_.Append(path), false)) { + ReportFailure("Error removing old image file."); + return false; + } + } + + // Write our parsed images back to disk as well. + for (size_t i = 0; i < images.size(); ++i) { + const SkBitmap& image = images[i].a; + FilePath path_suffix = images[i].b; + if (path_suffix.IsAbsolute() || path_suffix.ReferencesParent()) { + ReportFailure("Invalid path for bitmap image."); + return false; + } + FilePath path = extension_root_.Append(path_suffix); + + std::vector<unsigned char> image_data; + // TODO(mpcomplete): It's lame that we're encoding all images as PNG, even + // though they may originally be .jpg, etc. Figure something out. + // http://code.google.com/p/chromium/issues/detail?id=12459 + if (!gfx::PNGCodec::EncodeBGRASkBitmap(image, false, &image_data)) { + ReportFailure("Error re-encoding theme image."); + return false; + } + + // Note: we're overwriting existing files that the utility process wrote, + // so we can be sure the directory exists. + const char* image_data_ptr = reinterpret_cast<const char*>(&image_data[0]); + if (!file_util::WriteFile(path, image_data_ptr, image_data.size())) { + ReportFailure("Error saving theme image."); + return false; + } + } + + return true; +} + +bool SandboxedExtensionUnpacker::RewriteCatalogFiles( + const DictionaryValue& catalogs) { + // Write our parsed catalogs back to disk. + DictionaryValue::key_iterator key_it = catalogs.begin_keys(); + for (; key_it != catalogs.end_keys(); ++key_it) { + DictionaryValue* catalog; + if (!catalogs.GetDictionary(*key_it, &catalog)) { + ReportFailure("Invalid catalog data."); + return false; + } + + FilePath relative_path = FilePath::FromWStringHack(*key_it); + relative_path = relative_path.AppendASCII(Extension::kMessagesFilename); + if (relative_path.IsAbsolute() || relative_path.ReferencesParent()) { + ReportFailure("Invalid path for catalog."); + return false; + } + FilePath path = extension_root_.Append(relative_path); + + std::string catalog_json; + JSONStringValueSerializer serializer(&catalog_json); + serializer.set_pretty_print(true); + if (!serializer.Serialize(*catalog)) { + ReportFailure("Error serializing catalog."); + return false; + } + + // Note: we're overwriting existing files that the utility process read, + // so we can be sure the directory exists. + if (!file_util::WriteFile(path, + catalog_json.c_str(), + catalog_json.size())) { + ReportFailure("Error saving catalog."); + return false; + } + } + + return true; +} diff --git a/chrome/browser/extensions/sandboxed_extension_unpacker.h b/chrome/browser/extensions/sandboxed_extension_unpacker.h index 06edcbe..9c04c9f 100644 --- a/chrome/browser/extensions/sandboxed_extension_unpacker.h +++ b/chrome/browser/extensions/sandboxed_extension_unpacker.h @@ -20,7 +20,7 @@ class ResourceDispatcherHost; class SandboxedExtensionUnpackerClient : public base::RefCountedThreadSafe<SandboxedExtensionUnpackerClient> { public: - // temp_dir - A temporary directoy containing the results of the extension + // temp_dir - A temporary directory containing the results of the extension // unpacking. The client is responsible for deleting this directory. // // extension_root - The path to the extension root inside of temp_dir. @@ -44,9 +44,9 @@ class SandboxedExtensionUnpackerClient // sources. // // Unpacking an extension using this class makes minor changes to its source, -// such as transcoding all images to PNG and rewriting the manifest JSON. As -// such, it should not be used when the output is not intended to be given back -// to the author. +// such as transcoding all images to PNG, parsing all message catalogs +// and rewriting the manifest JSON. As such, it should not be used when the +// output is not intended to be given back to the author. // // // Lifetime management: @@ -102,6 +102,7 @@ class SandboxedExtensionUnpacker : public UtilityProcessHost::Client { private: class ProcessHostClient; friend class ProcessHostClient; + friend class SandboxedExtensionUnpackerTest; ~SandboxedExtensionUnpacker() {} @@ -120,13 +121,23 @@ class SandboxedExtensionUnpacker : public UtilityProcessHost::Client { void StartProcessOnIOThread(const FilePath& temp_crx_path); // SandboxedExtensionUnpacker - void OnUnpackExtensionSucceeded(const DictionaryValue& manifest); + void OnUnpackExtensionSucceeded(const DictionaryValue& manifest, + const DictionaryValue& catalogs); void OnUnpackExtensionFailed(const std::string& error_message); void OnProcessCrashed(); void ReportFailure(const std::string& message); void ReportSuccess(); + // Overwrites original manifest with safe result from utility process. + // Returns NULL on error. Caller owns the returned object. + DictionaryValue* RewriteManifestFile(const DictionaryValue& manifest); + + // Overwrites original files with safe results from utility process. + // Reports error and returns false if it fails. + bool RewriteImageFiles(); + bool RewriteCatalogFiles(const DictionaryValue& parsed_catalogs); + FilePath crx_path_; ChromeThread::ID thread_identifier_; ResourceDispatcherHost* rdh_; diff --git a/chrome/browser/extensions/sandboxed_extension_unpacker_unittest.cc b/chrome/browser/extensions/sandboxed_extension_unpacker_unittest.cc new file mode 100644 index 0000000..93f550d --- /dev/null +++ b/chrome/browser/extensions/sandboxed_extension_unpacker_unittest.cc @@ -0,0 +1,160 @@ +// 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/ref_counted.h" +#include "base/scoped_temp_dir.h" +#include "base/string_util.h" +#include "base/values.h" +#include "chrome/browser/extensions/sandboxed_extension_unpacker.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/extensions/extension.h" +#include "chrome/common/extensions/extension_constants.h" +#include "chrome/common/extensions/extension_unpacker.h" +#include "testing/gmock/include/gmock/gmock.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; + +using testing::_; +using testing::Invoke; + +void OnUnpackSuccess(const FilePath& temp_dir, + const FilePath& extension_root, + Extension* extension) { + delete extension; + // Don't delete temp_dir here, we need to do some post op checking. +} + +class MockSandboxedExtensionUnpackerClient + : public SandboxedExtensionUnpackerClient { + public: + virtual ~MockSandboxedExtensionUnpackerClient() {} + + MOCK_METHOD3(OnUnpackSuccess, + void(const FilePath& temp_dir, + const FilePath& extension_root, + Extension* extension)); + + MOCK_METHOD1(OnUnpackFailure, + void(const std::string& error)); + + void DelegateToFake() { + ON_CALL(*this, OnUnpackSuccess(_, _, _)) + .WillByDefault(Invoke(::OnUnpackSuccess)); + } +}; + +class SandboxedExtensionUnpackerTest : public testing::Test { + public: + virtual void SetUp () { + // It will delete itself. + client_ = new MockSandboxedExtensionUnpackerClient; + client_->DelegateToFake(); + } + + virtual void TearDown() { + // Clean up finally. + ASSERT_TRUE(file_util::Delete(install_dir_, true)) << + install_dir_.value(); + } + + 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("sandboxed_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)); + + // It will delete itself. + sandboxed_unpacker_ = + new SandboxedExtensionUnpacker(crx_path, NULL, client_); + PrepareUnpackerEnv(); + } + + void PrepareUnpackerEnv() { + sandboxed_unpacker_->extension_root_ = + install_dir_.AppendASCII("TEMP_INSTALL"); + + sandboxed_unpacker_->temp_dir_.Set(install_dir_); + sandboxed_unpacker_->public_key_ = + "ocnapchkplbmjmpfehjocmjnipfmogkh"; + } + + void OnUnpackSucceeded() { + sandboxed_unpacker_->OnUnpackExtensionSucceeded( + *unpacker_->parsed_manifest(), + *unpacker_->parsed_catalogs()); + } + + FilePath GetInstallPath() { + return install_dir_.AppendASCII("TEMP_INSTALL"); + } + + protected: + FilePath install_dir_; + MockSandboxedExtensionUnpackerClient* client_; + scoped_ptr<ExtensionUnpacker> unpacker_; + scoped_refptr<SandboxedExtensionUnpacker> sandboxed_unpacker_; +}; + +TEST_F(SandboxedExtensionUnpackerTest, NoCatalogsSuccess) { + EXPECT_CALL(*client_, OnUnpackSuccess(_, _, _)); + + SetupUnpacker("no_l10n.crx"); + ASSERT_TRUE(unpacker_->Run()); + ASSERT_TRUE(unpacker_->DumpImagesToFile()); + + // Check that there is no _locales folder. + FilePath install_path = + GetInstallPath().AppendASCII(Extension::kLocaleFolder); + EXPECT_FALSE(file_util::PathExists(install_path)); + + OnUnpackSucceeded(); + + // Check that there still is no _locales folder. + EXPECT_FALSE(file_util::PathExists(install_path)); +} + +TEST_F(SandboxedExtensionUnpackerTest, WithCatalogsSuccess) { + EXPECT_CALL(*client_, OnUnpackSuccess(_, _, _)); + + SetupUnpacker("good_l10n.crx"); + ASSERT_TRUE(unpacker_->Run()); + ASSERT_TRUE(unpacker_->DumpImagesToFile()); + + // Delete _locales/en_US/messages.json. + FilePath messages_file; + messages_file = GetInstallPath() + .AppendASCII(Extension::kLocaleFolder) + .AppendASCII("en_US") + .AppendASCII(Extension::kMessagesFilename); + EXPECT_TRUE(file_util::Delete(messages_file, false)); + + OnUnpackSucceeded(); + + // Check that there is _locales/en_US/messages.json file. + EXPECT_TRUE(file_util::PathExists(messages_file)); +} diff --git a/chrome/browser/utility_process_host.h b/chrome/browser/utility_process_host.h index a37a968..773b515 100644 --- a/chrome/browser/utility_process_host.h +++ b/chrome/browser/utility_process_host.h @@ -35,9 +35,11 @@ class UtilityProcessHost : public ChildProcessHost { virtual void OnProcessCrashed() {} // Called when the extension has unpacked successfully. |manifest| is the - // parsed manifest.json file. |images| contains a list of decoded images - // and the associated paths where those images live on disk. - virtual void OnUnpackExtensionSucceeded(const DictionaryValue& manifest) {} + // parsed manifest.json file. |catalogs| contains list of all parsed + // message catalogs. |images| contains a list of decoded images and the + // associated paths where those images live on disk. + virtual void OnUnpackExtensionSucceeded(const DictionaryValue& manifest, + const DictionaryValue& catalogs) {} // Called when an error occurred while unpacking the extension. // |error_message| contains a description of the problem. @@ -51,8 +53,7 @@ class UtilityProcessHost : public ChildProcessHost { // Called when an error occurred while parsing the resource data. // |error_message| contains a description of the problem. - virtual void OnUnpackWebResourceFailed( - const std::string& error_message) {} + virtual void OnUnpackWebResourceFailed(const std::string& error_message) {} // Called when an update manifest xml file was successfully parsed. virtual void OnParseUpdateManifestSucceeded( diff --git a/chrome/browser/utility_process_host_unittest.cc b/chrome/browser/utility_process_host_unittest.cc index 950c596..44fb0f8 100644 --- a/chrome/browser/utility_process_host_unittest.cc +++ b/chrome/browser/utility_process_host_unittest.cc @@ -43,7 +43,8 @@ class TestUtilityProcessHostClient : public UtilityProcessHost::Client { NOTREACHED(); } - virtual void OnUnpackExtensionSucceeded(const DictionaryValue& manifest) { + virtual void OnUnpackExtensionSucceeded(const DictionaryValue& manifest, + const DictionaryValue& catalogs) { success_ = true; } diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp index 969c303..98c4a90 100755 --- a/chrome/chrome.gyp +++ b/chrome/chrome.gyp @@ -4665,6 +4665,7 @@ 'browser/extensions/extension_updater_unittest.cc', 'browser/extensions/extensions_service_unittest.cc', 'browser/extensions/file_reader_unittest.cc', + 'browser/extensions/sandboxed_extension_unpacker_unittest.cc', 'browser/extensions/user_script_listener_unittest.cc', 'browser/extensions/user_script_master_unittest.cc', 'browser/find_backend_unittest.cc', @@ -4788,6 +4789,7 @@ 'common/extensions/extension_action_unittest.cc', 'common/extensions/extension_l10n_util_unittest.cc', 'common/extensions/extension_message_bundle_unittest.cc', + 'common/extensions/extension_unpacker_unittest.cc', 'common/extensions/update_manifest_unittest.cc', 'common/extensions/url_pattern_unittest.cc', 'common/extensions/user_script_unittest.cc', 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()); +} diff --git a/chrome/common/render_messages_internal.h b/chrome/common/render_messages_internal.h index 6da2ec7..b883e87 100644 --- a/chrome/common/render_messages_internal.h +++ b/chrome/common/render_messages_internal.h @@ -1913,11 +1913,13 @@ IPC_BEGIN_MESSAGES(ViewHost) // because we ran out of spare message types. // Reply when the utility process is done unpacking an extension. |manifest| - // is the parsed manifest.json file. The unpacker should also have written - // out a file containing decoded images from the extension. See - // ExtensionUnpacker for details. - IPC_MESSAGE_CONTROL1(UtilityHostMsg_UnpackExtension_Succeeded, - DictionaryValue /* manifest */) + // is the parsed manifest.json file. |catalogs| is the list of all parsed + // message catalogs and relative paths to them. + // The unpacker should also have written out a file containing decoded images + // from the extension. See ExtensionUnpacker for details. + IPC_MESSAGE_CONTROL2(UtilityHostMsg_UnpackExtension_Succeeded, + DictionaryValue /* manifest */, + DictionaryValue /* catalogs */) // Reply when the utility process has failed while unpacking an extension. // |error_message| is a user-displayable explanation of what went wrong. diff --git a/chrome/test/data/extensions/unpacker/empty_default_locale.crx b/chrome/test/data/extensions/unpacker/empty_default_locale.crx Binary files differnew file mode 100644 index 0000000..d8f1e0e --- /dev/null +++ b/chrome/test/data/extensions/unpacker/empty_default_locale.crx diff --git a/chrome/test/data/extensions/unpacker/empty_default_locale/manifest.json b/chrome/test/data/extensions/unpacker/empty_default_locale/manifest.json new file mode 100644 index 0000000..6f43cf2 --- /dev/null +++ b/chrome/test/data/extensions/unpacker/empty_default_locale/manifest.json @@ -0,0 +1,6 @@ +{ + "description": "Default locale can't be empty", + "name": "empty_default_locale", + "default_locale": "", + "version": "1.0" +} diff --git a/chrome/test/data/extensions/unpacker/good_l10n.crx b/chrome/test/data/extensions/unpacker/good_l10n.crx Binary files differnew file mode 100644 index 0000000..ee56569 --- /dev/null +++ b/chrome/test/data/extensions/unpacker/good_l10n.crx diff --git a/chrome/test/data/extensions/unpacker/good_l10n/_locales/en_US/messages.json b/chrome/test/data/extensions/unpacker/good_l10n/_locales/en_US/messages.json new file mode 100644 index 0000000..b5b53c6 --- /dev/null +++ b/chrome/test/data/extensions/unpacker/good_l10n/_locales/en_US/messages.json @@ -0,0 +1,5 @@ +{ + "eh": { + "message": "this is valid catalog" + } +} diff --git a/chrome/test/data/extensions/unpacker/good_l10n/_locales/sr/messages.json b/chrome/test/data/extensions/unpacker/good_l10n/_locales/sr/messages.json new file mode 100644 index 0000000..b5b53c6 --- /dev/null +++ b/chrome/test/data/extensions/unpacker/good_l10n/_locales/sr/messages.json @@ -0,0 +1,5 @@ +{ + "eh": { + "message": "this is valid catalog" + } +} diff --git a/chrome/test/data/extensions/unpacker/good_l10n/manifest.json b/chrome/test/data/extensions/unpacker/good_l10n/manifest.json new file mode 100644 index 0000000..cf2e2a9 --- /dev/null +++ b/chrome/test/data/extensions/unpacker/good_l10n/manifest.json @@ -0,0 +1,6 @@ +{ + "description": "All is well", + "name": "good_l10n", + "default_locale": "sr", + "version": "1.0" +} diff --git a/chrome/test/data/extensions/unpacker/has_default_missing_locales.crx b/chrome/test/data/extensions/unpacker/has_default_missing_locales.crx Binary files differnew file mode 100644 index 0000000..8d2808e --- /dev/null +++ b/chrome/test/data/extensions/unpacker/has_default_missing_locales.crx diff --git a/chrome/test/data/extensions/unpacker/has_default_missing_locales/manifest.json b/chrome/test/data/extensions/unpacker/has_default_missing_locales/manifest.json new file mode 100644 index 0000000..e572fc2 --- /dev/null +++ b/chrome/test/data/extensions/unpacker/has_default_missing_locales/manifest.json @@ -0,0 +1,6 @@ +{ + "description": "Cannot have default_locale but skip _locales", + "name": "has_default_missing_locales", + "default_locale": "en_US", + "version": "1.0" +} diff --git a/chrome/test/data/extensions/unpacker/invalid_default_locale.crx b/chrome/test/data/extensions/unpacker/invalid_default_locale.crx Binary files differnew file mode 100644 index 0000000..8a426c9 --- /dev/null +++ b/chrome/test/data/extensions/unpacker/invalid_default_locale.crx diff --git a/chrome/test/data/extensions/unpacker/invalid_default_locale/manifest.json b/chrome/test/data/extensions/unpacker/invalid_default_locale/manifest.json new file mode 100644 index 0000000..ecf7d82 --- /dev/null +++ b/chrome/test/data/extensions/unpacker/invalid_default_locale/manifest.json @@ -0,0 +1,6 @@ +{ + "description": "Default locale has to be string and can't be empty", + "name": "invalid_default_locale", + "default_locale": 5, + "version": "1.0" +} diff --git a/chrome/test/data/extensions/unpacker/invalid_messages_file.crx b/chrome/test/data/extensions/unpacker/invalid_messages_file.crx Binary files differnew file mode 100644 index 0000000..15691d4 --- /dev/null +++ b/chrome/test/data/extensions/unpacker/invalid_messages_file.crx diff --git a/chrome/test/data/extensions/unpacker/invalid_messages_file/_locales/en_US/messages.json b/chrome/test/data/extensions/unpacker/invalid_messages_file/_locales/en_US/messages.json new file mode 100644 index 0000000..a992f8f --- /dev/null +++ b/chrome/test/data/extensions/unpacker/invalid_messages_file/_locales/en_US/messages.json @@ -0,0 +1,3 @@ +{ + "eh: +} diff --git a/chrome/test/data/extensions/unpacker/invalid_messages_file/manifest.json b/chrome/test/data/extensions/unpacker/invalid_messages_file/manifest.json new file mode 100644 index 0000000..7a4b9ef --- /dev/null +++ b/chrome/test/data/extensions/unpacker/invalid_messages_file/manifest.json @@ -0,0 +1,6 @@ +{ + "description": "Fail if messages.json is not valid (check JSON syntax only)", + "name": "invalid_messages_file", + "default_locale": "en_US", + "version": "1.0" +} diff --git a/chrome/test/data/extensions/unpacker/missing_default_data.crx b/chrome/test/data/extensions/unpacker/missing_default_data.crx Binary files differnew file mode 100644 index 0000000..eeb76fe --- /dev/null +++ b/chrome/test/data/extensions/unpacker/missing_default_data.crx diff --git a/chrome/test/data/extensions/unpacker/missing_default_data/_locales/en_US/messages.json b/chrome/test/data/extensions/unpacker/missing_default_data/_locales/en_US/messages.json new file mode 100644 index 0000000..b5b53c6 --- /dev/null +++ b/chrome/test/data/extensions/unpacker/missing_default_data/_locales/en_US/messages.json @@ -0,0 +1,5 @@ +{ + "eh": { + "message": "this is valid catalog" + } +} diff --git a/chrome/test/data/extensions/unpacker/missing_default_data/manifest.json b/chrome/test/data/extensions/unpacker/missing_default_data/manifest.json new file mode 100644 index 0000000..03ecc4a --- /dev/null +++ b/chrome/test/data/extensions/unpacker/missing_default_data/manifest.json @@ -0,0 +1,6 @@ +{ + "description": "Data for default_locale has to be present", + "name": "missing_default_data", + "default_locale": "sr", + "version": "1.0" +} diff --git a/chrome/test/data/extensions/unpacker/missing_default_has_locales.crx b/chrome/test/data/extensions/unpacker/missing_default_has_locales.crx Binary files differnew file mode 100644 index 0000000..0bf345d --- /dev/null +++ b/chrome/test/data/extensions/unpacker/missing_default_has_locales.crx diff --git a/chrome/test/data/extensions/unpacker/missing_default_has_locales/manifest.json b/chrome/test/data/extensions/unpacker/missing_default_has_locales/manifest.json new file mode 100644 index 0000000..13a5d4b --- /dev/null +++ b/chrome/test/data/extensions/unpacker/missing_default_has_locales/manifest.json @@ -0,0 +1,5 @@ +{ + "description": "If _locales is present, default_locale has to be defined too.", + "name": "missing_default_has_locales", + "version": "1.0" +} diff --git a/chrome/test/data/extensions/unpacker/missing_messages_file.crx b/chrome/test/data/extensions/unpacker/missing_messages_file.crx Binary files differnew file mode 100644 index 0000000..4ca3634 --- /dev/null +++ b/chrome/test/data/extensions/unpacker/missing_messages_file.crx diff --git a/chrome/test/data/extensions/unpacker/missing_messages_file/manifest.json b/chrome/test/data/extensions/unpacker/missing_messages_file/manifest.json new file mode 100644 index 0000000..a3ccf4b --- /dev/null +++ b/chrome/test/data/extensions/unpacker/missing_messages_file/manifest.json @@ -0,0 +1,6 @@ +{ + "description": "Each locale has to have messages.json file", + "name": "missing_messages_file", + "default_locale": "en_US", + "version": "1.0" +} diff --git a/chrome/test/data/extensions/unpacker/no_l10n.crx b/chrome/test/data/extensions/unpacker/no_l10n.crx Binary files differnew file mode 100644 index 0000000..d9edd9e --- /dev/null +++ b/chrome/test/data/extensions/unpacker/no_l10n.crx diff --git a/chrome/test/data/extensions/unpacker/no_l10n/manifest.json b/chrome/test/data/extensions/unpacker/no_l10n/manifest.json new file mode 100644 index 0000000..8ba809f --- /dev/null +++ b/chrome/test/data/extensions/unpacker/no_l10n/manifest.json @@ -0,0 +1,5 @@ +{ + "description": "This extension is not localized.", + "name": "no_l10n", + "version": "1.0" +} diff --git a/chrome/test/data/extensions/unpacker/no_locale_data.crx b/chrome/test/data/extensions/unpacker/no_locale_data.crx Binary files differnew file mode 100644 index 0000000..30a8f40 --- /dev/null +++ b/chrome/test/data/extensions/unpacker/no_locale_data.crx diff --git a/chrome/test/data/extensions/unpacker/no_locale_data/manifest.json b/chrome/test/data/extensions/unpacker/no_locale_data/manifest.json new file mode 100644 index 0000000..c4d68b4 --- /dev/null +++ b/chrome/test/data/extensions/unpacker/no_locale_data/manifest.json @@ -0,0 +1,6 @@ +{ + "description": "_locales can't be empty", + "name": "no_locale_data", + "default_locale": "en_US", + "version": "1.0" +} diff --git a/chrome/utility/utility_thread.cc b/chrome/utility/utility_thread.cc index 9c6912c..2c59887 100644 --- a/chrome/utility/utility_thread.cc +++ b/chrome/utility/utility_thread.cc @@ -31,7 +31,7 @@ void UtilityThread::OnUnpackExtension(const FilePath& extension_path) { ExtensionUnpacker unpacker(extension_path); if (unpacker.Run() && unpacker.DumpImagesToFile()) { Send(new UtilityHostMsg_UnpackExtension_Succeeded( - *unpacker.parsed_manifest())); + *unpacker.parsed_manifest(), *unpacker.parsed_catalogs())); } else { Send(new UtilityHostMsg_UnpackExtension_Failed(unpacker.error_message())); } |