// 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/extension.h"
#include <algorithm>
#include "base/base64.h"
#include "base/basictypes.h"
#include "base/command_line.h"
#include "base/file_path.h"
#include "base/file_util.h"
#include "base/i18n/rtl.h"
#include "base/logging.h"
#include "base/memory/singleton.h"
#include "base/stl_util-inl.h"
#include "base/string16.h"
#include "base/string_number_conversions.h"
#include "base/utf_string_conversions.h"
#include "base/values.h"
#include "base/version.h"
#include "crypto/sha2.h"
#include "crypto/third_party/nss/blapi.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/chrome_version_info.h"
#include "chrome/common/extensions/extension_action.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/common/extensions/extension_error_utils.h"
#include "chrome/common/extensions/extension_l10n_util.h"
#include "chrome/common/extensions/extension_resource.h"
#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/user_script.h"
#include "chrome/common/url_constants.h"
#include "googleurl/src/url_util.h"
#include "grit/chromium_strings.h"
#include "grit/generated_resources.h"
#include "grit/theme_resources.h"
#include "net/base/registry_controlled_domain.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "webkit/glue/image_decoder.h"
namespace keys = extension_manifest_keys;
namespace values = extension_manifest_values;
namespace errors = extension_manifest_errors;
namespace {
const int kPEMOutputColumns = 65;
// KEY MARKERS
const char kKeyBeginHeaderMarker[] = "-----BEGIN";
const char kKeyBeginFooterMarker[] = "-----END";
const char kKeyInfoEndMarker[] = "KEY-----";
const char kPublic[] = "PUBLIC";
const char kPrivate[] = "PRIVATE";
const int kRSAKeySize = 1024;
// Converts a normal hexadecimal string into the alphabet used by extensions.
// We use the characters 'a'-'p' instead of '0'-'f' to avoid ever having a
// completely numeric host, since some software interprets that as an IP
// address.
static void ConvertHexadecimalToIDAlphabet(std::string* id) {
for (size_t i = 0; i < id->size(); ++i) {
int val;
if (base::HexStringToInt(id->begin() + i, id->begin() + i + 1, &val))
(*id)[i] = val + 'a';
else
(*id)[i] = 'a';
}
}
// 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::kName,
keys::kPublicKey,
keys::kSignature,
keys::kVersion,
keys::kUpdateURL
};
bool IsBaseCrxKey(const std::string& key) {
for (size_t i = 0; i < arraysize(kBaseCrxKeys); ++i) {
if (key == kBaseCrxKeys[i])
return true;
}
return false;
}
// Constant used to represent an undefined l10n message id.
const int kUndefinedMessageId = -1;
// Names of API modules that do not require a permission.
const char kBrowserActionModuleName[] = "browserAction";
const char kBrowserActionsModuleName[] = "browserActions";
const char kDevToolsModuleName[] = "devtools";
const char kExtensionModuleName[] = "extension";
const char kI18NModuleName[] = "i18n";
const char kOmniboxModuleName[] = "omnibox";
const char kPageActionModuleName[] = "pageAction";
const char kPageActionsModuleName[] = "pageActions";
const char kTestModuleName[] = "test";
// Names of modules that can be used without listing it in the permissions
// section of the manifest.
const char* kNonPermissionModuleNames[] = {
kBrowserActionModuleName,
kBrowserActionsModuleName,
kDevToolsModuleName,
kExtensionModuleName,
kI18NModuleName,
kOmniboxModuleName,
kPageActionModuleName,
kPageActionsModuleName,
kTestModuleName
};
const size_t kNumNonPermissionModuleNames =
arraysize(kNonPermissionModuleNames);
// Names of functions (within modules requiring permissions) that can be used
// without asking for the module permission. In other words, functions you can
// use with no permissions specified.
const char* kNonPermissionFunctionNames[] = {
"tabs.create",
"tabs.update"
};
const size_t kNumNonPermissionFunctionNames =
arraysize(kNonPermissionFunctionNames);
// A singleton object containing global data needed by the extension objects.
class ExtensionConfig {
public:
static ExtensionConfig* GetInstance() {
return Singleton<ExtensionConfig>::get();
}
Extension::PermissionMessage::MessageId GetPermissionMessageId(
const std::string& permission) {
return Extension::kPermissions[permission_map_[permission]].message_id;
}
Extension::ScriptingWhitelist* whitelist() { return &scripting_whitelist_; }
private:
friend struct DefaultSingletonTraits<ExtensionConfig>;
ExtensionConfig() {
for (size_t i = 0; i < Extension::kNumPermissions; ++i)
permission_map_[Extension::kPermissions[i].name] = i;
};
~ExtensionConfig() { }
std::map<const std::string, size_t> permission_map_;
// A whitelist of extensions that can script anywhere. Do not add to this
// list (except in tests) without consulting the Extensions team first.
// Note: Component extensions have this right implicitly and do not need to be
// added to this list.
Extension::ScriptingWhitelist scripting_whitelist_;
};
// Aliased to kTabPermission for purposes of API checks, but not allowed
// in the permissions field of the manifest.
static const char kWindowPermission[] = "windows";
// Rank extension locations in a way that allows
// Extension::GetHigherPriorityLocation() to compare locations.
// An extension installed from two locations will have the location
// with the higher rank, as returned by this function. The actual
// integer values may change, and should never be persisted.
int GetLocationRank(Extension::Location location) {
const int kInvalidRank = -1;
int rank = kInvalidRank; // Will CHECK that rank is not kInvalidRank.
switch (location) {
// Component extensions can not be overriden by any other type.
case Extension::COMPONENT:
rank = 6;
break;
// Policy controlled extensions may not be overridden by any type
// that is not part of chrome.
case Extension::EXTERNAL_POLICY_DOWNLOAD:
rank = 5;
break;
// A developer-loaded extension should override any installed type
// that a user can disable.
case Extension::LOAD:
rank = 4;
break;
// The relative priority of various external sources is not important,
// but having some order ensures deterministic behavior.
case Extension::EXTERNAL_REGISTRY:
rank = 3;
break;
case Extension::EXTERNAL_PREF:
rank = 2;
break;
case Extension::EXTERNAL_PREF_DOWNLOAD:
rank = 1;
break;
// User installed extensions are overridden by any external type.
case Extension::INTERNAL:
rank = 0;
break;
default:
NOTREACHED() << "Need to add new extension locaton " << location;
}
CHECK(rank != kInvalidRank);
return rank;
}
} // namespace
const FilePath::CharType Extension::kManifestFilename[] =
FILE_PATH_LITERAL("manifest.json");
const FilePath::CharType Extension::kLocaleFolder[] =
FILE_PATH_LITERAL("_locales");
const FilePath::CharType Extension::kMessagesFilename[] =
FILE_PATH_LITERAL("messages.json");
#if defined(OS_WIN)
const char Extension::kExtensionRegistryPath[] =
"Software\\Google\\Chrome\\Extensions";
#endif
// first 16 bytes of SHA256 hashed public key.
const size_t Extension::kIdSize = 16;
const char Extension::kMimeType[] = "application/x-chrome-extension";
const int Extension::kIconSizes[] = {
EXTENSION_ICON_LARGE,
EXTENSION_ICON_MEDIUM,
EXTENSION_ICON_SMALL,
EXTENSION_ICON_SMALLISH,
EXTENSION_ICON_BITTY
};
const int Extension::kPageActionIconMaxSize = 19;
const int Extension::kBrowserActionIconMaxSize = 19;
const int Extension::kSidebarIconMaxSize = 16;
// Explicit permissions -- permission declaration required.
const char Extension::kBackgroundPermission[] = "background";
const char Extension::kBookmarkPermission[] = "bookmarks";
const char Extension::kContextMenusPermission[] = "contextMenus";
const char Extension::kContentSettingsPermission[] = "contentSettings";
const char Extension::kCookiePermission[] = "cookies";
const char Extension::kChromeosInfoPrivatePermissions[] = "chromeosInfoPrivate";
const char Extension::kDebuggerPermission[] = "debugger";
const char Extension::kExperimentalPermission[] = "experimental";
const char Extension::kFileBrowserHandlerPermission[] = "fileBrowserHandler";
const char Extension::kFileBrowserPrivatePermission[] = "fileBrowserPrivate";
const char Extension::kGeolocationPermission[] = "geolocation";
const char Extension::kHistoryPermission[] = "history";
const char Extension::kIdlePermission[] = "idle";
const char Extension::kManagementPermission[] = "management";
const char Extension::kNotificationPermission[] = "notifications";
const char Extension::kProxyPermission[] = "proxy";
const char Extension::kTabPermission[] = "tabs";
const char Extension::kUnlimitedStoragePermission[] = "unlimitedStorage";
const char Extension::kWebstorePrivatePermission[] = "webstorePrivate";
// In general, all permissions should have an install message.
// See ExtensionsTest.PermissionMessages for an explanation of each
// exception.
const Extension::Permission Extension::kPermissions[] = {
{ kBackgroundPermission, PermissionMessage::ID_NONE },
{ kBookmarkPermission, PermissionMessage::ID_BOOKMARKS },
{ kChromeosInfoPrivatePermissions, PermissionMessage::ID_NONE },
{ kContentSettingsPermission, PermissionMessage::ID_NONE },
{ kContextMenusPermission, PermissionMessage::ID_NONE },
{ kCookiePermission, PermissionMessage::ID_NONE },
{ kDebuggerPermission, PermissionMessage::ID_DEBUGGER },
{ kExperimentalPermission, PermissionMessage::ID_NONE },
{ kFileBrowserHandlerPermission, PermissionMessage::ID_NONE },
{ kFileBrowserPrivatePermission, PermissionMessage::ID_NONE },
{ kGeolocationPermission, PermissionMessage::ID_GEOLOCATION },
{ kIdlePermission, PermissionMessage::ID_NONE },
{ kHistoryPermission, PermissionMessage::ID_BROWSING_HISTORY },
{ kManagementPermission, PermissionMessage::ID_MANAGEMENT },
{ kNotificationPermission, PermissionMessage::ID_NONE },
{ kProxyPermission, PermissionMessage::ID_NONE },
{ kTabPermission, PermissionMessage::ID_TABS },
{ kUnlimitedStoragePermission, PermissionMessage::ID_NONE },
{ kWebstorePrivatePermission, PermissionMessage::ID_NONE }
};
const size_t Extension::kNumPermissions =
arraysize(Extension::kPermissions);
const char* const Extension::kHostedAppPermissionNames[] = {
Extension::kBackgroundPermission,
Extension::kGeolocationPermission,
Extension::kNotificationPermission,
Extension::kUnlimitedStoragePermission,
Extension::kWebstorePrivatePermission,
};
const size_t Extension::kNumHostedAppPermissions =
arraysize(Extension::kHostedAppPermissionNames);
const char* const Extension::kComponentPrivatePermissionNames[] = {
Extension::kFileBrowserPrivatePermission,
Extension::kWebstorePrivatePermission,
Extension::kChromeosInfoPrivatePermissions,
};
const size_t Extension::kNumComponentPrivatePermissions =
arraysize(Extension::kComponentPrivatePermissionNames);
// We purposefully don't put this into kPermissionNames.
const char Extension::kOldUnlimitedStoragePermission[] = "unlimited_storage";
const int Extension::kValidWebExtentSchemes =
URLPattern::SCHEME_HTTP | URLPattern::SCHEME_HTTPS;
const int Extension::kValidHostPermissionSchemes =
UserScript::kValidUserScriptSchemes | URLPattern::SCHEME_CHROMEUI;
//
// PermissionMessage
//
// static
Extension::PermissionMessage Extension::PermissionMessage::CreateFromMessageId(
Extension::PermissionMessage::MessageId message_id) {
DCHECK_GT(PermissionMessage::ID_NONE, PermissionMessage::ID_UNKNOWN);
if (message_id <= ID_NONE)
return PermissionMessage(message_id, string16());
string16 message = l10n_util::GetStringUTF16(kMessageIds[message_id]);
return PermissionMessage(message_id, message);
}
// static
Extension::PermissionMessage Extension::PermissionMessage::CreateFromHostList(
const std::vector<std::string> hosts) {
CHECK(hosts.size() > 0);
MessageId message_id;
string16 message;
switch (hosts.size()) {
case 1:
message_id = ID_HOSTS_1;
message = l10n_util::GetStringFUTF16(kMessageIds[message_id],
UTF8ToUTF16(hosts[0]));
break;
case 2:
message_id = ID_HOSTS_2;
message = l10n_util::GetStringFUTF16(kMessageIds[message_id],
UTF8ToUTF16(hosts[0]),
UTF8ToUTF16(hosts[1]));
break;
case 3:
message_id = ID_HOSTS_3;
message = l10n_util::GetStringFUTF16(kMessageIds[message_id],
UTF8ToUTF16(hosts[0]),
UTF8ToUTF16(hosts[1]),
UTF8ToUTF16(hosts[2]));
break;
default:
message_id = ID_HOSTS_4_OR_MORE;
message = l10n_util::GetStringFUTF16(
kMessageIds[message_id],
UTF8ToUTF16(hosts[0]),
UTF8ToUTF16(hosts[1]),
base::IntToString16(hosts.size() - 2));
break;
}
return PermissionMessage(message_id, message);
}
Extension::PermissionMessage::PermissionMessage(
Extension::PermissionMessage::MessageId message_id, string16 message)
: message_id_(message_id),
message_(message) {
}
const int Extension::PermissionMessage::kMessageIds[] = {
kUndefinedMessageId, // "unknown"
kUndefinedMessageId, // "none"
IDS_EXTENSION_PROMPT_WARNING_BOOKMARKS,
IDS_EXTENSION_PROMPT_WARNING_GEOLOCATION,
IDS_EXTENSION_PROMPT_WARNING_BROWSING_HISTORY,
IDS_EXTENSION_PROMPT_WARNING_TABS,
IDS_EXTENSION_PROMPT_WARNING_MANAGEMENT,
IDS_EXTENSION_PROMPT_WARNING_DEBUGGER,
IDS_EXTENSION_PROMPT_WARNING_1_HOST,
IDS_EXTENSION_PROMPT_WARNING_2_HOSTS,
IDS_EXTENSION_PROMPT_WARNING_3_HOSTS,
IDS_EXTENSION_PROMPT_WARNING_4_OR_MORE_HOSTS,
IDS_EXTENSION_PROMPT_WARNING_ALL_HOSTS,
IDS_EXTENSION_PROMPT_WARNING_FULL_ACCESS
};
//
// Extension
//
// static
scoped_refptr<Extension> Extension::Create(const FilePath& path,
Location location,
const DictionaryValue& value,
int flags,
std::string* error) {
scoped_refptr<Extension> extension = new Extension(path, location);
if (!extension->InitFromValue(value, flags, error))
return NULL;
return extension;
}
namespace {
const char* kGalleryUpdateHttpUrl =
"http://clients2.google.com/service/update2/crx";
const char* kGalleryUpdateHttpsUrl =
"https://clients2.google.com/service/update2/crx";
} // namespace
// static
GURL Extension::GalleryUpdateUrl(bool secure) {
CommandLine* cmdline = CommandLine::ForCurrentProcess();
if (cmdline->HasSwitch(switches::kAppsGalleryUpdateURL))
return GURL(cmdline->GetSwitchValueASCII(switches::kAppsGalleryUpdateURL));
else
return GURL(secure ? kGalleryUpdateHttpsUrl : kGalleryUpdateHttpUrl);
}
// static
Extension::Location Extension::GetHigherPriorityLocation(
Extension::Location loc1, Extension::Location loc2) {
if (loc1 == loc2)
return loc1;
int loc1_rank = GetLocationRank(loc1);
int loc2_rank = GetLocationRank(loc2);
// If two different locations have the same rank, then we can not
// deterministicly choose a location.
CHECK(loc1_rank != loc2_rank);
// Lowest rank has highest priority.
return (loc1_rank > loc2_rank ? loc1 : loc2 );
}
// static
Extension::PermissionMessage::MessageId Extension::GetPermissionMessageId(
const std::string& permission) {
return ExtensionConfig::GetInstance()->GetPermissionMessageId(permission);
}
Extension::PermissionMessages Extension::GetPermissionMessages() const {
PermissionMessages messages;
if (!plugins().empty()) {
messages.push_back(PermissionMessage::CreateFromMessageId(
PermissionMessage::ID_FULL_ACCESS));
return messages;
}
if (HasEffectiveAccessToAllHosts()) {
messages.push_back(PermissionMessage::CreateFromMessageId(
PermissionMessage::ID_HOSTS_ALL));
} else {
std::vector<std::string> hosts = GetDistinctHostsForDisplay(
GetEffectiveHostPermissions().patterns());
if (!hosts.empty())
messages.push_back(PermissionMessage::CreateFromHostList(hosts));
}
std::set<PermissionMessage> simple_msgs = GetSimplePermissionMessages();
messages.insert(messages.end(), simple_msgs.begin(), simple_msgs.end());
return messages;
}
std::vector<string16> Extension::GetPermissionMessageStrings() const {
std::vector<string16> messages;
PermissionMessages permissions = GetPermissionMessages();
for (PermissionMessages::const_iterator i = permissions.begin();
i != permissions.end(); ++i)
messages.push_back(i->message());
return messages;
}
std::set<Extension::PermissionMessage>
Extension::GetSimplePermissionMessages() const {
std::set<PermissionMessage> messages;
std::set<std::string>::const_iterator i;
for (i = api_permissions().begin(); i != api_permissions().end(); ++i) {
PermissionMessage::MessageId message_id = GetPermissionMessageId(*i);
DCHECK_GT(PermissionMessage::ID_NONE, PermissionMessage::ID_UNKNOWN);
if (message_id > PermissionMessage::ID_NONE)
messages.insert(PermissionMessage::CreateFromMessageId(message_id));
}
return messages;
}
// static
std::vector<std::string> Extension::GetDistinctHostsForDisplay(
const URLPatternList& list) {
return GetDistinctHosts(list, true);
}
// static
bool Extension::IsElevatedHostList(
const URLPatternList& old_list, const URLPatternList& new_list) {
// TODO(jstritar): This is overly conservative with respect to subdomains.
// For example, going from *.google.com to www.google.com will be
// considered an elevation, even though it is not (http://crbug.com/65337).
std::vector<std::string> new_hosts = GetDistinctHosts(new_list, false);
std::vector<std::string> old_hosts = GetDistinctHosts(old_list, false);
std::set<std::string> old_hosts_set(old_hosts.begin(), old_hosts.end());
std::set<std::string> new_hosts_set(new_hosts.begin(), new_hosts.end());
std::set<std::string> new_hosts_only;
std::set_difference(new_hosts_set.begin(), new_hosts_set.end(),
old_hosts_set.begin(), old_hosts_set.end(),
std::inserter(new_hosts_only, new_hosts_only.begin()));
return !new_hosts_only.empty();
}
// Helper for GetDistinctHosts(): com > net > org > everything else.
static bool RcdBetterThan(const std::string& a, const std::string& b) {
if (a == b)
return false;
if (a == "com")
return true;
if (a == "net")
return b != "com";
if (a == "org")
return b != "com" && b != "net";
return false;
}
// static
std::vector<std::string> Extension::GetDistinctHosts(
const URLPatternList& host_patterns, bool include_rcd) {
// Use a vector to preserve order (also faster than a map on small sets).
// Each item is a host split into two parts: host without RCDs and
// current best RCD.
typedef std::vector<std::pair<std::string, std::string> > HostVector;
HostVector hosts_best_rcd;
for (size_t i = 0; i < host_patterns.size(); ++i) {
std::string host = host_patterns[i].host();
// Add the subdomain wildcard back to the host, if necessary.
if (host_patterns[i].match_subdomains())
host = "*." + host;
// If the host has an RCD, split it off so we can detect duplicates.
std::string rcd;
size_t reg_len = net::RegistryControlledDomainService::GetRegistryLength(
host, false);
if (reg_len && reg_len != std::string::npos) {
if (include_rcd) // else leave rcd empty
rcd = host.substr(host.size() - reg_len);
host = host.substr(0, host.size() - reg_len);
}
// Check if we've already seen this host.
HostVector::iterator it = hosts_best_rcd.begin();
for (; it != hosts_best_rcd.end(); ++it) {
if (it->first == host)
break;
}
// If this host was found, replace the RCD if this one is better.
if (it != hosts_best_rcd.end()) {
if (include_rcd && RcdBetterThan(rcd, it->second))
it->second = rcd;
} else { // Previously unseen host, append it.
hosts_best_rcd.push_back(std::make_pair(host, rcd));
}
}
// Build up the final vector by concatenating hosts and RCDs.
std::vector<std::string> distinct_hosts;
for (HostVector::iterator it = hosts_best_rcd.begin();
it != hosts_best_rcd.end(); ++it)
distinct_hosts.push_back(it->first + it->second);
return distinct_hosts;
}
FilePath Extension::MaybeNormalizePath(const FilePath& path) {
#if defined(OS_WIN)
// Normalize any drive letter to upper-case. We do this for consistency with
// net_utils::FilePathToFileURL(), which does the same thing, to make string
// comparisons simpler.
std::wstring path_str = path.value();
if (path_str.size() >= 2 && path_str[0] >= L'a' && path_str[0] <= L'z' &&
path_str[1] == ':')
path_str[0] += ('A' - 'a');
return FilePath(path_str);
#else
return path;
#endif
}
// static
bool Extension::IsHostedAppPermission(const std::string& str) {
for (size_t i = 0; i < Extension::kNumHostedAppPermissions; ++i) {
if (str == Extension::kHostedAppPermissionNames[i]) {
return true;
}
}
return false;
}
const std::string Extension::VersionString() const {
return version()->GetString();
}
// static
bool Extension::IsExtension(const FilePath& file_name) {
return file_name.MatchesExtension(chrome::kExtensionFileExtension);
}
// static
bool Extension::IdIsValid(const std::string& id) {
// Verify that the id is legal.
if (id.size() != (kIdSize * 2))
return false;
// We only support lowercase IDs, because IDs can be used as URL components
// (where GURL will lowercase it).
std::string temp = StringToLowerASCII(id);
for (size_t i = 0; i < temp.size(); i++)
if (temp[i] < 'a' || temp[i] > 'p')
return false;
return true;
}
// static
std::string Extension::GenerateIdForPath(const FilePath& path) {
FilePath new_path = Extension::MaybeNormalizePath(path);
std::string path_bytes =
std::string(reinterpret_cast<const char*>(new_path.value().data()),
new_path.value().size() * sizeof(FilePath::CharType));
std::string id;
if (!GenerateId(path_bytes, &id))
return "";
return id;
}
Extension::Type Extension::GetType() const {
if (is_theme())
return TYPE_THEME;
if (converted_from_user_script())
return TYPE_USER_SCRIPT;
if (is_hosted_app())
return TYPE_HOSTED_APP;
if (is_packaged_app())
return TYPE_PACKAGED_APP;
return TYPE_EXTENSION;
}
// static
GURL Extension::GetResourceURL(const GURL& extension_url,
const std::string& relative_path) {
DCHECK(extension_url.SchemeIs(chrome::kExtensionScheme));
DCHECK_EQ("/", extension_url.path());
GURL ret_val = GURL(extension_url.spec() + relative_path);
DCHECK(StartsWithASCII(ret_val.spec(), extension_url.spec(), false));
return ret_val;
}
bool Extension::GenerateId(const std::string& input, std::string* output) {
CHECK(output);
uint8 hash[Extension::kIdSize];
crypto::SHA256HashString(input, hash, sizeof(hash));
*output = StringToLowerASCII(base::HexEncode(hash, sizeof(hash)));
ConvertHexadecimalToIDAlphabet(output);
return true;
}
// Helper method that loads a UserScript object from a dictionary in the
// content_script list of the manifest.
bool Extension::LoadUserScriptHelper(const DictionaryValue* content_script,
int definition_index,
int flags,
std::string* error,
UserScript* result) {
// When strict error checks are enabled, make URL pattern parsing strict.
URLPattern::ParseOption parse_strictness =
(flags & STRICT_ERROR_CHECKS ? URLPattern::PARSE_STRICT
: URLPattern::PARSE_LENIENT);
// run_at
if (content_script->HasKey(keys::kRunAt)) {
std::string run_location;
if (!content_script->GetString(keys::kRunAt, &run_location)) {
*error = ExtensionErrorUtils::FormatErrorMessage(errors::kInvalidRunAt,
base::IntToString(definition_index));
return false;
}
if (run_location == values::kRunAtDocumentStart) {
result->set_run_location(UserScript::DOCUMENT_START);
} else if (run_location == values::kRunAtDocumentEnd) {
result->set_run_location(UserScript::DOCUMENT_END);
} else if (run_location == values::kRunAtDocumentIdle) {
result->set_run_location(UserScript::DOCUMENT_IDLE);
} else {
*error = ExtensionErrorUtils::FormatErrorMessage(errors::kInvalidRunAt,
base::IntToString(definition_index));
return false;
}
}
// all frames
if (content_script->HasKey(keys::kAllFrames)) {
bool all_frames = false;
if (!content_script->GetBoolean(keys::kAllFrames, &all_frames)) {
*error = ExtensionErrorUtils::FormatErrorMessage(
errors::kInvalidAllFrames, base::IntToString(definition_index));
return false;
}
result->set_match_all_frames(all_frames);
}
// matches
ListValue* matches = NULL;
if (!content_script->GetList(keys::kMatches, &matches)) {
*error = ExtensionErrorUtils::FormatErrorMessage(errors::kInvalidMatches,
base::IntToString(definition_index));
return false;
}
if (matches->GetSize() == 0) {
*error = ExtensionErrorUtils::FormatErrorMessage(errors::kInvalidMatchCount,
base::IntToString(definition_index));
return false;
}
for (size_t j = 0; j < matches->GetSize(); ++j) {
std::string match_str;
if (!matches->GetString(j, &match_str)) {
*error = ExtensionErrorUtils::FormatErrorMessage(
errors::kInvalidMatch,
base::IntToString(definition_index),
base::IntToString(j),
errors::kExpectString);
return false;
}
URLPattern pattern(UserScript::kValidUserScriptSchemes);
if (CanExecuteScriptEverywhere())
pattern.set_valid_schemes(URLPattern::SCHEME_ALL);
URLPattern::ParseResult parse_result = pattern.Parse(match_str,
parse_strictness);
if (parse_result != URLPattern::PARSE_SUCCESS) {
*error = ExtensionErrorUtils::FormatErrorMessage(
errors::kInvalidMatch,
base::IntToString(definition_index),
base::IntToString(j),
URLPattern::GetParseResultString(parse_result));
return false;
}
if (pattern.MatchesScheme(chrome::kFileScheme) &&
!CanExecuteScriptEverywhere()) {
wants_file_access_ = true;
if (!(flags & ALLOW_FILE_ACCESS))
pattern.set_valid_schemes(
pattern.valid_schemes() & ~URLPattern::SCHEME_FILE);
}
result->add_url_pattern(pattern);
}
// include/exclude globs (mostly for Greasemonkey compatibility)
if (!LoadGlobsHelper(content_script, definition_index, keys::kIncludeGlobs,
error, &UserScript::add_glob, result)) {
return false;
}
if (!LoadGlobsHelper(content_script, definition_index, keys::kExcludeGlobs,
error, &UserScript::add_exclude_glob, result)) {
return false;
}
// js and css keys
ListValue* js = NULL;
if (content_script->HasKey(keys::kJs) &&
!content_script->GetList(keys::kJs, &js)) {
*error = ExtensionErrorUtils::FormatErrorMessage(errors::kInvalidJsList,
base::IntToString(definition_index));
return false;
}
ListValue* css = NULL;
if (content_script->HasKey(keys::kCss) &&
!content_script->GetList(keys::kCss, &css)) {
*error = ExtensionErrorUtils::FormatErrorMessage(errors::kInvalidCssList,
base::IntToString(definition_index));
return false;
}
// The manifest needs to have at least one js or css user script definition.
if (((js ? js->GetSize() : 0) + (css ? css->GetSize() : 0)) == 0) {
*error = ExtensionErrorUtils::FormatErrorMessage(errors::kMissingFile,
base::IntToString(definition_index));
return false;
}
if (js) {
for (size_t script_index = 0; script_index < js->GetSize();
++script_index) {
Value* value;
std::string relative;
if (!js->Get(script_index, &value) || !value->GetAsString(&relative)) {
*error = ExtensionErrorUtils::FormatErrorMessage(errors::kInvalidJs,
base::IntToString(definition_index),
base::IntToString(script_index));
return false;
}
GURL url = GetResourceURL(relative);
ExtensionResource resource = GetResource(relative);
result->js_scripts().push_back(UserScript::File(
resource.extension_root(), resource.relative_path(), url));
}
}
if (css) {
for (size_t script_index = 0; script_index < css->GetSize();
++script_index) {
Value* value;
std::string relative;
if (!css->Get(script_index, &value) || !value->GetAsString(&relative)) {
*error = ExtensionErrorUtils::FormatErrorMessage(errors::kInvalidCss,
base::IntToString(definition_index),
base::IntToString(script_index));
return false;
}
GURL url = GetResourceURL(relative);
ExtensionResource resource = GetResource(relative);
result->css_scripts().push_back(UserScript::File(
resource.extension_root(), resource.relative_path(), url));
}
}
return true;
}
bool Extension::LoadGlobsHelper(
const DictionaryValue* content_script,
int content_script_index,
const char* globs_property_name,
std::string* error,
void(UserScript::*add_method)(const std::string& glob),
UserScript *instance) {
if (!content_script->HasKey(globs_property_name))
return true; // they are optional
ListValue* list = NULL;
if (!content_script->GetList(globs_property_name, &list)) {
*error = ExtensionErrorUtils::FormatErrorMessage(errors::kInvalidGlobList,
base::IntToString(content_script_index),
globs_property_name);
return false;
}
for (size_t i = 0; i < list->GetSize(); ++i) {
std::string glob;
if (!list->GetString(i, &glob)) {
*error = ExtensionErrorUtils::FormatErrorMessage(errors::kInvalidGlob,
base::IntToString(content_script_index),
globs_property_name,
base::IntToString(i));
return false;
}
(instance->*add_method)(glob);
}
return true;
}
ExtensionAction* Extension::LoadExtensionActionHelper(
const DictionaryValue* extension_action, std::string* error) {
scoped_ptr<ExtensionAction> result(new ExtensionAction());
result->set_extension_id(id());
// Page actions are hidden by default, and browser actions ignore
// visibility.
result->SetIsVisible(ExtensionAction::kDefaultTabId, false);
// TODO(EXTENSIONS_DEPRECATED): icons list is obsolete.
ListValue* icons = NULL;
if (extension_action->HasKey(keys::kPageActionIcons) &&
extension_action->GetList(keys::kPageActionIcons, &icons)) {
for (ListValue::const_iterator iter = icons->begin();
iter != icons->end(); ++iter) {
std::string path;
if (!(*iter)->GetAsString(&path) || path.empty()) {
*error = errors::kInvalidPageActionIconPath;
return NULL;
}
result->icon_paths()->push_back(path);
}
}
// TODO(EXTENSIONS_DEPRECATED): Read the page action |id| (optional).
std::string id;
if (extension_action->HasKey(keys::kPageActionId)) {
if (!extension_action->GetString(keys::kPageActionId, &id)) {
*error = errors::kInvalidPageActionId;
return NULL;
}
result->set_id(id);
}
std::string default_icon;
// Read the page action |default_icon| (optional).
if (extension_action->HasKey(keys::kPageActionDefaultIcon)) {
if (!extension_action->GetString(keys::kPageActionDefaultIcon,
&default_icon) ||
default_icon.empty()) {
*error = errors::kInvalidPageActionIconPath;
return NULL;
}
result->set_default_icon_path(default_icon);
}
// Read the page action title from |default_title| if present, |name| if not
// (both optional).
std::string title;
if (extension_action->HasKey(keys::kPageActionDefaultTitle)) {
if (!extension_action->GetString(keys::kPageActionDefaultTitle, &title)) {
*error = errors::kInvalidPageActionDefaultTitle;
return NULL;
}
} else if (extension_action->HasKey(keys::kName)) {
if (!extension_action->GetString(keys::kName, &title)) {
*error = errors::kInvalidPageActionName;
return NULL;
}
}
result->SetTitle(ExtensionAction::kDefaultTabId, title);
// Read the action's |popup| (optional).
const char* popup_key = NULL;
if (extension_action->HasKey(keys::kPageActionDefaultPopup))
popup_key = keys::kPageActionDefaultPopup;
// For backward compatibility, alias old key "popup" to new
// key "default_popup".
if (extension_action->HasKey(keys::kPageActionPopup)) {
if (popup_key) {
*error = ExtensionErrorUtils::FormatErrorMessage(
errors::kInvalidPageActionOldAndNewKeys,
keys::kPageActionDefaultPopup,
keys::kPageActionPopup);
return NULL;
}
popup_key = keys::kPageActionPopup;
}
if (popup_key) {
DictionaryValue* popup = NULL;
std::string url_str;
if (extension_action->GetString(popup_key, &url_str)) {
// On success, |url_str| is set. Nothing else to do.
} else if (extension_action->GetDictionary(popup_key, &popup)) {
// TODO(EXTENSIONS_DEPRECATED): popup is now a string only.
// Support the old dictionary format for backward compatibility.
if (!popup->GetString(keys::kPageActionPopupPath, &url_str)) {
*error = ExtensionErrorUtils::FormatErrorMessage(
errors::kInvalidPageActionPopupPath, "<missing>");
return NULL;
}
} else {
*error = errors::kInvalidPageActionPopup;
return NULL;
}
if (!url_str.empty()) {
// An empty string is treated as having no popup.
GURL url = GetResourceURL(url_str);
if (!url.is_valid()) {
*error = ExtensionErrorUtils::FormatErrorMessage(
errors::kInvalidPageActionPopupPath, url_str);
return NULL;
}
result->SetPopupUrl(ExtensionAction::kDefaultTabId, url);
} else {
DCHECK(!result->HasPopup(ExtensionAction::kDefaultTabId))
<< "Shouldn't be posible for the popup to be set.";
}
}
return result.release();
}
Extension::FileBrowserHandlerList* Extension::LoadFileBrowserHandlers(
const ListValue* extension_actions, std::string* error) {
scoped_ptr<FileBrowserHandlerList> result(
new FileBrowserHandlerList());
for (ListValue::const_iterator iter = extension_actions->begin();
iter != extension_actions->end();
++iter) {
if (!(*iter)->IsType(Value::TYPE_DICTIONARY)) {
*error = errors::kInvalidFileBrowserHandler;
return NULL;
}
scoped_ptr<FileBrowserHandler> action(
LoadFileBrowserHandler(
reinterpret_cast<DictionaryValue*>(*iter), error));
if (!action.get())
return NULL; // Failed to parse file browser action definition.
result->push_back(linked_ptr<FileBrowserHandler>(action.release()));
}
return result.release();
}
FileBrowserHandler* Extension::LoadFileBrowserHandler(
const DictionaryValue* file_browser_handler, std::string* error) {
scoped_ptr<FileBrowserHandler> result(
new FileBrowserHandler());
result->set_extension_id(id());
std::string id;
// Read the file action |id| (mandatory).
if (!file_browser_handler->HasKey(keys::kPageActionId) ||
!file_browser_handler->GetString(keys::kPageActionId, &id)) {
*error = errors::kInvalidPageActionId;
return NULL;
}
result->set_id(id);
// Read the page action title from |default_title| (mandatory).
std::string title;
if (!file_browser_handler->HasKey(keys::kPageActionDefaultTitle) ||
!file_browser_handler->GetString(keys::kPageActionDefaultTitle, &title)) {
*error = errors::kInvalidPageActionDefaultTitle;
return NULL;
}
result->set_title(title);
// Initialize file filters (mandatory).
ListValue* list_value = NULL;
if (!file_browser_handler->HasKey(keys::kFileFilters) ||
!file_browser_handler->GetList(keys::kFileFilters, &list_value) ||
list_value->empty()) {
*error = errors::kInvalidFileFiltersList;
return NULL;
}
for (size_t i = 0; i < list_value->GetSize(); ++i) {
std::string filter;
if (!list_value->GetString(i, &filter)) {
*error = ExtensionErrorUtils::FormatErrorMessage(
errors::kInvalidFileFilterValue, base::IntToString(i));
return NULL;
}
URLPattern pattern(URLPattern::SCHEME_FILESYSTEM);
if (URLPattern::PARSE_SUCCESS != pattern.Parse(filter,
URLPattern::PARSE_STRICT)) {
*error = ExtensionErrorUtils::FormatErrorMessage(
errors::kInvalidURLPatternError, filter);
return NULL;
}
result->AddPattern(pattern);
}
std::string default_icon;
// Read the file browser action |default_icon| (optional).
if (file_browser_handler->HasKey(keys::kPageActionDefaultIcon)) {
if (!file_browser_handler->GetString(
keys::kPageActionDefaultIcon,&default_icon) ||
default_icon.empty()) {
*error = errors::kInvalidPageActionIconPath;
return NULL;
}
result->set_icon_path(default_icon);
}
return result.release();
}
ExtensionSidebarDefaults* Extension::LoadExtensionSidebarDefaults(
const DictionaryValue* extension_sidebar, std::string* error) {
scoped_ptr<ExtensionSidebarDefaults> result(new ExtensionSidebarDefaults());
std::string default_icon;
// Read sidebar's |default_icon| (optional).
if (extension_sidebar->HasKey(keys::kSidebarDefaultIcon)) {
if (!extension_sidebar->GetString(keys::kSidebarDefaultIcon,
&default_icon) ||
default_icon.empty()) {
*error = errors::kInvalidSidebarDefaultIconPath;
return NULL;
}
result->set_default_icon_path(default_icon);
}
// Read sidebar's |default_title| (optional).
string16 default_title;
if (extension_sidebar->HasKey(keys::kSidebarDefaultTitle)) {
if (!extension_sidebar->GetString(keys::kSidebarDefaultTitle,
&default_title)) {
*error = errors::kInvalidSidebarDefaultTitle;
return NULL;
}
}
result->set_default_title(default_title);
// Read sidebar's |default_page| (optional).
std::string default_page;
if (extension_sidebar->HasKey(keys::kSidebarDefaultPage)) {
if (!extension_sidebar->GetString(keys::kSidebarDefaultPage,
&default_page) ||
default_page.empty()) {
*error = errors::kInvalidSidebarDefaultPage;
return NULL;
}
GURL url = extension_sidebar_utils::ResolveRelativePath(
default_page, this, error);
if (!url.is_valid())
return NULL;
result->set_default_page(url);
}
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;
return true;
}
bool Extension::LoadExtent(const DictionaryValue* manifest,
const char* key,
ExtensionExtent* extent,
const char* list_error,
const char* value_error,
URLPattern::ParseOption parse_strictness,
std::string* error) {
Value* temp = NULL;
if (!manifest->Get(key, &temp))
return true;
if (temp->GetType() != Value::TYPE_LIST) {
*error = list_error;
return false;
}
ListValue* pattern_list = static_cast<ListValue*>(temp);
for (size_t i = 0; i < pattern_list->GetSize(); ++i) {
std::string pattern_string;
if (!pattern_list->GetString(i, &pattern_string)) {
*error = ExtensionErrorUtils::FormatErrorMessage(value_error,
base::UintToString(i),
errors::kExpectString);
return false;
}
URLPattern pattern(kValidWebExtentSchemes);
URLPattern::ParseResult parse_result = pattern.Parse(pattern_string,
parse_strictness);
if (parse_result == URLPattern::PARSE_ERROR_EMPTY_PATH) {
pattern_string += "/";
parse_result = pattern.Parse(pattern_string, parse_strictness);
}
if (parse_result != URLPattern::PARSE_SUCCESS) {
*error = ExtensionErrorUtils::FormatErrorMessage(
value_error,
base::UintToString(i),
URLPattern::GetParseResultString(parse_result));
return false;
}
// Do not allow authors to claim "<all_urls>".
if (pattern.match_all_urls()) {
*error = ExtensionErrorUtils::FormatErrorMessage(
value_error,
base::UintToString(i),
errors::kCannotClaimAllURLsInExtent);
return false;
}
// Do not allow authors to claim "*" for host.
if (pattern.host().empty()) {
*error = ExtensionErrorUtils::FormatErrorMessage(
value_error,
base::UintToString(i),
errors::kCannotClaimAllHostsInExtent);
return false;
}
// We do not allow authors to put wildcards in their paths. Instead, we
// imply one at the end.
if (pattern.path().find('*') != std::string::npos) {
*error = ExtensionErrorUtils::FormatErrorMessage(
value_error,
base::UintToString(i),
errors::kNoWildCardsInPaths);
return false;
}
pattern.SetPath(pattern.path() + '*');
extent->AddPattern(pattern);
}
return true;
}
bool Extension::LoadLaunchURL(const DictionaryValue* manifest,
std::string* error) {
Value* temp = NULL;
// launch URL can be either local (to chrome-extension:// root) or an absolute
// web URL.
if (manifest->Get(keys::kLaunchLocalPath, &temp)) {
if (manifest->Get(keys::kLaunchWebURL, NULL)) {
*error = errors::kLaunchPathAndURLAreExclusive;
return false;
}
std::string launch_path;
if (!temp->GetAsString(&launch_path)) {
*error = errors::kInvalidLaunchLocalPath;
return false;
}
// Ensure the launch path is a valid relative URL.
GURL resolved = url().Resolve(launch_path);
if (!resolved.is_valid() || resolved.GetOrigin() != url()) {
*error = errors::kInvalidLaunchLocalPath;
return false;
}
launch_local_path_ = launch_path;
} else if (manifest->Get(keys::kLaunchWebURL, &temp)) {
std::string launch_url;
if (!temp->GetAsString(&launch_url)) {
*error = errors::kInvalidLaunchWebURL;
return false;
}
// Ensure the launch URL is a valid absolute URL and web extent scheme.
GURL url(launch_url);
URLPattern pattern(kValidWebExtentSchemes);
if (!url.is_valid() || !pattern.SetScheme(url.scheme())) {
*error = errors::kInvalidLaunchWebURL;
return false;
}
launch_web_url_ = launch_url;
} else if (is_app()) {
*error = errors::kLaunchURLRequired;
return false;
}
// If there is no extent, we default the extent based on the launch URL.
if (web_extent().is_empty() && !launch_web_url().empty()) {
GURL launch_url(launch_web_url());
URLPattern pattern(kValidWebExtentSchemes);
if (!pattern.SetScheme("*")) {
*error = errors::kInvalidLaunchWebURL;
return false;
}
pattern.set_host(launch_url.host());
pattern.SetPath("/*");
extent_.AddPattern(pattern);
}
// In order for the --apps-gallery-url switch to work with the gallery
// process isolation, we must insert any provided value into the component
// app's launch url and web extent.
if (id() == extension_misc::kWebStoreAppId) {
std::string gallery_url_str = CommandLine::ForCurrentProcess()->
GetSwitchValueASCII(switches::kAppsGalleryURL);
// Empty string means option was not used.
if (!gallery_url_str.empty()) {
GURL gallery_url(gallery_url_str);
if (!gallery_url.is_valid()) {
LOG(WARNING) << "Invalid url given in switch "
<< switches::kAppsGalleryURL;
} else {
if (gallery_url.has_port()) {
LOG(WARNING) << "URLs passed to switch " << switches::kAppsGalleryURL
<< " should not contain a port. Removing it.";
GURL::Replacements remove_port;
remove_port.ClearPort();
gallery_url = gallery_url.ReplaceComponents(remove_port);
}
launch_web_url_ = gallery_url.spec();
URLPattern pattern(kValidWebExtentSchemes);
pattern.Parse(gallery_url.spec(), URLPattern::PARSE_STRICT);
pattern.SetPath(pattern.path() + '*');
extent_.AddPattern(pattern);
}
}
}
return true;
}
bool Extension::LoadLaunchContainer(const DictionaryValue* manifest,
std::string* error) {
Value* temp = NULL;
if (!manifest->Get(keys::kLaunchContainer, &temp))
return true;
std::string launch_container_string;
if (!temp->GetAsString(&launch_container_string)) {
*error = errors::kInvalidLaunchContainer;
return false;
}
if (launch_container_string == values::kLaunchContainerPanel) {
launch_container_ = extension_misc::LAUNCH_PANEL;
} else if (launch_container_string == values::kLaunchContainerTab) {
launch_container_ = extension_misc::LAUNCH_TAB;
} else {
*error = errors::kInvalidLaunchContainer;
return false;
}
// Validate the container width if present.
if (manifest->Get(keys::kLaunchWidth, &temp)) {
if (launch_container() != extension_misc::LAUNCH_PANEL &&
launch_container() != extension_misc::LAUNCH_WINDOW) {
*error = errors::kInvalidLaunchWidthContainer;
return false;
}
if (!temp->GetAsInteger(&launch_width_) ||
launch_width_ < 0) {
launch_width_ = 0;
*error = errors::kInvalidLaunchWidth;
return false;
}
}
// Validate container height if present.
if (manifest->Get(keys::kLaunchHeight, &temp)) {
if (launch_container() != extension_misc::LAUNCH_PANEL &&
launch_container() != extension_misc::LAUNCH_WINDOW) {
*error = errors::kInvalidLaunchHeightContainer;
return false;
}
if (!temp->GetAsInteger(&launch_height_) || launch_height_ < 0) {
launch_height_ = 0;
*error = errors::kInvalidLaunchHeight;
return false;
}
}
return true;
}
bool Extension::LoadAppIsolation(const DictionaryValue* manifest,
std::string* error) {
// Only parse app isolation features if this switch is present.
if (!CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableExperimentalAppManifests))
return true;
Value* temp = NULL;
if (!manifest->Get(keys::kIsolation, &temp))
return true;
if (temp->GetType() != Value::TYPE_LIST) {
*error = errors::kInvalidIsolation;
return false;
}
ListValue* isolation_list = static_cast<ListValue*>(temp);
for (size_t i = 0; i < isolation_list->GetSize(); ++i) {
std::string isolation_string;
if (!isolation_list->GetString(i, &isolation_string)) {
*error = ExtensionErrorUtils::FormatErrorMessage(
errors::kInvalidIsolationValue,
base::UintToString(i));
return false;
}
// Check for isolated storage.
if (isolation_string == values::kIsolatedStorage) {
is_storage_isolated_ = true;
} else {
LOG(WARNING) << "Did not recognize isolation type: "
<< isolation_string;
}
}
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::kOptionsPage &&
*key != keys::kBackground) {
*error = ExtensionErrorUtils::FormatErrorMessage(
errors::kHostedAppsCannotIncludeExtensionFeatures, *key);
return false;
}
}
return true;
}
Extension::Extension(const FilePath& path, Location location)
: incognito_split_mode_(false),
location_(location),
converted_from_user_script_(false),
is_theme_(false),
is_app_(false),
is_storage_isolated_(false),
launch_container_(extension_misc::LAUNCH_TAB),
launch_width_(0),
launch_height_(0),
wants_file_access_(false) {
DCHECK(path.empty() || path.IsAbsolute());
path_ = MaybeNormalizePath(path);
}
Extension::~Extension() {
}
ExtensionResource Extension::GetResource(
const std::string& relative_path) const {
#if defined(OS_POSIX)
FilePath relative_file_path(relative_path);
#elif defined(OS_WIN)
FilePath relative_file_path(UTF8ToWide(relative_path));
#endif
return ExtensionResource(id(), path(), relative_file_path);
}
ExtensionResource Extension::GetResource(
const FilePath& relative_file_path) const {
return ExtensionResource(id(), path(), relative_file_path);
}
// TODO(rafaelw): Move ParsePEMKeyBytes, ProducePEM & FormatPEMForOutput to a
// util class in base:
// http://code.google.com/p/chromium/issues/detail?id=13572
bool Extension::ParsePEMKeyBytes(const std::string& input,
std::string* output) {
DCHECK(output);
if (!output)
return false;
if (input.length() == 0)
return false;
std::string working = input;
if (StartsWithASCII(working, kKeyBeginHeaderMarker, true)) {
working = CollapseWhitespaceASCII(working, true);
size_t header_pos = working.find(kKeyInfoEndMarker,
sizeof(kKeyBeginHeaderMarker) - 1);
if (header_pos == std::string::npos)
return false;
size_t start_pos = header_pos + sizeof(kKeyInfoEndMarker) - 1;
size_t end_pos = working.rfind(kKeyBeginFooterMarker);
if (end_pos == std::string::npos)
return false;
if (start_pos >= end_pos)
return false;
working = working.substr(start_pos, end_pos - start_pos);
if (working.length() == 0)
return false;
}
return base::Base64Decode(working, output);
}
bool Extension::ProducePEM(const std::string& input, std::string* output) {
CHECK(output);
if (input.length() == 0)
return false;
return base::Base64Encode(input, output);
}
bool Extension::FormatPEMForFileOutput(const std::string& input,
std::string* output,
bool is_public) {
CHECK(output);
if (input.length() == 0)
return false;
*output = "";
output->append(kKeyBeginHeaderMarker);
output->append(" ");
output->append(is_public ? kPublic : kPrivate);
output->append(" ");
output->append(kKeyInfoEndMarker);
output->append("\n");
for (size_t i = 0; i < input.length(); ) {
int slice = std::min<int>(input.length() - i, kPEMOutputColumns);
output->append(input.substr(i, slice));
output->append("\n");
i += slice;
}
output->append(kKeyBeginFooterMarker);
output->append(" ");
output->append(is_public ? kPublic : kPrivate);
output->append(" ");
output->append(kKeyInfoEndMarker);
output->append("\n");
return true;
}
// static
bool Extension::IsPrivilegeIncrease(const bool granted_full_access,
const std::set<std::string>& granted_apis,
const ExtensionExtent& granted_extent,
const Extension* new_extension) {
// If the extension had native code access, we don't need to go any further.
// Things can't get any worse.
if (granted_full_access)
return false;
// Otherwise, if the new extension has a plugin, it's a privilege increase.
if (new_extension->HasFullPermissions())
return true;
// If the extension hadn't been granted access to all hosts in the past, then
// see if the extension requires more host permissions.
if (!HasEffectiveAccessToAllHosts(granted_extent, granted_apis)) {
if (new_extension->HasEffectiveAccessToAllHosts())
return true;
const ExtensionExtent new_extent =
new_extension->GetEffectiveHostPermissions();
if (IsElevatedHostList(granted_extent.patterns(), new_extent.patterns()))
return true;
}
std::set<std::string> new_apis = new_extension->api_permissions();
std::set<std::string> new_apis_only;
std::set_difference(new_apis.begin(), new_apis.end(),
granted_apis.begin(), granted_apis.end(),
std::inserter(new_apis_only, new_apis_only.begin()));
// Ignore API permissions that don't require user approval when deciding if
// an extension has increased its privileges.
size_t new_api_count = 0;
for (std::set<std::string>::iterator i = new_apis_only.begin();
i != new_apis_only.end(); ++i) {
DCHECK_GT(PermissionMessage::ID_NONE, PermissionMessage::ID_UNKNOWN);
if (GetPermissionMessageId(*i) > PermissionMessage::ID_NONE)
new_api_count++;
}
if (new_api_count)
return true;
return false;
}
// static
void Extension::DecodeIcon(const Extension* extension,
Icons icon_size,
scoped_ptr<SkBitmap>* result) {
FilePath icon_path = extension->GetIconResource(
icon_size, ExtensionIconSet::MATCH_EXACTLY).GetFilePath();
DecodeIconFromPath(icon_path, icon_size, result);
}
// static
void Extension::DecodeIconFromPath(const FilePath& icon_path,
Icons icon_size,
scoped_ptr<SkBitmap>* result) {
if (icon_path.empty())
return;
std::string file_contents;
if (!file_util::ReadFileToString(icon_path, &file_contents)) {
LOG(ERROR) << "Could not read icon file: " << icon_path.LossyDisplayName();
return;
}
// Decode the image using WebKit's image decoder.
const unsigned char* data =
reinterpret_cast<const unsigned char*>(file_contents.data());
webkit_glue::ImageDecoder decoder;
scoped_ptr<SkBitmap> decoded(new SkBitmap());
*decoded = decoder.Decode(data, file_contents.length());
if (decoded->empty()) {
LOG(ERROR) << "Could not decode icon file: "
<< icon_path.LossyDisplayName();
return;
}
if (decoded->width() != icon_size || decoded->height() != icon_size) {
LOG(ERROR) << "Icon file has unexpected size: "
<< base::IntToString(decoded->width()) << "x"
<< base::IntToString(decoded->height());
return;
}
result->swap(decoded);
}
// static
const SkBitmap& Extension::GetDefaultIcon(bool is_app) {
if (is_app) {
return *ResourceBundle::GetSharedInstance().GetBitmapNamed(
IDR_APP_DEFAULT_ICON);
} else {
return *ResourceBundle::GetSharedInstance().GetBitmapNamed(
IDR_EXTENSION_DEFAULT_ICON);
}
}
GURL Extension::GetBaseURLFromExtensionId(const std::string& extension_id) {
return GURL(std::string(chrome::kExtensionScheme) +
chrome::kStandardSchemeSeparator + extension_id + "/");
}
bool Extension::InitFromValue(const DictionaryValue& source, int flags,
std::string* error) {
// When strict error checks are enabled, make URL pattern parsing strict.
URLPattern::ParseOption parse_strictness =
(flags & STRICT_ERROR_CHECKS ? URLPattern::PARSE_STRICT
: URLPattern::PARSE_LENIENT);
if (source.HasKey(keys::kPublicKey)) {
std::string public_key_bytes;
if (!source.GetString(keys::kPublicKey,
&public_key_) ||
!ParsePEMKeyBytes(public_key_,
&public_key_bytes) ||
!GenerateId(public_key_bytes, &id_)) {
*error = errors::kInvalidKey;
return false;
}
} else if (flags & REQUIRE_KEY) {
*error = errors::kInvalidKey;
return false;
} else {
// If there is a path, we generate the ID from it. This is useful for
// development mode, because it keeps the ID stable across restarts and
// reloading the extension.
id_ = Extension::GenerateIdForPath(path());
if (id_.empty()) {
NOTREACHED() << "Could not create ID from path.";
return false;
}
}
// Make a copy of the manifest so we can store it in prefs.
manifest_value_.reset(source.DeepCopy());
// Initialize the URL.
extension_url_ = Extension::GetBaseURLFromExtensionId(id());
// Initialize version.
std::string version_str;
if (!source.GetString(keys::kVersion, &version_str)) {
*error = errors::kInvalidVersion;
return false;
}
version_.reset(Version::GetVersionFromString(version_str));
if (!version_.get() ||
version_->components().size() > 4) {
*error = errors::kInvalidVersion;
return false;
}
// Initialize name.
string16 localized_name;
if (!source.GetString(keys::kName, &localized_name)) {
*error = errors::kInvalidName;
return false;
}
base::i18n::AdjustStringForLocaleDirection(&localized_name);
name_ = UTF16ToUTF8(localized_name);
// Initialize description (if present).
if (source.HasKey(keys::kDescription)) {
if (!source.GetString(keys::kDescription,
&description_)) {
*error = errors::kInvalidDescription;
return false;
}
}
// Initialize homepage url (if present).
if (source.HasKey(keys::kHomepageURL)) {
std::string tmp;
if (!source.GetString(keys::kHomepageURL, &tmp)) {
*error = ExtensionErrorUtils::FormatErrorMessage(
errors::kInvalidHomepageURL, "");
return false;
}
homepage_url_ = GURL(tmp);
if (!homepage_url_.is_valid()) {
*error = ExtensionErrorUtils::FormatErrorMessage(
errors::kInvalidHomepageURL, tmp);
return false;
}
}
// Initialize update url (if present).
if (source.HasKey(keys::kUpdateURL)) {
std::string tmp;
if (!source.GetString(keys::kUpdateURL, &tmp)) {
*error = ExtensionErrorUtils::FormatErrorMessage(
errors::kInvalidUpdateURL, "");
return false;
}
update_url_ = GURL(tmp);
if (!update_url_.is_valid() ||
update_url_.has_ref()) {
*error = ExtensionErrorUtils::FormatErrorMessage(
errors::kInvalidUpdateURL, tmp);
return false;
}
}
// 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)) {
std::string minimum_version_string;
if (!source.GetString(keys::kMinimumChromeVersion,
&minimum_version_string)) {
*error = errors::kInvalidMinimumChromeVersion;
return false;
}
scoped_ptr<Version> minimum_version(
Version::GetVersionFromString(minimum_version_string));
if (!minimum_version.get()) {
*error = errors::kInvalidMinimumChromeVersion;
return false;
}
chrome::VersionInfo current_version_info;
if (!current_version_info.is_valid()) {
NOTREACHED();
return false;
}
scoped_ptr<Version> current_version(
Version::GetVersionFromString(current_version_info.Version()));
if (!current_version.get()) {
DCHECK(false);
return false;
}
if (current_version->CompareTo(*minimum_version) < 0) {
*error = ExtensionErrorUtils::FormatErrorMessage(
errors::kChromeVersionTooLow,
l10n_util::GetStringUTF8(IDS_PRODUCT_NAME),
minimum_version_string);
return false;
}
}
// Initialize converted_from_user_script (if present)
source.GetBoolean(keys::kConvertedFromUserScript,
&converted_from_user_script_);
// Initialize icons (if present).
if (source.HasKey(keys::kIcons)) {
DictionaryValue* icons_value = NULL;
if (!source.GetDictionary(keys::kIcons, &icons_value)) {
*error = errors::kInvalidIcons;
return false;
}
for (size_t i = 0; i < arraysize(kIconSizes); ++i) {
std::string key = base::IntToString(kIconSizes[i]);
if (icons_value->HasKey(key)) {
std::string icon_path;
if (!icons_value->GetString(key, &icon_path)) {
*error = ExtensionErrorUtils::FormatErrorMessage(
errors::kInvalidIconPath, key);
return false;
}
if (!icon_path.empty() && icon_path[0] == '/')
icon_path = icon_path.substr(1);
if (icon_path.empty()) {
*error = ExtensionErrorUtils::FormatErrorMessage(
errors::kInvalidIconPath, key);
return false;
}
icons_.Add(kIconSizes[i], icon_path);
}
}
}
// 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;
}
DictionaryValue* theme_value = NULL;
if (!source.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)) {
// Validate that the images are all strings
for (DictionaryValue::key_iterator iter = images_value->begin_keys();
iter != images_value->end_keys(); ++iter) {
std::string val;
if (!images_value->GetString(*iter, &val)) {
*error = errors::kInvalidThemeImages;
return false;
}
}
theme_images_.reset(images_value->DeepCopy());
}
DictionaryValue* colors_value = NULL;
if (theme_value->GetDictionary(keys::kThemeColors, &colors_value)) {
// Validate that the colors are RGB or RGBA lists
for (DictionaryValue::key_iterator iter = colors_value->begin_keys();
iter != colors_value->end_keys(); ++iter) {
ListValue* color_list = NULL;
double alpha = 0.0;
int alpha_int = 0;
int color = 0;
// The color must be a list
if (!colors_value->GetListWithoutPathExpansion(*iter, &color_list) ||
// And either 3 items (RGB) or 4 (RGBA)
((color_list->GetSize() != 3) &&
((color_list->GetSize() != 4) ||
// For RGBA, the fourth item must be a real or int alpha value
(!color_list->GetDouble(3, &alpha) &&
!color_list->GetInteger(3, &alpha_int)))) ||
// For both RGB and RGBA, the first three items must be ints (R,G,B)
!color_list->GetInteger(0, &color) ||
!color_list->GetInteger(1, &color) ||
!color_list->GetInteger(2, &color)) {
*error = errors::kInvalidThemeColors;
return false;
}
}
theme_colors_.reset(colors_value->DeepCopy());
}
DictionaryValue* tints_value = NULL;
if (theme_value->GetDictionary(keys::kThemeTints, &tints_value)) {
// Validate that the tints are all reals.
for (DictionaryValue::key_iterator iter = tints_value->begin_keys();
iter != tints_value->end_keys(); ++iter) {
ListValue* tint_list = NULL;
double v = 0.0;
int vi = 0;
if (!tints_value->GetListWithoutPathExpansion(*iter, &tint_list) ||
tint_list->GetSize() != 3 ||
!(tint_list->GetDouble(0, &v) || tint_list->GetInteger(0, &vi)) ||
!(tint_list->GetDouble(1, &v) || tint_list->GetInteger(1, &vi)) ||
!(tint_list->GetDouble(2, &v) || tint_list->GetInteger(2, &vi))) {
*error = errors::kInvalidThemeTints;
return false;
}
}
theme_tints_.reset(tints_value->DeepCopy());
}
DictionaryValue* display_properties_value = NULL;
if (theme_value->GetDictionary(keys::kThemeDisplayProperties,
&display_properties_value)) {
theme_display_properties_.reset(
display_properties_value->DeepCopy());
}
return true;
}
// Initialize plugins (optional).
if (source.HasKey(keys::kPlugins)) {
ListValue* list_value = NULL;
if (!source.GetList(keys::kPlugins, &list_value)) {
*error = errors::kInvalidPlugins;
return false;
}
for (size_t i = 0; i < list_value->GetSize(); ++i) {
DictionaryValue* plugin_value = NULL;
std::string path_str;
bool is_public = false;
if (!list_value->GetDictionary(i, &plugin_value)) {
*error = errors::kInvalidPlugins;
return false;
}
// Get plugins[i].path.
if (!plugin_value->GetString(keys::kPluginsPath, &path_str)) {
*error = ExtensionErrorUtils::FormatErrorMessage(
errors::kInvalidPluginsPath, base::IntToString(i));
return false;
}
// Get plugins[i].content (optional).
if (plugin_value->HasKey(keys::kPluginsPublic)) {
if (!plugin_value->GetBoolean(keys::kPluginsPublic, &is_public)) {
*error = ExtensionErrorUtils::FormatErrorMessage(
errors::kInvalidPluginsPublic, base::IntToString(i));
return false;
}
}
// We don't allow extension plugins to run on Chrome OS. We still
// parse the manifest entry so that error messages are consistently
// displayed across platforms.
#if !defined(OS_CHROMEOS)
plugins_.push_back(PluginInfo());
plugins_.back().path = path().AppendASCII(path_str);
plugins_.back().is_public = is_public;
#endif
}
}
if (CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableExperimentalExtensionApis) &&
source.HasKey(keys::kNaClModules)) {
ListValue* list_value = NULL;
if (!source.GetList(keys::kNaClModules, &list_value)) {
*error = errors::kInvalidNaClModules;
return false;
}
for (size_t i = 0; i < list_value->GetSize(); ++i) {
DictionaryValue* module_value = NULL;
std::string path_str;
std::string mime_type;
if (!list_value->GetDictionary(i, &module_value)) {
*error = errors::kInvalidNaClModules;
return false;
}
// Get nacl_modules[i].path.
if (!module_value->GetString(keys::kNaClModulesPath, &path_str)) {
*error = ExtensionErrorUtils::FormatErrorMessage(
errors::kInvalidNaClModulesPath, base::IntToString(i));
return false;
}
// Get nacl_modules[i].mime_type.
if (!module_value->GetString(keys::kNaClModulesMIMEType, &mime_type)) {
*error = ExtensionErrorUtils::FormatErrorMessage(
errors::kInvalidNaClModulesMIMEType, base::IntToString(i));
return false;
}
nacl_modules_.push_back(NaClModuleInfo());
nacl_modules_.back().url = GetResourceURL(path_str);
nacl_modules_.back().mime_type = mime_type;
}
}
// Initialize toolstrips. This is deprecated for public use.
// NOTE(erikkay) Although deprecated, we intend to preserve this parsing
// code indefinitely. Please contact me or Joi for details as to why.
if (CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableExperimentalExtensionApis) &&
source.HasKey(keys::kToolstrips)) {
ListValue* list_value = NULL;
if (!source.GetList(keys::kToolstrips, &list_value)) {
*error = errors::kInvalidToolstrips;
return false;
}
for (size_t i = 0; i < list_value->GetSize(); ++i) {
GURL toolstrip;
DictionaryValue* toolstrip_value = NULL;
std::string toolstrip_path;
if (list_value->GetString(i, &toolstrip_path)) {
// Support a simple URL value for backwards compatibility.
toolstrip = GetResourceURL(toolstrip_path);
} else if (list_value->GetDictionary(i, &toolstrip_value)) {
if (!toolstrip_value->GetString(keys::kToolstripPath,
&toolstrip_path)) {
*error = ExtensionErrorUtils::FormatErrorMessage(
errors::kInvalidToolstrip, base::IntToString(i));
return false;
}
toolstrip = GetResourceURL(toolstrip_path);
} else {
*error = ExtensionErrorUtils::FormatErrorMessage(
errors::kInvalidToolstrip, base::IntToString(i));
return false;
}
toolstrips_.push_back(toolstrip);
}
}
// Initialize content scripts (optional).
if (source.HasKey(keys::kContentScripts)) {
ListValue* list_value;
if (!source.GetList(keys::kContentScripts, &list_value)) {
*error = errors::kInvalidContentScriptsList;
return false;
}
for (size_t i = 0; i < list_value->GetSize(); ++i) {
DictionaryValue* content_script = NULL;
if (!list_value->GetDictionary(i, &content_script)) {
*error = ExtensionErrorUtils::FormatErrorMessage(
errors::kInvalidContentScript, base::IntToString(i));
return false;
}
UserScript script;
if (!LoadUserScriptHelper(content_script, i, flags, error, &script))
return false; // Failed to parse script context definition.
script.set_extension_id(id());
if (converted_from_user_script_) {
script.set_emulate_greasemonkey(true);
script.set_match_all_frames(true); // Greasemonkey matches all frames.
}
content_scripts_.push_back(script);
}
}
// Initialize page action (optional).
DictionaryValue* page_action_value = NULL;
if (source.HasKey(keys::kPageActions)) {
ListValue* list_value = NULL;
if (!source.GetList(keys::kPageActions, &list_value)) {
*error = errors::kInvalidPageActionsList;
return false;
}
size_t list_value_length = list_value->GetSize();
if (list_value_length == 0u) {
// A list with zero items is allowed, and is equivalent to not having
// a page_actions key in the manifest. Don't set |page_action_value|.
} else if (list_value_length == 1u) {
if (!list_value->GetDictionary(0, &page_action_value)) {
*error = errors::kInvalidPageAction;
return false;
}
} else { // list_value_length > 1u.
*error = errors::kInvalidPageActionsListSize;
return false;
}
} else if (source.HasKey(keys::kPageAction)) {
if (!source.GetDictionary(keys::kPageAction, &page_action_value)) {
*error = errors::kInvalidPageAction;
return false;
}
}
// If page_action_value is not NULL, then there was a valid page action.
if (page_action_value) {
page_action_.reset(
LoadExtensionActionHelper(page_action_value, error));
if (!page_action_.get())
return false; // Failed to parse page action definition.
}
// Initialize browser action (optional).
if (source.HasKey(keys::kBrowserAction)) {
DictionaryValue* browser_action_value = NULL;
if (!source.GetDictionary(keys::kBrowserAction, &browser_action_value)) {
*error = errors::kInvalidBrowserAction;
return false;
}
browser_action_.reset(
LoadExtensionActionHelper(browser_action_value, error));
if (!browser_action_.get())
return false; // Failed to parse browser action definition.
}
// Initialize file browser actions (optional).
if (source.HasKey(keys::kFileBrowserHandlers)) {
ListValue* file_browser_handlers_value = NULL;
if (!source.GetList(keys::kFileBrowserHandlers,
&file_browser_handlers_value)) {
*error = errors::kInvalidFileBrowserHandler;
return false;
}
file_browser_handlers_.reset(
LoadFileBrowserHandlers(file_browser_handlers_value, error));
if (!file_browser_handlers_.get())
return false; // Failed to parse file browser actions definition.
}
// Load App settings.
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) ||
!LoadAppIsolation(manifest_value_.get(), error)) {
return false;
}
// Initialize options page url (optional).
// Funtion LoadIsApp() set is_app_ above.
if (source.HasKey(keys::kOptionsPage)) {
std::string options_str;
if (!source.GetString(keys::kOptionsPage, &options_str)) {
*error = errors::kInvalidOptionsPage;
return false;
}
if (is_hosted_app()) {
// hosted apps require an absolute URL.
GURL options_url(options_str);
if (!options_url.is_valid() ||
!(options_url.SchemeIs("http") || options_url.SchemeIs("https"))) {
*error = errors::kInvalidOptionsPageInHostedApp;
return false;
}
options_url_ = options_url;
} else {
GURL absolute(options_str);
if (absolute.is_valid()) {
*error = errors::kInvalidOptionsPageExpectUrlInPackage;
return false;
}
options_url_ = GetResourceURL(options_str);
if (!options_url_.is_valid()) {
*error = errors::kInvalidOptionsPage;
return false;
}
}
}
// Initialize the permissions (optional).
if (source.HasKey(keys::kPermissions)) {
ListValue* permissions = NULL;
if (!source.GetList(keys::kPermissions, &permissions)) {
*error = ExtensionErrorUtils::FormatErrorMessage(
errors::kInvalidPermissions, "");
return false;
}
for (size_t i = 0; i < permissions->GetSize(); ++i) {
std::string permission_str;
if (!permissions->GetString(i, &permission_str)) {
*error = ExtensionErrorUtils::FormatErrorMessage(
errors::kInvalidPermission, base::IntToString(i));
return false;
}
// Only COMPONENT extensions can use private APIs.
// TODO(asargent) - We want a more general purpose mechanism for this,
// and better error messages. (http://crbug.com/54013)
if (!IsComponentOnlyPermission(permission_str)
#ifndef NDEBUG
&& !CommandLine::ForCurrentProcess()->HasSwitch(
switches::kExposePrivateExtensionApi)
#endif
) {
continue;
}
// Remap the old unlimited storage permission name.
if (permission_str == kOldUnlimitedStoragePermission)
permission_str = kUnlimitedStoragePermission;
if (web_extent().is_empty() || location() == Extension::COMPONENT) {
// Check if it's a module permission. If so, enable that permission.
if (IsAPIPermission(permission_str)) {
// Only allow the experimental API permission if the command line
// flag is present, or if the extension is a component of Chrome.
if (permission_str == Extension::kExperimentalPermission &&
!CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableExperimentalExtensionApis) &&
location() != Extension::COMPONENT) {
*error = errors::kExperimentalFlagRequired;
return false;
}
api_permissions_.insert(permission_str);
continue;
}
} else {
// Hosted apps only get access to a subset of the valid permissions.
if (IsHostedAppPermission(permission_str)) {
api_permissions_.insert(permission_str);
continue;
}
}
// Check if it's a host pattern permission.
URLPattern pattern = URLPattern(CanExecuteScriptEverywhere() ?
URLPattern::SCHEME_ALL : kValidHostPermissionSchemes);
URLPattern::ParseResult parse_result = pattern.Parse(permission_str,
parse_strictness);
if (parse_result == URLPattern::PARSE_SUCCESS) {
if (!CanSpecifyHostPermission(pattern)) {
*error = ExtensionErrorUtils::FormatErrorMessage(
errors::kInvalidPermissionScheme, base::IntToString(i));
return false;
}
// The path component is not used for host permissions, so we force it
// to match all paths.
pattern.SetPath("/*");
if (pattern.MatchesScheme(chrome::kFileScheme) &&
!CanExecuteScriptEverywhere()) {
wants_file_access_ = true;
if (!(flags & ALLOW_FILE_ACCESS))
pattern.set_valid_schemes(
pattern.valid_schemes() & ~URLPattern::SCHEME_FILE);
}
host_permissions_.push_back(pattern);
}
// If it's not a host permission, then it's probably an unknown API
// permission. Do not throw an error so extensions can retain
// backwards compatability (http://crbug.com/42742).
// TODO(jstritar): We can improve error messages by adding better
// validation of API permissions here.
// TODO(skerner): Consider showing the reason |permission_str| is not
// a valid URL pattern if it is almost valid. For example, if it has
// a valid scheme, and failed to parse because it has a port, show an
// error.
}
}
// Initialize background url (optional).
if (source.HasKey(keys::kBackground)) {
std::string background_str;
if (!source.GetString(keys::kBackground, &background_str)) {
*error = errors::kInvalidBackground;
return false;
}
if (is_hosted_app()) {
// Make sure "background" permission is set.
if (api_permissions_.find(kBackgroundPermission) ==
api_permissions_.end()) {
*error = errors::kBackgroundPermissionNeeded;
return false;
}
// Hosted apps require an absolute URL.
GURL bg_page(background_str);
if (!bg_page.is_valid()) {
*error = errors::kInvalidBackgroundInHostedApp;
return false;
}
if (!(bg_page.SchemeIs("https") ||
(CommandLine::ForCurrentProcess()->HasSwitch(
switches::kAllowHTTPBackgroundPage) &&
bg_page.SchemeIs("http")))) {
*error = errors::kInvalidBackgroundInHostedApp;
return false;
}
background_url_ = bg_page;
} else {
background_url_ = GetResourceURL(background_str);
}
}
if (source.HasKey(keys::kDefaultLocale)) {
if (!source.GetString(keys::kDefaultLocale, &default_locale_) ||
!l10n_util::IsValidLocaleSyntax(default_locale_)) {
*error = errors::kInvalidDefaultLocale;
return false;
}
}
// Chrome URL overrides (optional)
if (source.HasKey(keys::kChromeURLOverrides)) {
DictionaryValue* overrides = NULL;
if (!source.GetDictionary(keys::kChromeURLOverrides, &overrides)) {
*error = errors::kInvalidChromeURLOverrides;
return false;
}
// Validate that the overrides are all strings
for (DictionaryValue::key_iterator iter = overrides->begin_keys();
iter != overrides->end_keys(); ++iter) {
std::string page = *iter;
std::string val;
// Restrict override pages to a list of supported URLs.
if ((page != chrome::kChromeUINewTabHost &&
#if defined(TOUCH_UI)
page != chrome::kChromeUIKeyboardHost &&
#endif
#if defined(OS_CHROMEOS)
page != chrome::kChromeUIActivationMessageHost &&
#endif
page != chrome::kChromeUIBookmarksHost &&
page != chrome::kChromeUIHistoryHost) ||
!overrides->GetStringWithoutPathExpansion(*iter, &val)) {
*error = errors::kInvalidChromeURLOverrides;
return false;
}
// Replace the entry with a fully qualified chrome-extension:// URL.
chrome_url_overrides_[page] = GetResourceURL(val);
}
// An extension may override at most one page.
if (overrides->size() > 1) {
*error = errors::kMultipleOverrides;
return false;
}
}
if (source.HasKey(keys::kOmnibox)) {
if (!source.GetString(keys::kOmniboxKeyword, &omnibox_keyword_) ||
omnibox_keyword_.empty()) {
*error = errors::kInvalidOmniboxKeyword;
return false;
}
}
// Initialize devtools page url (optional).
if (source.HasKey(keys::kDevToolsPage)) {
std::string devtools_str;
if (!source.GetString(keys::kDevToolsPage, &devtools_str)) {
*error = errors::kInvalidDevToolsPage;
return false;
}
if (!HasApiPermission(Extension::kExperimentalPermission)) {
*error = errors::kDevToolsExperimental;
return false;
}
devtools_url_ = GetResourceURL(devtools_str);
}
// Initialize sidebar action (optional).
if (source.HasKey(keys::kSidebar)) {
DictionaryValue* sidebar_value = NULL;
if (!source.GetDictionary(keys::kSidebar, &sidebar_value)) {
*error = errors::kInvalidSidebar;
return false;
}
if (!HasApiPermission(Extension::kExperimentalPermission)) {
*error = errors::kSidebarExperimental;
return false;
}
sidebar_defaults_.reset(LoadExtensionSidebarDefaults(sidebar_value, error));
if (!sidebar_defaults_.get())
return false; // Failed to parse sidebar definition.
}
// Initialize text-to-speech voices (optional).
if (source.HasKey(keys::kTts)) {
DictionaryValue* tts_dict = NULL;
if (!source.GetDictionary(keys::kTts, &tts_dict)) {
*error = errors::kInvalidTts;
return false;
}
if (tts_dict->HasKey(keys::kTtsVoices)) {
ListValue* tts_voices = NULL;
if (!tts_dict->GetList(keys::kTtsVoices, &tts_voices)) {
*error = errors::kInvalidTtsVoices;
return false;
}
for (size_t i = 0; i < tts_voices->GetSize(); i++) {
DictionaryValue* one_tts_voice = NULL;
if (!tts_voices->GetDictionary(i, &one_tts_voice)) {
*error = errors::kInvalidTtsVoices;
return false;
}
TtsVoice voice_data;
if (one_tts_voice->HasKey(keys::kTtsVoicesVoiceName)) {
if (!one_tts_voice->GetString(
keys::kTtsVoicesVoiceName, &voice_data.voice_name)) {
*error = errors::kInvalidTtsVoicesVoiceName;
return false;
}
}
if (one_tts_voice->HasKey(keys::kTtsVoicesLocale)) {
if (!one_tts_voice->GetString(
keys::kTtsVoicesLocale, &voice_data.locale) ||
!l10n_util::IsValidLocaleSyntax(voice_data.locale)) {
*error = errors::kInvalidTtsVoicesLocale;
return false;
}
}
if (one_tts_voice->HasKey(keys::kTtsVoicesGender)) {
if (!one_tts_voice->GetString(
keys::kTtsVoicesGender, &voice_data.gender) ||
(voice_data.gender != keys::kTtsGenderMale &&
voice_data.gender != keys::kTtsGenderFemale)) {
*error = errors::kInvalidTtsVoicesGender;
return false;
}
}
tts_voices_.push_back(voice_data);
}
}
}
// Initialize incognito behavior. Apps default to split mode, extensions
// default to spanning.
incognito_split_mode_ = is_app();
if (source.HasKey(keys::kIncognito)) {
std::string value;
if (!source.GetString(keys::kIncognito, &value)) {
*error = errors::kInvalidIncognitoBehavior;
return false;
}
if (value == values::kIncognitoSpanning) {
incognito_split_mode_ = false;
} else if (value == values::kIncognitoSplit) {
incognito_split_mode_ = true;
} else {
*error = errors::kInvalidIncognitoBehavior;
return false;
}
}
if (HasMultipleUISurfaces()) {
*error = errors::kOneUISurfaceOnly;
return false;
}
InitEffectiveHostPermissions();
// 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;
}
// static
std::string Extension::ChromeStoreLaunchURL() {
std::string gallery_prefix = extension_urls::kGalleryBrowsePrefix;
if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kAppsGalleryURL))
gallery_prefix = CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kAppsGalleryURL);
if (EndsWith(gallery_prefix, "/", true))
gallery_prefix = gallery_prefix.substr(0, gallery_prefix.length() - 1);
return gallery_prefix;
}
GURL Extension::GetHomepageURL() const {
if (homepage_url_.is_valid())
return homepage_url_;
if (!UpdatesFromGallery())
return GURL();
// TODO(erikkay): This may not be entirely correct with the webstore.
// I think it will have a mixture of /extensions/detail and /webstore/detail
// URLs. Perhaps they'll handle this nicely with redirects?
GURL url(ChromeStoreLaunchURL() + std::string("/detail/") + id());
return url;
}
std::set<FilePath> Extension::GetBrowserImages() const {
std::set<FilePath> image_paths;
// TODO(viettrungluu): These |FilePath::FromWStringHack(UTF8ToWide())|
// indicate that we're doing something wrong.
// Extension icons.
for (ExtensionIconSet::IconMap::const_iterator iter = icons().map().begin();
iter != icons().map().end(); ++iter) {
image_paths.insert(FilePath::FromWStringHack(UTF8ToWide(iter->second)));
}
// Theme images.
DictionaryValue* theme_images = GetThemeImages();
if (theme_images) {
for (DictionaryValue::key_iterator it = theme_images->begin_keys();
it != theme_images->end_keys(); ++it) {
std::string val;
if (theme_images->GetStringWithoutPathExpansion(*it, &val))
image_paths.insert(FilePath::FromWStringHack(UTF8ToWide(val)));
}
}
// Page action icons.
if (page_action()) {
std::vector<std::string>* icon_paths = page_action()->icon_paths();
for (std::vector<std::string>::iterator iter = icon_paths->begin();
iter != icon_paths->end(); ++iter) {
image_paths.insert(FilePath::FromWStringHack(UTF8ToWide(*iter)));
}
}
// Browser action icons.
if (browser_action()) {
std::vector<std::string>* icon_paths = browser_action()->icon_paths();
for (std::vector<std::string>::iterator iter = icon_paths->begin();
iter != icon_paths->end(); ++iter) {
image_paths.insert(FilePath::FromWStringHack(UTF8ToWide(*iter)));
}
}
return image_paths;
}
GURL Extension::GetFullLaunchURL() const {
if (!launch_local_path().empty())
return url().Resolve(launch_local_path());
else
return GURL(launch_web_url());
}
static std::string SizeToString(const gfx::Size& max_size) {
return base::IntToString(max_size.width()) + "x" +
base::IntToString(max_size.height());
}
// static
void Extension::SetScriptingWhitelist(
const Extension::ScriptingWhitelist& whitelist) {
ScriptingWhitelist* current_whitelist =
ExtensionConfig::GetInstance()->whitelist();
current_whitelist->clear();
for (ScriptingWhitelist::const_iterator it = whitelist.begin();
it != whitelist.end(); ++it) {
current_whitelist->push_back(*it);
}
}
// static
const Extension::ScriptingWhitelist* Extension::GetScriptingWhitelist() {
return ExtensionConfig::GetInstance()->whitelist();
}
void Extension::SetCachedImage(const ExtensionResource& source,
const SkBitmap& image,
const gfx::Size& original_size) const {
DCHECK(source.extension_root() == path()); // The resource must come from
// this extension.
const FilePath& path = source.relative_path();
gfx::Size actual_size(image.width(), image.height());
if (actual_size == original_size) {
image_cache_[ImageCacheKey(path, std::string())] = image;
} else {
image_cache_[ImageCacheKey(path, SizeToString(actual_size))] = image;
}
}
bool Extension::HasCachedImage(const ExtensionResource& source,
const gfx::Size& max_size) const {
DCHECK(source.extension_root() == path()); // The resource must come from
// this extension.
return GetCachedImageImpl(source, max_size) != NULL;
}
SkBitmap Extension::GetCachedImage(const ExtensionResource& source,
const gfx::Size& max_size) const {
DCHECK(source.extension_root() == path()); // The resource must come from
// this extension.
SkBitmap* image = GetCachedImageImpl(source, max_size);
return image ? *image : SkBitmap();
}
SkBitmap* Extension::GetCachedImageImpl(const ExtensionResource& source,
const gfx::Size& max_size) const {
const FilePath& path = source.relative_path();
// Look for exact size match.
ImageCache::iterator i = image_cache_.find(
ImageCacheKey(path, SizeToString(max_size)));
if (i != image_cache_.end())
return &(i->second);
// If we have the original size version cached, return that if it's small
// enough.
i = image_cache_.find(ImageCacheKey(path, std::string()));
if (i != image_cache_.end()) {
SkBitmap& image = i->second;
if (image.width() <= max_size.width() &&
image.height() <= max_size.height())
return &(i->second);
}
return NULL;
}
ExtensionResource Extension::GetIconResource(
int size, ExtensionIconSet::MatchType match_type) const {
std::string path = icons().Get(size, match_type);
if (path.empty())
return ExtensionResource();
return GetResource(path);
}
GURL Extension::GetIconURL(int size,
ExtensionIconSet::MatchType match_type) const {
std::string path = icons().Get(size, match_type);
if (path.empty())
return GURL();
else
return GetResourceURL(path);
}
bool Extension::CanSpecifyHostPermission(const URLPattern& pattern) const {
if (!pattern.match_all_urls() &&
pattern.MatchesScheme(chrome::kChromeUIScheme)) {
// Only allow access to chrome://favicon to regular extensions. Component
// extensions can have access to all of chrome://*.
return (pattern.host() == chrome::kChromeUIFaviconHost ||
CanExecuteScriptEverywhere());
}
// Otherwise, the valid schemes were handled by URLPattern.
return true;
}
// static
bool Extension::HasApiPermission(
const std::set<std::string>& api_permissions,
const std::string& function_name) {
std::string permission_name = function_name;
for (size_t i = 0; i < kNumNonPermissionFunctionNames; ++i) {
if (permission_name == kNonPermissionFunctionNames[i])
return true;
}
// See if this is a function or event name first and strip out the package.
// Functions will be of the form package.function
// Events will be of the form package/id or package.optional.stuff
size_t separator = function_name.find_first_of("./");
if (separator != std::string::npos)
permission_name = function_name.substr(0, separator);
// windows and tabs are the same permission.
if (permission_name == kWindowPermission)
permission_name = Extension::kTabPermission;
if (api_permissions.count(permission_name))
return true;
for (size_t i = 0; i < kNumNonPermissionModuleNames; ++i) {
if (permission_name == kNonPermissionModuleNames[i]) {
return true;
}
}
return false;
}
bool Extension::HasHostPermission(const GURL& url) const {
for (URLPatternList::const_iterator host = host_permissions().begin();
host != host_permissions().end(); ++host) {
// Non-component extensions can only access chrome://favicon and no other
// chrome:// scheme urls.
if (url.SchemeIs(chrome::kChromeUIScheme) &&
url.host() != chrome::kChromeUIFaviconHost &&
location() != Extension::COMPONENT)
return false;
if (host->MatchesUrl(url))
return true;
}
return false;
}
void Extension::InitEffectiveHostPermissions() {
// Some APIs effectively grant access to every site. New ones should be
// added here. (I'm looking at you, network API)
if (HasApiPermission(api_permissions_, kProxyPermission) ||
!devtools_url_.is_empty()) {
URLPattern all_urls(URLPattern::SCHEME_ALL);
all_urls.set_match_all_urls(true);
effective_host_permissions_.AddPattern(all_urls);
return;
}
for (URLPatternList::const_iterator host = host_permissions().begin();
host != host_permissions().end(); ++host)
effective_host_permissions_.AddPattern(*host);
for (UserScriptList::const_iterator content_script =
content_scripts().begin();
content_script != content_scripts().end(); ++content_script) {
UserScript::PatternList::const_iterator pattern =
content_script->url_patterns().begin();
for (; pattern != content_script->url_patterns().end(); ++pattern)
effective_host_permissions_.AddPattern(*pattern);
}
}
bool Extension::IsComponentOnlyPermission
(const std::string& permission) const {
if (location() == Extension::COMPONENT)
return true;
// Non-component extensions are not allowed to access private apis.
for (size_t i = 0; i < Extension::kNumComponentPrivatePermissions; ++i) {
if (permission == Extension::kComponentPrivatePermissionNames[i])
return false;
}
return true;
}
bool Extension::HasMultipleUISurfaces() const {
int num_surfaces = 0;
if (page_action())
++num_surfaces;
if (browser_action())
++num_surfaces;
if (is_app())
++num_surfaces;
return num_surfaces > 1;
}
bool Extension::CanExecuteScriptOnPage(const GURL& page_url,
const UserScript* script,
std::string* error) const {
// The gallery is special-cased as a restricted URL for scripting to prevent
// access to special JS bindings we expose to the gallery (and avoid things
// like extensions removing the "report abuse" link).
// TODO(erikkay): This seems like the wrong test. Shouldn't we we testing
// against the store app extent?
if ((page_url.host() == GURL(Extension::ChromeStoreLaunchURL()).host()) &&
!CanExecuteScriptEverywhere() &&
!CommandLine::ForCurrentProcess()->HasSwitch(
switches::kAllowScriptingGallery)) {
if (error)
*error = errors::kCannotScriptGallery;
return false;
}
if (page_url.SchemeIs(chrome::kChromeUIScheme) &&
!CanExecuteScriptEverywhere())
return false;
// If a script is specified, use its matches.
if (script)
return script->MatchesUrl(page_url);
// Otherwise, see if this extension has permission to execute script
// programmatically on pages.
for (size_t i = 0; i < host_permissions_.size(); ++i) {
if (host_permissions_[i].MatchesUrl(page_url))
return true;
}
if (error) {
*error = ExtensionErrorUtils::FormatErrorMessage(errors::kCannotAccessPage,
page_url.spec());
}
return false;
}
// static
bool Extension::HasEffectiveAccessToAllHosts(
const ExtensionExtent& effective_host_permissions,
const std::set<std::string>& api_permissions) {
const URLPatternList patterns = effective_host_permissions.patterns();
for (URLPatternList::const_iterator host = patterns.begin();
host != patterns.end(); ++host) {
if (host->match_all_urls() ||
(host->match_subdomains() && host->host().empty()))
return true;
}
return false;
}
bool Extension::HasEffectiveAccessToAllHosts() const {
return HasEffectiveAccessToAllHosts(GetEffectiveHostPermissions(),
api_permissions());
}
bool Extension::HasFullPermissions() const {
return !plugins().empty();
}
bool Extension::ShowConfigureContextMenus() const {
// Don't show context menu for component extensions. We might want to show
// options for component extension button but now there is no component
// extension with options. All other menu items like uninstall have
// no sense for component extensions.
return location() != Extension::COMPONENT;
}
bool Extension::IsAPIPermission(const std::string& str) const {
for (size_t i = 0; i < Extension::kNumPermissions; ++i) {
if (str == Extension::kPermissions[i].name) {
return true;
}
}
return false;
}
bool Extension::CanExecuteScriptEverywhere() const {
if (location() == Extension::COMPONENT
#ifndef NDEBUG
|| CommandLine::ForCurrentProcess()->HasSwitch(
switches::kExposePrivateExtensionApi)
#endif
)
return true;
ScriptingWhitelist* whitelist =
ExtensionConfig::GetInstance()->whitelist();
for (ScriptingWhitelist::const_iterator it = whitelist->begin();
it != whitelist->end(); ++it) {
if (id() == *it) {
return true;
}
}
return false;
}
bool Extension::CanCaptureVisiblePage(const GURL& page_url,
std::string *error) const {
if (HasHostPermission(page_url) || page_url.GetOrigin() == url())
return true;
if (error) {
*error = ExtensionErrorUtils::FormatErrorMessage(errors::kCannotAccessPage,
page_url.spec());
}
return false;
}
bool Extension::UpdatesFromGallery() const {
return update_url() == GalleryUpdateUrl(false) ||
update_url() == GalleryUpdateUrl(true);
}
bool Extension::OverlapsWithOrigin(const GURL& origin) const {
if (url() == origin)
return true;
if (web_extent().is_empty())
return false;
// Note: patterns and extents ignore port numbers.
URLPattern origin_only_pattern(kValidWebExtentSchemes);
if (!origin_only_pattern.SetScheme(origin.scheme()))
return false;
origin_only_pattern.set_host(origin.host());
origin_only_pattern.SetPath("/*");
ExtensionExtent origin_only_pattern_list;
origin_only_pattern_list.AddPattern(origin_only_pattern);
return web_extent().OverlapsWith(origin_only_pattern_list);
}
ExtensionInfo::ExtensionInfo(const DictionaryValue* manifest,
const std::string& id,
const FilePath& path,
Extension::Location location)
: extension_id(id),
extension_path(path),
extension_location(location) {
if (manifest)
extension_manifest.reset(manifest->DeepCopy());
}
ExtensionInfo::~ExtensionInfo() {}
UninstalledExtensionInfo::UninstalledExtensionInfo(
const Extension& extension)
: extension_id(extension.id()),
extension_api_permissions(extension.api_permissions()),
extension_type(extension.GetType()),
update_url(extension.update_url()) {}
UninstalledExtensionInfo::~UninstalledExtensionInfo() {}
UnloadedExtensionInfo::UnloadedExtensionInfo(
const Extension* extension,
Reason reason)
: reason(reason),
already_disabled(false),
extension(extension) {}