diff options
author | jstritar@chromium.org <jstritar@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-12-04 00:55:32 +0000 |
---|---|---|
committer | jstritar@chromium.org <jstritar@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-12-04 00:55:32 +0000 |
commit | 953620b6b138131552b29163a6a3fa4b3593f083 (patch) | |
tree | 6837b6816b4401a665266a96786180c9a038e8a8 | |
parent | 107248107d7b38e9614e99972b3a99385005956e (diff) | |
download | chromium_src-953620b6b138131552b29163a6a3fa4b3593f083.zip chromium_src-953620b6b138131552b29163a6a3fa4b3593f083.tar.gz chromium_src-953620b6b138131552b29163a6a3fa4b3593f083.tar.bz2 |
Reland restrict extension features based on the extension type.
The "chrome_url_overrides" manifest key is now accessible by packaged apps.
BUG=101992, 104103
TEST=existing, ManifestTest
Review URL: http://codereview.chromium.org/8654001
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@112914 0039d316-1c4b-4281-b951-d872f2087c98
20 files changed, 868 insertions, 275 deletions
diff --git a/chrome/browser/extensions/chrome_app_api_browsertest.cc b/chrome/browser/extensions/chrome_app_api_browsertest.cc index 0f5219e..738afe1 100644 --- a/chrome/browser/extensions/chrome_app_api_browsertest.cc +++ b/chrome/browser/extensions/chrome_app_api_browsertest.cc @@ -13,6 +13,7 @@ #include "chrome/browser/ui/browser.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/extensions/extension.h" +#include "chrome/common/extensions/manifest.h" #include "chrome/test/base/ui_test_utils.h" #include "content/browser/tab_contents/tab_contents.h" #include "googleurl/src/gurl.h" @@ -98,10 +99,10 @@ IN_PROC_BROWSER_TEST_F(ChromeAppAPITest, IsInstalled) { scoped_ptr<DictionaryValue> app_details( static_cast<DictionaryValue*>( base::JSONReader::Read(result, false /* allow trailing comma */))); - // extension->manifest_value() does not contain the id. + // extension->manifest() does not contain the id. app_details->Remove("id", NULL); EXPECT_TRUE(app_details.get()); - EXPECT_TRUE(app_details->Equals(extension->manifest_value())); + EXPECT_TRUE(app_details->Equals(extension->manifest()->value())); // Try to change app.isInstalled. Should silently fail, so // that isInstalled should have the initial value. @@ -177,8 +178,8 @@ IN_PROC_BROWSER_TEST_F(ChromeAppAPITest, GetDetailsForFrame) { scoped_ptr<DictionaryValue> app_details( static_cast<DictionaryValue*>( base::JSONReader::Read(json, false /* allow trailing comma */))); - // extension->manifest_value() does not contain the id. + // extension->manifest() does not contain the id. app_details->Remove("id", NULL); EXPECT_TRUE(app_details.get()); - EXPECT_TRUE(app_details->Equals(extension->manifest_value())); + EXPECT_TRUE(app_details->Equals(extension->manifest()->value())); } diff --git a/chrome/browser/extensions/extension_prefs.cc b/chrome/browser/extensions/extension_prefs.cc index 6e7a907..875b842 100644 --- a/chrome/browser/extensions/extension_prefs.cc +++ b/chrome/browser/extensions/extension_prefs.cc @@ -12,6 +12,7 @@ #include "chrome/browser/prefs/scoped_user_pref_update.h" #include "chrome/common/chrome_notification_types.h" #include "chrome/common/extensions/extension.h" +#include "chrome/common/extensions/manifest.h" #include "chrome/common/extensions/url_pattern.h" #include "chrome/common/pref_names.h" #include "chrome/common/url_constants.h" @@ -1106,7 +1107,7 @@ void ExtensionPrefs::OnExtensionInstalled( // since it may change on disk. if (extension->location() != Extension::LOAD) { extension_dict->Set(kPrefManifest, - extension->manifest_value()->DeepCopy()); + extension->manifest()->value()->DeepCopy()); } if (extension->is_app()) { @@ -1196,10 +1197,10 @@ void ExtensionPrefs::UpdateManifest(const Extension* extension) { DictionaryValue* old_manifest = NULL; bool update_required = !extension_dict->GetDictionary(kPrefManifest, &old_manifest) || - !extension->manifest_value()->Equals(old_manifest); + !extension->manifest()->value()->Equals(old_manifest); if (update_required) { UpdateExtensionPref(extension->id(), kPrefManifest, - extension->manifest_value()->DeepCopy()); + extension->manifest()->value()->DeepCopy()); } } } diff --git a/chrome/browser/extensions/installed_loader.cc b/chrome/browser/extensions/installed_loader.cc index 43a1304..2fb5e2d 100644 --- a/chrome/browser/extensions/installed_loader.cc +++ b/chrome/browser/extensions/installed_loader.cc @@ -15,6 +15,7 @@ #include "chrome/common/extensions/extension.h" #include "chrome/common/extensions/extension_file_util.h" #include "chrome/common/extensions/extension_l10n_util.h" +#include "chrome/common/extensions/manifest.h" #include "chrome/common/pref_names.h" #include "content/public/browser/notification_service.h" #include "content/browser/user_metrics.h" @@ -141,7 +142,7 @@ void InstalledLoader::LoadAllExtensions() { if (extension.get()) { extensions_info->at(i)->extension_manifest.reset( static_cast<DictionaryValue*>( - extension->manifest_value()->DeepCopy())); + extension->manifest()->value()->DeepCopy())); should_write_prefs = true; } } diff --git a/chrome/chrome_common.gypi b/chrome/chrome_common.gypi index 4ab037f..d29f9db 100644 --- a/chrome/chrome_common.gypi +++ b/chrome/chrome_common.gypi @@ -138,6 +138,8 @@ 'common/extensions/extension_unpacker.h', 'common/extensions/file_browser_handler.cc', 'common/extensions/file_browser_handler.h', + 'common/extensions/manifest.cc', + 'common/extensions/manifest.h', 'common/extensions/update_manifest.cc', 'common/extensions/update_manifest.h', 'common/extensions/url_pattern.cc', diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 89b904d..f543d377 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -1959,6 +1959,7 @@ 'common/extensions/extension_test_util.cc', 'common/extensions/extension_unittest.cc', 'common/extensions/extension_unpacker_unittest.cc', + 'common/extensions/manifest_unittest.cc', 'common/extensions/update_manifest_unittest.cc', 'common/extensions/url_pattern_set_unittest.cc', 'common/extensions/url_pattern_unittest.cc', diff --git a/chrome/common/extensions/extension.cc b/chrome/common/extensions/extension.cc index c68280f..1e84be7 100644 --- a/chrome/common/extensions/extension.cc +++ b/chrome/common/extensions/extension.cc @@ -34,6 +34,7 @@ #include "chrome/common/extensions/extension_sidebar_defaults.h" #include "chrome/common/extensions/extension_sidebar_utils.h" #include "chrome/common/extensions/file_browser_handler.h" +#include "chrome/common/extensions/manifest.h" #include "chrome/common/extensions/user_script.h" #include "chrome/common/url_constants.h" #include "googleurl/src/url_util.h" @@ -85,29 +86,6 @@ static void ConvertHexadecimalToIDAlphabet(std::string* id) { } } -// These keys are allowed by all crx files (apps, extensions, themes, etc). -static const char* kBaseCrxKeys[] = { - keys::kCurrentLocale, - keys::kDefaultLocale, - keys::kDescription, - keys::kIcons, - keys::kManifestVersion, - keys::kName, - keys::kPublicKey, - keys::kSignature, - keys::kUpdateURL, - keys::kVersion, -}; - -bool IsBaseCrxKey(const std::string& key) { - for (size_t i = 0; i < arraysize(kBaseCrxKeys); ++i) { - if (key == kBaseCrxKeys[i]) - return true; - } - - return false; -} - // A singleton object containing global data needed by the extension objects. class ExtensionConfig { public: @@ -253,7 +231,8 @@ scoped_refptr<Extension> Extension::Create(const FilePath& path, DCHECK(error); scoped_refptr<Extension> extension = new Extension(path, location); - if (!extension->InitFromValue(value, flags, error)) + if (!extension->InitFromValue(new extensions::Manifest(value.DeepCopy()), + flags, error)) return NULL; return extension; } @@ -900,31 +879,7 @@ ExtensionSidebarDefaults* Extension::LoadExtensionSidebarDefaults( return result.release(); } -bool Extension::ContainsNonThemeKeys(const DictionaryValue& source) const { - for (DictionaryValue::key_iterator key = source.begin_keys(); - key != source.end_keys(); ++key) { - if (!IsBaseCrxKey(*key) && *key != keys::kTheme) - return true; - } - return false; -} - -bool Extension::LoadIsApp(const DictionaryValue* manifest, - std::string* error) { - if (manifest->HasKey(keys::kApp)) - is_app_ = true; - - if (CommandLine::ForCurrentProcess()->HasSwitch( - switches::kEnablePlatformApps)) { - if (manifest->HasKey(keys::kPlatformApp)) { - manifest->GetBoolean(keys::kPlatformApp, &is_platform_app_); - } - } - - return true; -} - -bool Extension::LoadExtent(const DictionaryValue* manifest, +bool Extension::LoadExtent(const extensions::Manifest* manifest, const char* key, URLPatternSet* extent, const char* list_error, @@ -1001,7 +956,7 @@ bool Extension::LoadExtent(const DictionaryValue* manifest, return true; } -bool Extension::LoadLaunchURL(const DictionaryValue* manifest, +bool Extension::LoadLaunchURL(const extensions::Manifest* manifest, std::string* error) { Value* temp = NULL; @@ -1099,7 +1054,7 @@ bool Extension::LoadLaunchURL(const DictionaryValue* manifest, return true; } -bool Extension::LoadLaunchContainer(const DictionaryValue* manifest, +bool Extension::LoadLaunchContainer(const extensions::Manifest* manifest, std::string* error) { Value* temp = NULL; if (!manifest->Get(keys::kLaunchContainer, &temp)) @@ -1154,7 +1109,7 @@ bool Extension::LoadLaunchContainer(const DictionaryValue* manifest, return true; } -bool Extension::LoadAppIsolation(const DictionaryValue* manifest, +bool Extension::LoadAppIsolation(const extensions::Manifest* manifest, std::string* error) { Value* temp = NULL; if (!manifest->Get(keys::kIsolation, &temp)) @@ -1186,18 +1141,18 @@ bool Extension::LoadAppIsolation(const DictionaryValue* manifest, return true; } -bool Extension::LoadWebIntentServices(const base::DictionaryValue& manifest, +bool Extension::LoadWebIntentServices(const extensions::Manifest* manifest, std::string* error) { DCHECK(error); if (!CommandLine::ForCurrentProcess()->HasSwitch(switches::kEnableWebIntents)) return true; - if (!manifest.HasKey(keys::kIntents)) + if (!manifest->HasKey(keys::kIntents)) return true; DictionaryValue* all_services = NULL; - if (!manifest.GetDictionary(keys::kIntents, &all_services)) { + if (!manifest->GetDictionary(keys::kIntents, &all_services)) { *error = errors::kInvalidIntents; return false; } @@ -1256,32 +1211,6 @@ bool Extension::LoadWebIntentServices(const base::DictionaryValue& manifest, return true; } - -bool Extension::EnsureNotHybridApp(const DictionaryValue* manifest, - std::string* error) { - if (web_extent().is_empty()) - return true; - - for (DictionaryValue::key_iterator key = manifest->begin_keys(); - key != manifest->end_keys(); ++key) { - if (!IsBaseCrxKey(*key) && - *key != keys::kApp && - *key != keys::kPermissions && - *key != keys::kOptionalPermissions && - *key != keys::kOptionsPage && - *key != keys::kBackground && - *key != keys::kOfflineEnabled && - *key != keys::kMinimumChromeVersion && - *key != keys::kRequirements) { - *error = ExtensionErrorUtils::FormatErrorMessage( - errors::kHostedAppsCannotIncludeExtensionFeatures, *key); - return false; - } - } - - return true; -} - // static bool Extension::IsTrustedId(const std::string& id) { // See http://b/4946060 for more details. @@ -1294,9 +1223,6 @@ Extension::Extension(const FilePath& path, Location location) offline_enabled_(false), location_(location), converted_from_user_script_(false), - is_theme_(false), - is_app_(false), - is_platform_app_(false), is_storage_isolated_(false), launch_container_(extension_misc::LAUNCH_TAB), launch_width_(0), @@ -1455,10 +1381,14 @@ GURL Extension::GetBaseURLFromExtensionId(const std::string& extension_id) { chrome::kStandardSchemeSeparator + extension_id + "/"); } -bool Extension::InitFromValue(const DictionaryValue& source, int flags, +bool Extension::InitFromValue(extensions::Manifest* manifest, int flags, std::string* error) { DCHECK(error); base::AutoLock auto_lock(runtime_data_lock_); + + if (!manifest->ValidateManifest(error)) + return false; + // When strict error checks are enabled, make URL pattern parsing strict. URLPattern::ParseOption parse_strictness = (flags & STRICT_ERROR_CHECKS ? URLPattern::ERROR_ON_PORTS @@ -1469,9 +1399,9 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags, optional_permission_set_ = new ExtensionPermissionSet(); required_permission_set_ = new ExtensionPermissionSet(); - if (source.HasKey(keys::kManifestVersion)) { + if (manifest->HasKey(keys::kManifestVersion)) { int manifest_version = 0; - if (!source.GetInteger(keys::kManifestVersion, &manifest_version) || + if (!manifest->GetInteger(keys::kManifestVersion, &manifest_version) || manifest_version < 1) { *error = errors::kInvalidManifestVersion; return false; @@ -1490,10 +1420,10 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags, return false; } - if (source.HasKey(keys::kPublicKey)) { + if (manifest->HasKey(keys::kPublicKey)) { std::string public_key_bytes; - if (!source.GetString(keys::kPublicKey, - &public_key_) || + if (!manifest->GetString(keys::kPublicKey, + &public_key_) || !ParsePEMKeyBytes(public_key_, &public_key_bytes) || !GenerateId(public_key_bytes, &id_)) { @@ -1516,15 +1446,14 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags, creation_flags_ = flags; - // Make a copy of the manifest so we can store it in prefs. - manifest_value_.reset(source.DeepCopy()); + manifest_.reset(manifest); // Initialize the URL. extension_url_ = Extension::GetBaseURLFromExtensionId(id()); // Initialize version. std::string version_str; - if (!source.GetString(keys::kVersion, &version_str)) { + if (!manifest->GetString(keys::kVersion, &version_str)) { *error = errors::kInvalidVersion; return false; } @@ -1537,7 +1466,7 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags, // Initialize name. string16 localized_name; - if (!source.GetString(keys::kName, &localized_name)) { + if (!manifest->GetString(keys::kName, &localized_name)) { *error = errors::kInvalidName; return false; } @@ -1547,18 +1476,17 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags, // Load App settings. LoadExtent at least has to be done before // ParsePermissions(), because the valid permissions depend on what type of // package this is. - if (!LoadIsApp(manifest_value_.get(), error) || - !LoadExtent(manifest_value_.get(), keys::kWebURLs, - &extent_, - errors::kInvalidWebURLs, errors::kInvalidWebURL, - parse_strictness, error) || - !EnsureNotHybridApp(manifest_value_.get(), error) || - !LoadLaunchURL(manifest_value_.get(), error) || - !LoadLaunchContainer(manifest_value_.get(), error)) { + if (is_app() && + (!LoadExtent(manifest_.get(), keys::kWebURLs, + &extent_, + errors::kInvalidWebURLs, errors::kInvalidWebURL, + parse_strictness, error) || + !LoadLaunchURL(manifest_.get(), error) || + !LoadLaunchContainer(manifest_.get(), error))) { return false; } - if (is_platform_app_) { + if (is_platform_app()) { if (launch_container() != extension_misc::LAUNCH_SHELL) { *error = errors::kInvalidLaunchContainerForPlatform; return false; @@ -1571,7 +1499,7 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags, // Initialize the permissions (optional). ExtensionAPIPermissionSet api_permissions; URLPatternSet host_permissions; - if (!ParsePermissions(&source, + if (!ParsePermissions(manifest_.get(), keys::kPermissions, flags, error, @@ -1583,7 +1511,7 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags, // Initialize the optional permissions (optional). ExtensionAPIPermissionSet optional_api_permissions; URLPatternSet optional_host_permissions; - if (!ParsePermissions(&source, + if (!ParsePermissions(manifest_.get(), keys::kOptionalPermissions, flags, error, @@ -1593,8 +1521,8 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags, } // Initialize description (if present). - if (source.HasKey(keys::kDescription)) { - if (!source.GetString(keys::kDescription, + if (manifest->HasKey(keys::kDescription)) { + if (!manifest->GetString(keys::kDescription, &description_)) { *error = errors::kInvalidDescription; return false; @@ -1602,9 +1530,9 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags, } // Initialize homepage url (if present). - if (source.HasKey(keys::kHomepageURL)) { + if (manifest->HasKey(keys::kHomepageURL)) { std::string tmp; - if (!source.GetString(keys::kHomepageURL, &tmp)) { + if (!manifest->GetString(keys::kHomepageURL, &tmp)) { *error = ExtensionErrorUtils::FormatErrorMessage( errors::kInvalidHomepageURL, ""); return false; @@ -1620,9 +1548,9 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags, } // Initialize update url (if present). - if (source.HasKey(keys::kUpdateURL)) { + if (manifest->HasKey(keys::kUpdateURL)) { std::string tmp; - if (!source.GetString(keys::kUpdateURL, &tmp)) { + if (!manifest->GetString(keys::kUpdateURL, &tmp)) { *error = ExtensionErrorUtils::FormatErrorMessage( errors::kInvalidUpdateURL, ""); return false; @@ -1638,9 +1566,9 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags, // Validate minimum Chrome version (if present). We don't need to store this, // since the extension is not valid if it is incorrect. - if (source.HasKey(keys::kMinimumChromeVersion)) { + if (manifest->HasKey(keys::kMinimumChromeVersion)) { std::string minimum_version_string; - if (!source.GetString(keys::kMinimumChromeVersion, + if (!manifest->GetString(keys::kMinimumChromeVersion, &minimum_version_string)) { *error = errors::kInvalidMinimumChromeVersion; return false; @@ -1676,13 +1604,14 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags, } // Initialize converted_from_user_script (if present) - source.GetBoolean(keys::kConvertedFromUserScript, - &converted_from_user_script_); + if (manifest->HasKey(keys::kConvertedFromUserScript)) + manifest->GetBoolean(keys::kConvertedFromUserScript, + &converted_from_user_script_); // Initialize icons (if present). - if (source.HasKey(keys::kIcons)) { + if (manifest->HasKey(keys::kIcons)) { DictionaryValue* icons_value = NULL; - if (!source.GetDictionary(keys::kIcons, &icons_value)) { + if (!manifest->GetDictionary(keys::kIcons, &icons_value)) { *error = errors::kInvalidIcons; return false; } @@ -1712,20 +1641,12 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags, } // Initialize themes (if present). - is_theme_ = false; - if (source.HasKey(keys::kTheme)) { - // Themes cannot contain extension keys. - if (ContainsNonThemeKeys(source)) { - *error = errors::kThemesCannotContainExtensions; - return false; - } - + if (manifest->HasKey(keys::kTheme)) { DictionaryValue* theme_value = NULL; - if (!source.GetDictionary(keys::kTheme, &theme_value)) { + if (!manifest->GetDictionary(keys::kTheme, &theme_value)) { *error = errors::kInvalidTheme; return false; } - is_theme_ = true; DictionaryValue* images_value = NULL; if (theme_value->GetDictionary(keys::kThemeImages, &images_value)) { @@ -1798,9 +1719,9 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags, } // Initialize plugins (optional). - if (source.HasKey(keys::kPlugins)) { + if (manifest->HasKey(keys::kPlugins)) { ListValue* list_value = NULL; - if (!source.GetList(keys::kPlugins, &list_value)) { + if (!manifest->GetList(keys::kPlugins, &list_value)) { *error = errors::kInvalidPlugins; return false; } @@ -1842,9 +1763,9 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags, } } - if (source.HasKey(keys::kNaClModules)) { + if (manifest->HasKey(keys::kNaClModules)) { ListValue* list_value = NULL; - if (!source.GetList(keys::kNaClModules, &list_value)) { + if (!manifest->GetList(keys::kNaClModules, &list_value)) { *error = errors::kInvalidNaClModules; return false; } @@ -1880,9 +1801,9 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags, } // Initialize content scripts (optional). - if (source.HasKey(keys::kContentScripts)) { + if (manifest->HasKey(keys::kContentScripts)) { ListValue* list_value; - if (!source.GetList(keys::kContentScripts, &list_value)) { + if (!manifest->GetList(keys::kContentScripts, &list_value)) { *error = errors::kInvalidContentScriptsList; return false; } @@ -1910,9 +1831,9 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags, // Initialize page action (optional). DictionaryValue* page_action_value = NULL; - if (source.HasKey(keys::kPageActions)) { + if (manifest->HasKey(keys::kPageActions)) { ListValue* list_value = NULL; - if (!source.GetList(keys::kPageActions, &list_value)) { + if (!manifest->GetList(keys::kPageActions, &list_value)) { *error = errors::kInvalidPageActionsList; return false; } @@ -1931,8 +1852,8 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags, *error = errors::kInvalidPageActionsListSize; return false; } - } else if (source.HasKey(keys::kPageAction)) { - if (!source.GetDictionary(keys::kPageAction, &page_action_value)) { + } else if (manifest->HasKey(keys::kPageAction)) { + if (!manifest->GetDictionary(keys::kPageAction, &page_action_value)) { *error = errors::kInvalidPageAction; return false; } @@ -1947,9 +1868,9 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags, } // Initialize browser action (optional). - if (source.HasKey(keys::kBrowserAction)) { + if (manifest->HasKey(keys::kBrowserAction)) { DictionaryValue* browser_action_value = NULL; - if (!source.GetDictionary(keys::kBrowserAction, &browser_action_value)) { + if (!manifest->GetDictionary(keys::kBrowserAction, &browser_action_value)) { *error = errors::kInvalidBrowserAction; return false; } @@ -1961,9 +1882,9 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags, } // Initialize file browser actions (optional). - if (source.HasKey(keys::kFileBrowserHandlers)) { + if (manifest->HasKey(keys::kFileBrowserHandlers)) { ListValue* file_browser_handlers_value = NULL; - if (!source.GetList(keys::kFileBrowserHandlers, + if (!manifest->GetList(keys::kFileBrowserHandlers, &file_browser_handlers_value)) { *error = errors::kInvalidFileBrowserHandler; return false; @@ -1977,15 +1898,14 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags, // App isolation. if (api_permissions.count(ExtensionAPIPermission::kExperimental)) { - if (!LoadAppIsolation(manifest_value_.get(), error)) + if (is_app() && !LoadAppIsolation(manifest_.get(), error)) return false; } // Initialize options page url (optional). - // Function LoadIsApp() set is_app_ above. - if (source.HasKey(keys::kOptionsPage)) { + if (manifest->HasKey(keys::kOptionsPage)) { std::string options_str; - if (!source.GetString(keys::kOptionsPage, &options_str)) { + if (!manifest->GetString(keys::kOptionsPage, &options_str)) { *error = errors::kInvalidOptionsPage; return false; } @@ -2014,9 +1934,9 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags, } // Initialize background url (optional). - if (source.HasKey(keys::kBackground)) { + if (manifest->HasKey(keys::kBackground)) { std::string background_str; - if (!source.GetString(keys::kBackground, &background_str)) { + if (!manifest->GetString(keys::kBackground, &background_str)) { *error = errors::kInvalidBackground; return false; } @@ -2047,8 +1967,8 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags, } } - if (source.HasKey(keys::kDefaultLocale)) { - if (!source.GetString(keys::kDefaultLocale, &default_locale_) || + if (manifest->HasKey(keys::kDefaultLocale)) { + if (!manifest->GetString(keys::kDefaultLocale, &default_locale_) || !l10n_util::IsValidLocaleSyntax(default_locale_)) { *error = errors::kInvalidDefaultLocale; return false; @@ -2056,9 +1976,9 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags, } // Chrome URL overrides (optional) - if (source.HasKey(keys::kChromeURLOverrides)) { + if (manifest->HasKey(keys::kChromeURLOverrides)) { DictionaryValue* overrides = NULL; - if (!source.GetDictionary(keys::kChromeURLOverrides, &overrides)) { + if (!manifest->GetDictionary(keys::kChromeURLOverrides, &overrides)) { *error = errors::kInvalidChromeURLOverrides; return false; } @@ -2100,9 +2020,9 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags, } if (api_permissions.count(ExtensionAPIPermission::kExperimental) && - source.HasKey(keys::kInputComponents)) { + manifest->HasKey(keys::kInputComponents)) { ListValue* list_value = NULL; - if (!source.GetList(keys::kInputComponents, &list_value)) { + if (!manifest->GetList(keys::kInputComponents, &list_value)) { *error = errors::kInvalidInputComponents; return false; } @@ -2231,17 +2151,17 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags, } } - if (source.HasKey(keys::kOmnibox)) { - if (!source.GetString(keys::kOmniboxKeyword, &omnibox_keyword_) || + if (manifest->HasKey(keys::kOmnibox)) { + if (!manifest->GetString(keys::kOmniboxKeyword, &omnibox_keyword_) || omnibox_keyword_.empty()) { *error = errors::kInvalidOmniboxKeyword; return false; } } - if (source.HasKey(keys::kContentSecurityPolicy)) { + if (manifest->HasKey(keys::kContentSecurityPolicy)) { std::string content_security_policy; - if (!source.GetString(keys::kContentSecurityPolicy, + if (!manifest->GetString(keys::kContentSecurityPolicy, &content_security_policy)) { *error = errors::kInvalidContentSecurityPolicy; return false; @@ -2266,9 +2186,9 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags, } // Initialize devtools page url (optional). - if (source.HasKey(keys::kDevToolsPage)) { + if (manifest->HasKey(keys::kDevToolsPage)) { std::string devtools_str; - if (!source.GetString(keys::kDevToolsPage, &devtools_str)) { + if (!manifest->GetString(keys::kDevToolsPage, &devtools_str)) { *error = errors::kInvalidDevToolsPage; return false; } @@ -2280,9 +2200,9 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags, } // Initialize sidebar action (optional). - if (source.HasKey(keys::kSidebar)) { + if (manifest->HasKey(keys::kSidebar)) { DictionaryValue* sidebar_value = NULL; - if (!source.GetDictionary(keys::kSidebar, &sidebar_value)) { + if (!manifest->GetDictionary(keys::kSidebar, &sidebar_value)) { *error = errors::kInvalidSidebar; return false; } @@ -2296,9 +2216,9 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags, } // Initialize text-to-speech voices (optional). - if (source.HasKey(keys::kTtsEngine)) { + if (manifest->HasKey(keys::kTtsEngine)) { DictionaryValue* tts_dict = NULL; - if (!source.GetDictionary(keys::kTtsEngine, &tts_dict)) { + if (!manifest->GetDictionary(keys::kTtsEngine, &tts_dict)) { *error = errors::kInvalidTts; return false; } @@ -2379,15 +2299,15 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags, } // Initialize web intents (optional). - if (!LoadWebIntentServices(source, error)) + if (!LoadWebIntentServices(manifest, error)) return false; // Initialize incognito behavior. Apps default to split mode, extensions // default to spanning. incognito_split_mode_ = is_app(); - if (source.HasKey(keys::kIncognito)) { + if (manifest->HasKey(keys::kIncognito)) { std::string value; - if (!source.GetString(keys::kIncognito, &value)) { + if (!manifest->GetString(keys::kIncognito, &value)) { *error = errors::kInvalidIncognitoBehavior; return false; } @@ -2402,8 +2322,8 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags, } // Initialize offline-enabled status. Defaults to false. - if (source.HasKey(keys::kOfflineEnabled)) { - if (!source.GetBoolean(keys::kOfflineEnabled, &offline_enabled_)) { + if (manifest->HasKey(keys::kOfflineEnabled)) { + if (!manifest->GetBoolean(keys::kOfflineEnabled, &offline_enabled_)) { *error = errors::kInvalidOfflineEnabled; return false; } @@ -2411,9 +2331,9 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags, // Initialize requirements (optional). Not actually persisted (they're only // used by the store), but still validated. - if (source.HasKey(keys::kRequirements)) { + if (manifest->HasKey(keys::kRequirements)) { DictionaryValue* requirements_value = NULL; - if (!source.GetDictionary(keys::kRequirements, &requirements_value)) { + if (!manifest->GetDictionary(keys::kRequirements, &requirements_value)) { *error = errors::kInvalidRequirements; return false; } @@ -2442,13 +2362,6 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags, optional_permission_set_ = new ExtensionPermissionSet( optional_api_permissions, optional_host_permissions, URLPatternSet()); - // Although |source| is passed in as a const, it's still possible to modify - // it. This is dangerous since the utility process re-uses |source| after - // it calls InitFromValue, passing it up to the browser process which calls - // InitFromValue again. As a result, we need to make sure that nobody - // accidentally modifies it. - DCHECK(source.Equals(manifest_value_.get())); - return true; } @@ -2604,7 +2517,7 @@ GURL Extension::GetIconURL(int size, return GetResourceURL(path); } -bool Extension::ParsePermissions(const DictionaryValue* source, +bool Extension::ParsePermissions(const extensions::Manifest* source, const char* key, int flags, std::string* error, diff --git a/chrome/common/extensions/extension.h b/chrome/common/extensions/extension.h index a0ec6d8..54bff60 100644 --- a/chrome/common/extensions/extension.h +++ b/chrome/common/extensions/extension.h @@ -20,6 +20,7 @@ #include "chrome/common/extensions/extension_constants.h" #include "chrome/common/extensions/extension_icon_set.h" #include "chrome/common/extensions/extension_permission_set.h" +#include "chrome/common/extensions/manifest.h" #include "chrome/common/extensions/user_script.h" #include "chrome/common/extensions/url_pattern.h" #include "chrome/common/extensions/url_pattern_set.h" @@ -374,7 +375,7 @@ class Extension : public base::RefCountedThreadSafe<Extension> { // Parses the host and api permissions from the specified permission |key| // in the manifest |source|. - bool ParsePermissions(const base::DictionaryValue* source, + bool ParsePermissions(const extensions::Manifest* source, const char* key, int flags, std::string* error, @@ -531,8 +532,8 @@ class Extension : public base::RefCountedThreadSafe<Extension> { } const GURL& update_url() const { return update_url_; } const ExtensionIconSet& icons() const { return icons_; } - const base::DictionaryValue* manifest_value() const { - return manifest_value_.get(); + const extensions::Manifest* manifest() const { + return manifest_.get(); } const std::string default_locale() const { return default_locale_; } const URLOverrideMap& GetChromeURLOverrides() const { @@ -557,12 +558,12 @@ class Extension : public base::RefCountedThreadSafe<Extension> { } // App-related. - bool is_app() const { return is_app_; } - bool is_platform_app() const { return is_platform_app_; } - bool is_hosted_app() const { return is_app() && !web_extent().is_empty(); } - bool is_packaged_app() const { - return !is_platform_app() && is_app() && web_extent().is_empty(); + bool is_app() const { + return is_packaged_app() || is_hosted_app() || is_platform_app(); } + bool is_platform_app() const { return manifest()->IsPlatformApp(); } + bool is_hosted_app() const { return manifest()->IsHostedApp(); } + bool is_packaged_app() const { return manifest()->IsPackagedApp(); } bool is_storage_isolated() const { return is_app() && is_storage_isolated_; } const URLPatternSet& web_extent() const { return extent_; } const std::string& launch_local_path() const { return launch_local_path_; } @@ -574,7 +575,7 @@ class Extension : public base::RefCountedThreadSafe<Extension> { int launch_height() const { return launch_height_; } // Theme-related. - bool is_theme() const { return is_theme_; } + bool is_theme() const { return manifest()->IsTheme(); } base::DictionaryValue* GetThemeImages() const { return theme_images_.get(); } base::DictionaryValue* GetThemeColors() const {return theme_colors_.get(); } base::DictionaryValue* GetThemeTints() const { return theme_tints_.get(); } @@ -616,7 +617,8 @@ class Extension : public base::RefCountedThreadSafe<Extension> { ~Extension(); // Initialize the extension from a parsed manifest. - bool InitFromValue(const base::DictionaryValue& value, int flags, + // Takes ownership of the manifest |value|. + bool InitFromValue(extensions::Manifest* value, int flags, std::string* error); // Helper function for implementing HasCachedImage/GetCachedImage. A return @@ -643,24 +645,21 @@ class Extension : public base::RefCountedThreadSafe<Extension> { UserScript *instance); // Helpers to load various chunks of the manifest. - bool LoadIsApp(const base::DictionaryValue* manifest, std::string* error); - bool LoadExtent(const base::DictionaryValue* manifest, + bool LoadExtent(const extensions::Manifest* manifest, const char* key, URLPatternSet* extent, const char* list_error, const char* value_error, URLPattern::ParseOption parse_strictness, std::string* error); - bool LoadLaunchContainer(const base::DictionaryValue* manifest, + bool LoadLaunchContainer(const extensions::Manifest* manifest, std::string* error); - bool LoadLaunchURL(const base::DictionaryValue* manifest, + bool LoadLaunchURL(const extensions::Manifest* manifest, std::string* error); - bool LoadAppIsolation(const base::DictionaryValue* manifest, + bool LoadAppIsolation(const extensions::Manifest* manifest, std::string* error); - bool LoadWebIntentServices(const base::DictionaryValue& manifest, + bool LoadWebIntentServices(const extensions::Manifest* manifest, std::string* error); - bool EnsureNotHybridApp(const base::DictionaryValue* manifest, - std::string* error); // Helper method to load an ExtensionAction from the page_action or // browser_action entries in the manifest. @@ -683,10 +682,6 @@ class Extension : public base::RefCountedThreadSafe<Extension> { // an extension that has a browser action and a page action. bool HasMultipleUISurfaces() const; - // Figures out if a source contains keys not associated with themes - we - // don't want to allow scripts and such to be bundled with themes. - bool ContainsNonThemeKeys(const base::DictionaryValue& source) const; - // Updates the launch URL and extents for the extension using the given // |override_url|. void OverrideLaunchUrl(const GURL& override_url); @@ -820,9 +815,6 @@ class Extension : public base::RefCountedThreadSafe<Extension> { // A map of display properties. scoped_ptr<base::DictionaryValue> theme_display_properties_; - // Whether the extension is a theme. - bool is_theme_; - // The homepage for this extension. Useful if it is not hosted by Google and // therefore does not have a Gallery URL. GURL homepage_url_; @@ -830,19 +822,13 @@ class Extension : public base::RefCountedThreadSafe<Extension> { // URL for fetching an update manifest GURL update_url_; - // A copy of the manifest that this extension was created from. - scoped_ptr<base::DictionaryValue> manifest_value_; + // The manifest that this extension was created from. + scoped_ptr<extensions::Manifest> manifest_; // A map of chrome:// hostnames (newtab, downloads, etc.) to Extension URLs // which override the handling of those URLs. (see ExtensionOverrideUI). URLOverrideMap chrome_url_overrides_; - // Whether this extension uses app features. - bool is_app_; - - // Whether this app uses platform features. - bool is_platform_app_; - // Whether this extension requests isolated storage. bool is_storage_isolated_; diff --git a/chrome/common/extensions/extension_constants.cc b/chrome/common/extensions/extension_constants.cc index 2d0e730..f818e62 100644 --- a/chrome/common/extensions/extension_constants.cc +++ b/chrome/common/extensions/extension_constants.cc @@ -166,8 +166,8 @@ const char kExpectString[] = "Expect string value."; const char kExperimentalFlagRequired[] = "Loading extensions with 'experimental' permission requires" " --enable-experimental-extension-apis command line flag."; -const char kHostedAppsCannotIncludeExtensionFeatures[] = - "Hosted apps cannot use the extension feature '*'."; +const char kFeatureNotAllowed[] = + "Feature '*' is not allowed in this type of manifest."; const char kInvalidAllFrames[] = "Invalid value for 'content_scripts[*].all_frames'."; const char kInvalidBackground[] = @@ -429,8 +429,6 @@ const char kReservedMessageFound[] = const char kSidebarExperimental[] = "You must request the 'experimental' permission in order to use the" " Sidebar API."; -const char kThemesCannotContainExtensions[] = - "A theme cannot contain extensions code."; #if defined(OS_CHROMEOS) const char kIllegalPlugins[] = "Extensions cannot install plugins on Chrome OS"; diff --git a/chrome/common/extensions/extension_constants.h b/chrome/common/extensions/extension_constants.h index 8c988f3..abb77f0 100644 --- a/chrome/common/extensions/extension_constants.h +++ b/chrome/common/extensions/extension_constants.h @@ -150,7 +150,7 @@ namespace extension_manifest_errors { extern const char kDisabledByPolicy[]; extern const char kExperimentalFlagRequired[]; extern const char kExpectString[]; - extern const char kHostedAppsCannotIncludeExtensionFeatures[]; + extern const char kFeatureNotAllowed[]; extern const char kInvalidAllFrames[]; extern const char kInvalidBackground[]; extern const char kInvalidBackgroundInHostedApp[]; @@ -280,7 +280,6 @@ namespace extension_manifest_errors { extern const char kOneUISurfaceOnly[]; extern const char kReservedMessageFound[]; extern const char kSidebarExperimental[]; - extern const char kThemesCannotContainExtensions[]; extern const char kWebContentMustBeEnabled[]; #if defined(OS_CHROMEOS) extern const char kIllegalPlugins[]; diff --git a/chrome/common/extensions/extension_manifests_unittest.cc b/chrome/common/extensions/extension_manifests_unittest.cc index adc6969..8136dea 100644 --- a/chrome/common/extensions/extension_manifests_unittest.cc +++ b/chrome/common/extensions/extension_manifests_unittest.cc @@ -588,12 +588,8 @@ TEST_F(ExtensionManifestTest, Sidebar) { extension->sidebar_defaults()->default_page().spec()); } -TEST_F(ExtensionManifestTest, DisallowHybridApps) { - LoadAndExpectError("disallow_hybrid_1.json", - ExtensionErrorUtils::FormatErrorMessage( - errors::kHostedAppsCannotIncludeExtensionFeatures, - keys::kBrowserAction)); - LoadAndExpectError("disallow_hybrid_2.json", +TEST_F(ExtensionManifestTest, BackgroundPermission) { + LoadAndExpectError("background_permission.json", errors::kBackgroundPermissionNeeded); } @@ -742,9 +738,7 @@ TEST_F(ExtensionManifestTest, NormalizeIconPaths) { } TEST_F(ExtensionManifestTest, DisallowMultipleUISurfaces) { - LoadAndExpectError("multiple_ui_surfaces_1.json", errors::kOneUISurfaceOnly); - LoadAndExpectError("multiple_ui_surfaces_2.json", errors::kOneUISurfaceOnly); - LoadAndExpectError("multiple_ui_surfaces_3.json", errors::kOneUISurfaceOnly); + LoadAndExpectError("multiple_ui_surfaces.json", errors::kOneUISurfaceOnly); } TEST_F(ExtensionManifestTest, ParseHomepageURLs) { diff --git a/chrome/common/extensions/extension_messages.cc b/chrome/common/extensions/extension_messages.cc index a326289..3adee27 100644 --- a/chrome/common/extensions/extension_messages.cc +++ b/chrome/common/extensions/extension_messages.cc @@ -5,6 +5,7 @@ #include "chrome/common/extensions/extension_messages.h" #include "chrome/common/extensions/extension_constants.h" +#include "chrome/common/extensions/manifest.h" #include "content/public/common/common_param_traits.h" ExtensionMsg_Loaded_Params::ExtensionMsg_Loaded_Params() @@ -49,10 +50,11 @@ ExtensionMsg_Loaded_Params::ExtensionMsg_Loaded_Params( extension_manifest_keys::kVersion, }; - // Copy only the data we need. + // Copy only the data we need and bypass the manifest type checks. + DictionaryValue* source = extension->manifest()->value(); for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kRendererExtensionKeys); ++i) { Value* temp = NULL; - if (extension->manifest_value()->Get(kRendererExtensionKeys[i], &temp)) + if (source->Get(kRendererExtensionKeys[i], &temp)) manifest->Set(kRendererExtensionKeys[i], temp->DeepCopy()); } } diff --git a/chrome/common/extensions/manifest.cc b/chrome/common/extensions/manifest.cc new file mode 100644 index 0000000..ecd5dfb --- /dev/null +++ b/chrome/common/extensions/manifest.cc @@ -0,0 +1,229 @@ +// Copyright (c) 2011 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/manifest.h" + +#include "base/basictypes.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/string_split.h" +#include "base/values.h" +#include "chrome/common/extensions/extension_constants.h" +#include "chrome/common/extensions/extension_error_utils.h" + +namespace errors = extension_manifest_errors; +namespace keys = extension_manifest_keys; + +namespace extensions { + +namespace { + +typedef std::map<std::string, int> RestrictionMap; + +struct Restrictions { + Restrictions() { + // Base keys that all manifests can specify. + map[keys::kName] = Manifest::kTypeAll; + map[keys::kVersion] = Manifest::kTypeAll; + map[keys::kManifestVersion] = Manifest::kTypeAll; + map[keys::kDescription] = Manifest::kTypeAll; + map[keys::kIcons] = Manifest::kTypeAll; + map[keys::kCurrentLocale] = Manifest::kTypeAll; + map[keys::kDefaultLocale] = Manifest::kTypeAll; + map[keys::kSignature] = Manifest::kTypeAll; + map[keys::kUpdateURL] = Manifest::kTypeAll; + map[keys::kPublicKey] = Manifest::kTypeAll; + + // Type specific. + map[keys::kApp] = Manifest::kTypeHostedApp | Manifest::kTypePackagedApp | + Manifest::kTypePlatformApp; + map[keys::kTheme] = Manifest::kTypeTheme; + + // keys::kPlatformApp holds a boolean, so all types can define it. + map[keys::kPlatformApp] = Manifest::kTypeAll; + + // Extensions only. + map[keys::kBrowserAction] = Manifest::kTypeExtension; + map[keys::kPageAction] = Manifest::kTypeExtension; + map[keys::kPageActions] = Manifest::kTypeExtension; + + // Everything except themes. + int all_but_themes = Manifest::kTypeAll - Manifest::kTypeTheme; + map[keys::kPermissions] = all_but_themes; + map[keys::kOptionalPermissions] = all_but_themes; + map[keys::kOptionsPage] = all_but_themes; + map[keys::kBackground] = all_but_themes; + map[keys::kOfflineEnabled] = all_but_themes; + map[keys::kMinimumChromeVersion] = all_but_themes; + map[keys::kRequirements] = all_but_themes; + map[keys::kConvertedFromUserScript] = all_but_themes; + map[keys::kNaClModules] = all_but_themes; + map[keys::kPlugins] = all_but_themes; + + // Extensions and packaged apps. + int ext_and_packaged = + Manifest::kTypeExtension | Manifest::kTypePackagedApp; + map[keys::kContentScripts] = ext_and_packaged; + map[keys::kOmnibox] = ext_and_packaged; + map[keys::kDevToolsPage] = ext_and_packaged; + map[keys::kSidebar] = ext_and_packaged; + map[keys::kHomepageURL] = ext_and_packaged; + map[keys::kChromeURLOverrides] = ext_and_packaged; + + // Extensions, packaged apps and platform apps. + int local_apps_and_ext = ext_and_packaged | Manifest::kTypePlatformApp; + map[keys::kContentSecurityPolicy] = local_apps_and_ext; + map[keys::kFileBrowserHandlers] = local_apps_and_ext; + map[keys::kIncognito] = local_apps_and_ext; + map[keys::kInputComponents] = local_apps_and_ext; + map[keys::kTtsEngine] = local_apps_and_ext; + map[keys::kIntents] = local_apps_and_ext; + } + + // Returns true if the |key| is recognized. + bool IsKnownKey(const std::string& key) const { + RestrictionMap::const_iterator i = map.find(key); + return i != map.end(); + } + + // Returns true if the given |key| can be specified by the manifest |type|. + bool CanAccessKey(const std::string& key, Manifest::Type type) const { + RestrictionMap::const_iterator i = map.find(key); + return (i != map.end() && (type & i->second) != 0); + } + + RestrictionMap map; +}; + +base::LazyInstance<Restrictions> g_restrictions; + +} // namespace + +// static +std::set<std::string> Manifest::GetAllKnownKeys() { + std::set<std::string> keys; + const RestrictionMap& map = g_restrictions.Get().map; + for (RestrictionMap::const_iterator i = map.begin(); i != map.end(); i++) + keys.insert(i->first); + return keys; +} + +Manifest::Manifest(DictionaryValue* value) : value_(value) {} +Manifest::~Manifest() {} + +bool Manifest::ValidateManifest(std::string* error) const { + Restrictions restrictions = g_restrictions.Get(); + Type type = GetType(); + + for (DictionaryValue::key_iterator key = value_->begin_keys(); + key != value_->end_keys(); ++key) { + // When validating the extension manifests, we ignore keys that are not + // recognized for forward compatibility. + if (!restrictions.IsKnownKey(*key)) { + // TODO(aa): Consider having an error here in the case of strict error + // checking to let developers know when they screw up. + continue; + } + + if (!restrictions.CanAccessKey(*key, type)) { + *error = ExtensionErrorUtils::FormatErrorMessage( + errors::kFeatureNotAllowed, *key); + return false; + } + } + + return true; +} + +bool Manifest::HasKey(const std::string& key) const { + Restrictions restrictions = g_restrictions.Get(); + return restrictions.CanAccessKey(key, GetType()) && value_->HasKey(key); +} + +bool Manifest::Get( + const std::string& path, Value** out_value) const { + return CanAccessPath(path) && value_->Get(path, out_value); +} + +bool Manifest::GetBoolean( + const std::string& path, bool* out_value) const { + return CanAccessPath(path) && value_->GetBoolean(path, out_value); +} + +bool Manifest::GetInteger( + const std::string& path, int* out_value) const { + return CanAccessPath(path) && value_->GetInteger(path, out_value); +} + +bool Manifest::GetString( + const std::string& path, std::string* out_value) const { + return CanAccessPath(path) && value_->GetString(path, out_value); +} + +bool Manifest::GetString( + const std::string& path, string16* out_value) const { + return CanAccessPath(path) && value_->GetString(path, out_value); +} + +bool Manifest::GetDictionary( + const std::string& path, DictionaryValue** out_value) const { + return CanAccessPath(path) && value_->GetDictionary(path, out_value); +} + +bool Manifest::GetList( + const std::string& path, ListValue** out_value) const { + return CanAccessPath(path) && value_->GetList(path, out_value); +} + +Manifest* Manifest::DeepCopy() const { + return new Manifest(value_->DeepCopy()); +} + +bool Manifest::Equals(const Manifest* other) const { + return other && value_->Equals(other->value()); +} + +Manifest::Type Manifest::GetType() const { + if (value_->HasKey(keys::kTheme)) + return kTypeTheme; + bool is_platform_app = false; + if (value_->GetBoolean(keys::kPlatformApp, &is_platform_app) && + is_platform_app) + return kTypePlatformApp; + if (value_->HasKey(keys::kApp)) { + if (value_->Get(keys::kWebURLs, NULL) || + value_->Get(keys::kLaunchWebURL, NULL)) + return kTypeHostedApp; + else + return kTypePackagedApp; + } else { + return kTypeExtension; + } +} + +bool Manifest::IsTheme() const { + return GetType() == kTypeTheme; +} + +bool Manifest::IsPlatformApp() const { + return GetType() == kTypePlatformApp; +} + +bool Manifest::IsPackagedApp() const { + return GetType() == kTypePackagedApp; +} + +bool Manifest::IsHostedApp() const { + return GetType() == kTypeHostedApp; +} + +bool Manifest::CanAccessPath(const std::string& path) const { + std::vector<std::string> components; + base::SplitString(path, '.', &components); + + Restrictions restrictions = g_restrictions.Get(); + return restrictions.CanAccessKey(components[0], GetType()); +} + +} // namespace extensions diff --git a/chrome/common/extensions/manifest.h b/chrome/common/extensions/manifest.h new file mode 100644 index 0000000..fb372708 --- /dev/null +++ b/chrome/common/extensions/manifest.h @@ -0,0 +1,112 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_COMMON_EXTENSIONS_MANIFEST_H_ +#define CHROME_COMMON_EXTENSIONS_MANIFEST_H_ +#pragma once + +#include <map> +#include <string> +#include <set> + +#include "base/memory/scoped_ptr.h" +#include "base/string16.h" + +namespace base { +class DictionaryValue; +class ListValue; +class Value; +} + +namespace extensions { + +// Lightweight wrapper around a DictionaryValue representing an extension's +// manifest. Currently enforces access to properties of the manifest based +// on manifest type. +// +// TODO(aa): Move more smarts about mmanifest into this class over time. +class Manifest { + public: + // Flags for matching types of extension manifests. + enum Type { + kTypeNone = 0, + + // Extension::TYPE_EXTENSION and Extension::TYPE_USER_SCRIPT + kTypeExtension = 1 << 0, + + // Extension::TYPE_THEME + kTypeTheme = 1 << 1, + + // Extension::TYPE_HOSTED_APP + kTypeHostedApp = 1 << 2, + + // Extension::TYPE_PACKAGED_APP + kTypePackagedApp = 1 << 3, + + // Extension::TYPE_PLATFORM_APP + kTypePlatformApp = 1 << 4, + + // All types + kTypeAll = (1 << 5) - 1, + }; + + // Returns all known keys (this is used for testing). + static std::set<std::string> GetAllKnownKeys(); + + // Takes over ownership of |value|. + explicit Manifest(base::DictionaryValue* value); + virtual ~Manifest(); + + // Returns true if all keys in the manifest can be specified by + // the extension type. + bool ValidateManifest(std::string* error) const; + + // Returns the manifest type. + Type GetType() const; + + // Returns true if the manifest represents an Extension::TYPE_THEME. + bool IsTheme() const; + + // Returns true for Extension::TYPE_PLATFORM_APP + bool IsPlatformApp() const; + + // Returns true for Extension::TYPE_PACKAGED_APP. + bool IsPackagedApp() const; + + // Returns true for Extension::TYPE_HOSTED_APP. + bool IsHostedApp() const; + + // These access the wrapped manifest value, returning false when the property + // does not exist or if the manifest type can't access it. + bool HasKey(const std::string& key) const; + bool Get(const std::string& path, base::Value** out_value) const; + bool GetBoolean(const std::string& path, bool* out_value) const; + bool GetInteger(const std::string& path, int* out_value) const; + bool GetString(const std::string& path, std::string* out_value) const; + bool GetString(const std::string& path, string16* out_value) const; + bool GetDictionary(const std::string& path, + base::DictionaryValue** out_value) const; + bool GetList(const std::string& path, base::ListValue** out_value) const; + + // Returns a new Manifest equal to this one, passing ownership to + // the caller. + Manifest* DeepCopy() const; + + // Returns true if this equals the |other| manifest. + bool Equals(const Manifest* other) const; + + // Gets the underlying DictionaryValue representing the manifest. + // Note: only know this when you KNOW you don't need the validation. + base::DictionaryValue* value() const { return value_.get(); } + + private: + // Returns true if the extension can specify the given |path|. + bool CanAccessPath(const std::string& path) const; + + scoped_ptr<base::DictionaryValue> value_; +}; + +} // namespace extensions + +#endif // CHROME_COMMON_EXTENSIONS_MANIFEST_H_ diff --git a/chrome/common/extensions/manifest_unittest.cc b/chrome/common/extensions/manifest_unittest.cc new file mode 100644 index 0000000..66d4a6d --- /dev/null +++ b/chrome/common/extensions/manifest_unittest.cc @@ -0,0 +1,391 @@ +// Copyright (c) 2011 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/manifest.h" + +#include <algorithm> +#include <set> +#include <string> + +#include "base/memory/scoped_ptr.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "chrome/common/extensions/extension_constants.h" +#include "chrome/common/extensions/extension_error_utils.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace keys = extension_manifest_keys; +namespace errors = extension_manifest_errors; + +namespace extensions { + +namespace { + +// Keys that define types. +const char* kTypeKeys[] = { + keys::kApp, + keys::kTheme, + keys::kPlatformApp +}; + +// Keys that are not accesible by themes. +const char* kNotThemeKeys[] = { + keys::kBrowserAction, + keys::kPageAction, + keys::kPageActions, + keys::kChromeURLOverrides, + keys::kPermissions, + keys::kOptionalPermissions, + keys::kOptionsPage, + keys::kBackground, + keys::kOfflineEnabled, + keys::kMinimumChromeVersion, + keys::kRequirements, + keys::kConvertedFromUserScript, + keys::kNaClModules, + keys::kPlugins, + keys::kContentScripts, + keys::kOmnibox, + keys::kDevToolsPage, + keys::kSidebar, + keys::kHomepageURL, + keys::kContentSecurityPolicy, + keys::kFileBrowserHandlers, + keys::kIncognito, + keys::kInputComponents, + keys::kTtsEngine, + keys::kIntents +}; + +// Keys that are not accessible by hosted apps. +const char* kNotHostedAppKeys[] = { + keys::kBrowserAction, + keys::kPageAction, + keys::kPageActions, + keys::kChromeURLOverrides, + keys::kContentScripts, + keys::kOmnibox, + keys::kDevToolsPage, + keys::kSidebar, + keys::kHomepageURL, + keys::kContentSecurityPolicy, + keys::kFileBrowserHandlers, + keys::kIncognito, + keys::kInputComponents, + keys::kTtsEngine, + keys::kIntents +}; + +// Keys not accessible by packaged aps. +const char* kNotPackagedAppKeys[] = { + keys::kBrowserAction, + keys::kPageAction, + keys::kPageActions +}; + +// Keys not accessible by platform apps. +const char* kNotPlatformAppKeys[] = { + keys::kBrowserAction, + keys::kPageAction, + keys::kPageActions, + keys::kChromeURLOverrides, + keys::kContentScripts, + keys::kOmnibox, + keys::kDevToolsPage, + keys::kSidebar, + keys::kHomepageURL, +}; + +// Returns all the manifest keys not including those in |filtered| or kTypeKeys. +std::set<std::string> GetAccessibleKeys(const char* filtered[], size_t length) { + std::set<std::string> all_keys = Manifest::GetAllKnownKeys(); + std::set<std::string> filtered_keys(filtered, filtered + length); + + // Starting with all possible manfiest keys, remove the keys that aren't + // accessible for the given type. + std::set<std::string> intermediate; + std::set_difference(all_keys.begin(), all_keys.end(), + filtered_keys.begin(), filtered_keys.end(), + std::insert_iterator<std::set<std::string> >( + intermediate, intermediate.begin())); + + // Then remove the keys that specify types (app, platform_app, etc.). + std::set<std::string> result; + std::set<std::string> type_keys( + kTypeKeys, kTypeKeys + ARRAYSIZE_UNSAFE(kTypeKeys)); + std::set_difference(intermediate.begin(), intermediate.end(), + type_keys.begin(), type_keys.end(), + std::insert_iterator<std::set<std::string> >( + result, result.begin())); + + return result; +} + +} // namespace + +class ManifestTest : public testing::Test { + public: + ManifestTest() : default_value_("test") {} + + protected: + void AssertType(Manifest* manifest, Manifest::Type type) { + EXPECT_EQ(type, manifest->GetType()); + EXPECT_EQ(type == Manifest::kTypeTheme, manifest->IsTheme()); + EXPECT_EQ(type == Manifest::kTypePlatformApp, manifest->IsPlatformApp()); + EXPECT_EQ(type == Manifest::kTypePackagedApp, manifest->IsPackagedApp()); + EXPECT_EQ(type == Manifest::kTypeHostedApp, manifest->IsHostedApp()); + } + + void TestRestrictedKeys(Manifest* manifest, + const char* restricted_keys[], + size_t restricted_keys_length) { + // Verify that the keys on the restricted key list for the given manifest + // fail validation and are filtered out. + DictionaryValue* value = manifest->value(); + for (size_t i = 0; i < restricted_keys_length; ++i) { + std::string error, str; + value->Set(restricted_keys[i], Value::CreateStringValue(default_value_)); + EXPECT_FALSE(manifest->ValidateManifest(&error)); + EXPECT_EQ(error, ExtensionErrorUtils::FormatErrorMessage( + errors::kFeatureNotAllowed, restricted_keys[i])); + EXPECT_FALSE(manifest->GetString(restricted_keys[i], &str)); + EXPECT_TRUE(value->Remove(restricted_keys[i], NULL)); + } + } + + std::string default_value_; +}; + +// Verifies that extensions can access the correct keys. +TEST_F(ManifestTest, Extension) { + // Generate the list of keys accessible by extensions. + std::set<std::string> extension_keys = GetAccessibleKeys(NULL, 0u); + + // Construct the underlying value using every single key other than those + // on the restricted list.. We can use the same value for every key because we + // validate only by checking the presence of the keys. + DictionaryValue* value = new DictionaryValue(); + for (std::set<std::string>::iterator i = extension_keys.begin(); + i != extension_keys.end(); ++i) + value->Set(*i, Value::CreateStringValue(default_value_)); + + scoped_ptr<Manifest> manifest(new Manifest(value)); + std::string error; + EXPECT_TRUE(manifest->ValidateManifest(&error)); + EXPECT_EQ("", error); + AssertType(manifest.get(), Manifest::kTypeExtension); + + // Verify that all the extension keys are accessible. + for (std::set<std::string>::iterator i = extension_keys.begin(); + i != extension_keys.end(); ++i) { + std::string value; + manifest->GetString(*i, &value); + EXPECT_EQ(default_value_, value) << *i; + } + + // Test DeepCopy and Equals. + scoped_ptr<Manifest> manifest2(manifest->DeepCopy()); + EXPECT_TRUE(manifest->Equals(manifest2.get())); + EXPECT_TRUE(manifest2->Equals(manifest.get())); + value->Set("foo", Value::CreateStringValue("blah")); + EXPECT_FALSE(manifest->Equals(manifest2.get())); +} + +// Verifies that themes can access the right keys. +TEST_F(ManifestTest, Theme) { + std::set<std::string> theme_keys = + GetAccessibleKeys(kNotThemeKeys, ARRAYSIZE_UNSAFE(kNotThemeKeys)); + + DictionaryValue* value = new DictionaryValue(); + for (std::set<std::string>::iterator i = theme_keys.begin(); + i != theme_keys.end(); ++i) + value->Set(*i, Value::CreateStringValue(default_value_)); + + std::string theme_key = keys::kTheme + std::string(".test"); + value->Set(theme_key, Value::CreateStringValue(default_value_)); + + scoped_ptr<Manifest> manifest(new Manifest(value)); + std::string error; + EXPECT_TRUE(manifest->ValidateManifest(&error)); + EXPECT_EQ("", error); + AssertType(manifest.get(), Manifest::kTypeTheme); + + // Verify that all the theme keys are accessible. + std::string str; + for (std::set<std::string>::iterator i = theme_keys.begin(); + i != theme_keys.end(); ++i) { + EXPECT_TRUE(manifest->GetString(*i, &str)); + EXPECT_EQ(default_value_, str) << *i; + } + EXPECT_TRUE(manifest->GetString(theme_key, &str)); + EXPECT_EQ(default_value_, str) << theme_key; + + // And that all the other keys fail validation and are filtered out + TestRestrictedKeys(manifest.get(), kNotThemeKeys, + ARRAYSIZE_UNSAFE(kNotThemeKeys)); +}; + +// Verifies that platform apps can access the right keys. +TEST_F(ManifestTest, PlatformApp) { + std::set<std::string> platform_keys = GetAccessibleKeys( + kNotPlatformAppKeys, + ARRAYSIZE_UNSAFE(kNotPlatformAppKeys)); + + DictionaryValue* value = new DictionaryValue(); + for (std::set<std::string>::iterator i = platform_keys.begin(); + i != platform_keys.end(); ++i) + value->Set(*i, Value::CreateStringValue(default_value_)); + + value->Set(keys::kPlatformApp, Value::CreateBooleanValue(true)); + + scoped_ptr<Manifest> manifest(new Manifest(value)); + std::string error; + EXPECT_TRUE(manifest->ValidateManifest(&error)); + EXPECT_EQ("", error); + AssertType(manifest.get(), Manifest::kTypePlatformApp); + + // Verify that all the platform app keys are accessible. + std::string str; + for (std::set<std::string>::iterator i = platform_keys.begin(); + i != platform_keys.end(); ++i) { + EXPECT_TRUE(manifest->GetString(*i, &str)); + EXPECT_EQ(default_value_, str) << *i; + } + bool is_platform_app = false; + EXPECT_TRUE(manifest->GetBoolean(keys::kPlatformApp, &is_platform_app)); + EXPECT_TRUE(is_platform_app) << keys::kPlatformApp; + + // And that all the other keys fail validation and are filtered out. + TestRestrictedKeys(manifest.get(), kNotPlatformAppKeys, + ARRAYSIZE_UNSAFE(kNotPlatformAppKeys)); +}; + +// Verifies that hosted apps can access the right keys. +TEST_F(ManifestTest, HostedApp) { + std::set<std::string> keys = GetAccessibleKeys( + kNotHostedAppKeys, + ARRAYSIZE_UNSAFE(kNotHostedAppKeys)); + + DictionaryValue* value = new DictionaryValue(); + for (std::set<std::string>::iterator i = keys.begin(); + i != keys.end(); ++i) + value->Set(*i, Value::CreateStringValue(default_value_)); + + value->Set(keys::kWebURLs, Value::CreateStringValue(default_value_)); + + scoped_ptr<Manifest> manifest(new Manifest(value)); + std::string error; + EXPECT_TRUE(manifest->ValidateManifest(&error)); + EXPECT_EQ("", error); + AssertType(manifest.get(), Manifest::kTypeHostedApp); + + // Verify that all the hosted app keys are accessible. + std::string str; + for (std::set<std::string>::iterator i = keys.begin(); + i != keys.end(); ++i) { + EXPECT_TRUE(manifest->GetString(*i, &str)); + EXPECT_EQ(default_value_, str) << *i; + } + EXPECT_TRUE(manifest->GetString(keys::kWebURLs, &str)); + EXPECT_EQ(default_value_, str) << keys::kWebURLs; + + // And that all the other keys fail validation and are filtered out. + TestRestrictedKeys(manifest.get(), kNotHostedAppKeys, + ARRAYSIZE_UNSAFE(kNotHostedAppKeys)); +}; + +// Verifies that packaged apps can access the right keys. +TEST_F(ManifestTest, PackagedApp) { + std::set<std::string> keys = GetAccessibleKeys( + kNotPackagedAppKeys, + ARRAYSIZE_UNSAFE(kNotPackagedAppKeys)); + + DictionaryValue* value = new DictionaryValue(); + for (std::set<std::string>::iterator i = keys.begin(); + i != keys.end(); ++i) + value->Set(*i, Value::CreateStringValue(default_value_)); + value->Set(keys::kApp, Value::CreateStringValue(default_value_)); + + scoped_ptr<Manifest> manifest(new Manifest(value)); + std::string error; + EXPECT_TRUE(manifest->ValidateManifest(&error)); + EXPECT_EQ("", error); + AssertType(manifest.get(), Manifest::kTypePackagedApp); + + // Verify that all the packaged app keys are accessible. + std::string str; + for (std::set<std::string>::iterator i = keys.begin(); + i != keys.end(); ++i) { + EXPECT_TRUE(manifest->GetString(*i, &str)); + EXPECT_EQ(default_value_, str) << *i; + } + EXPECT_TRUE(manifest->GetString(keys::kApp, &str)); + EXPECT_EQ(default_value_, str) << keys::kApp; + + // And that all the other keys fail validation and are filtered out. + TestRestrictedKeys(manifest.get(), kNotPackagedAppKeys, + ARRAYSIZE_UNSAFE(kNotPackagedAppKeys)); +}; + +// Verifies that the various getters filter unknown and restricted keys. +TEST_F(ManifestTest, Getters) { + DictionaryValue* value = new DictionaryValue(); + scoped_ptr<Manifest> manifest(new Manifest(value)); + std::string unknown_key = "asdfaskldjf"; + + // Verify that the key filtering works for each of the getters. + // Get and GetBoolean + bool expected_bool = true, actual_bool = false; + value->Set(unknown_key, Value::CreateBooleanValue(expected_bool)); + EXPECT_FALSE(manifest->HasKey(unknown_key)); + EXPECT_FALSE(manifest->GetBoolean(unknown_key, &actual_bool)); + EXPECT_FALSE(actual_bool); + Value* actual_value = NULL; + EXPECT_FALSE(manifest->Get(unknown_key, &actual_value)); + EXPECT_TRUE(value->Remove(unknown_key, NULL)); + + // GetInteger + int expected_int = 5, actual_int = 0; + value->Set(unknown_key, Value::CreateIntegerValue(expected_int)); + EXPECT_FALSE(manifest->GetInteger(unknown_key, &actual_int)); + EXPECT_NE(expected_int, actual_int); + EXPECT_TRUE(value->Remove(unknown_key, NULL)); + + // GetString + std::string expected_str = "hello", actual_str; + value->Set(unknown_key, Value::CreateStringValue(expected_str)); + EXPECT_FALSE(manifest->GetString(unknown_key, &actual_str)); + EXPECT_NE(expected_str, actual_str); + EXPECT_TRUE(value->Remove(unknown_key, NULL)); + + // GetString (string16) + string16 expected_str16(UTF8ToUTF16("hello")), actual_str16; + value->Set(unknown_key, Value::CreateStringValue(expected_str16)); + EXPECT_FALSE(manifest->GetString(unknown_key, &actual_str16)); + EXPECT_NE(expected_str16, actual_str16); + EXPECT_TRUE(value->Remove(unknown_key, NULL)); + + // GetDictionary + DictionaryValue* expected_dict = new DictionaryValue(); + DictionaryValue* actual_dict = NULL; + expected_dict->Set("foo", Value::CreateStringValue("bar")); + value->Set(unknown_key, expected_dict); + EXPECT_FALSE(manifest->GetDictionary(unknown_key, &actual_dict)); + EXPECT_EQ(NULL, actual_dict); + std::string path = unknown_key + ".foo"; + EXPECT_FALSE(manifest->GetString(path, &actual_str)); + EXPECT_NE("bar", actual_str); + EXPECT_TRUE(value->Remove(unknown_key, NULL)); + + // GetList + ListValue* expected_list = new ListValue(); + ListValue* actual_list = NULL; + expected_list->Append(Value::CreateStringValue("blah")); + value->Set(unknown_key, expected_list); + EXPECT_FALSE(manifest->GetList(unknown_key, &actual_list)); + EXPECT_EQ(NULL, actual_list); + EXPECT_TRUE(value->Remove(unknown_key, NULL)); +} + +} // namespace extensions diff --git a/chrome/renderer/extensions/app_bindings.cc b/chrome/renderer/extensions/app_bindings.cc index 48893eb..2b3faab 100644 --- a/chrome/renderer/extensions/app_bindings.cc +++ b/chrome/renderer/extensions/app_bindings.cc @@ -12,6 +12,7 @@ #include "chrome/common/chrome_switches.h" #include "chrome/common/extensions/extension_messages.h" #include "chrome/common/extensions/extension_set.h" +#include "chrome/common/extensions/manifest.h" #include "chrome/renderer/extensions/chrome_v8_context.h" #include "chrome/renderer/extensions/extension_dispatcher.h" #include "chrome/renderer/extensions/extension_helper.h" @@ -194,7 +195,7 @@ v8::Handle<v8::Value> AppBindingsHandler::GetDetailsForFrameImpl( return v8::Null(); scoped_ptr<DictionaryValue> manifest_copy( - extension->manifest_value()->DeepCopy()); + extension->manifest()->value()->DeepCopy()); manifest_copy->SetString("id", extension->id()); scoped_ptr<V8ValueConverter> converter(V8ValueConverter::create()); return converter->ToV8Value(manifest_copy.get(), diff --git a/chrome/test/data/extensions/manifest_tests/disallow_hybrid_2.json b/chrome/test/data/extensions/manifest_tests/background_permission.json index 06be2c3..06be2c3 100644 --- a/chrome/test/data/extensions/manifest_tests/disallow_hybrid_2.json +++ b/chrome/test/data/extensions/manifest_tests/background_permission.json diff --git a/chrome/test/data/extensions/manifest_tests/disallow_hybrid_1.json b/chrome/test/data/extensions/manifest_tests/disallow_hybrid_1.json deleted file mode 100644 index 3ec899f..0000000 --- a/chrome/test/data/extensions/manifest_tests/disallow_hybrid_1.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "test", - "version": "1", - "app": { - "urls": [ - "http://www.google.com/mail/", - "http://www.google.com/foobar/" - ], - "launch": { - "web_url": "http://www.google.com/mail/" - } - }, - "permissions": [ - "notifications" - ], - "browser_action": {} -} diff --git a/chrome/test/data/extensions/manifest_tests/multiple_ui_surfaces_1.json b/chrome/test/data/extensions/manifest_tests/multiple_ui_surfaces.json index ecc86eb..ecc86eb 100644 --- a/chrome/test/data/extensions/manifest_tests/multiple_ui_surfaces_1.json +++ b/chrome/test/data/extensions/manifest_tests/multiple_ui_surfaces.json diff --git a/chrome/test/data/extensions/manifest_tests/multiple_ui_surfaces_2.json b/chrome/test/data/extensions/manifest_tests/multiple_ui_surfaces_2.json deleted file mode 100644 index 13c798b..0000000 --- a/chrome/test/data/extensions/manifest_tests/multiple_ui_surfaces_2.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "foo", - "version": "1", - "browser_action": {}, - "app": { - "launch": { - "local_path": "foo.html" - } - } -} diff --git a/chrome/test/data/extensions/manifest_tests/multiple_ui_surfaces_3.json b/chrome/test/data/extensions/manifest_tests/multiple_ui_surfaces_3.json deleted file mode 100644 index 2317026..0000000 --- a/chrome/test/data/extensions/manifest_tests/multiple_ui_surfaces_3.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "foo", - "version": "1", - "browser_action": {}, - "page_action": {}, - "app": { - "launch": { - "local_path": "foo.html" - } - } -} |