// 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"
#if defined(TOOLKIT_GTK)
#include <gtk/gtk.h>
#endif
#include "base/format_macros.h"
#include "base/file_path.h"
#include "base/file_util.h"
#include "base/i18n/rtl.h"
#include "base/path_service.h"
#include "base/string_number_conversions.h"
#include "base/string_util.h"
#include "base/utf_string_conversions.h"
#include "chrome/common/chrome_paths.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_resource.h"
#include "chrome/common/url_constants.h"
#include "content/common/json_value_serializer.h"
#include "googleurl/src/gurl.h"
#include "net/base/mime_sniffer.h"
#include "skia/ext/image_operations.h"
#include "chrome/test/ui_test_utils.h"
#include "net/base/mock_host_resolver.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/codec/png_codec.h"
namespace keys = extension_manifest_keys;
namespace values = extension_manifest_values;
namespace errors = extension_manifest_errors;
namespace {
void CompareLists(const std::vector<std::string>& expected,
const std::vector<std::string>& actual) {
ASSERT_EQ(expected.size(), actual.size());
for (size_t i = 0; i < expected.size(); ++i) {
EXPECT_EQ(expected[i], actual[i]);
}
}
static void AddPattern(ExtensionExtent* extent, const std::string& pattern) {
int schemes = URLPattern::SCHEME_ALL;
extent->AddPattern(URLPattern(schemes, pattern));
}
}
class ExtensionTest : public testing::Test {
};
// We persist location values in the preferences, so this is a sanity test that
// someone doesn't accidentally change them.
TEST(ExtensionTest, LocationValuesTest) {
ASSERT_EQ(0, Extension::INVALID);
ASSERT_EQ(1, Extension::INTERNAL);
ASSERT_EQ(2, Extension::EXTERNAL_PREF);
ASSERT_EQ(3, Extension::EXTERNAL_REGISTRY);
ASSERT_EQ(4, Extension::LOAD);
ASSERT_EQ(5, Extension::COMPONENT);
ASSERT_EQ(6, Extension::EXTERNAL_PREF_DOWNLOAD);
ASSERT_EQ(7, Extension::EXTERNAL_POLICY_DOWNLOAD);
}
TEST(ExtensionTest, LocationPriorityTest) {
for (int i = 0; i < Extension::NUM_LOCATIONS; i++) {
Extension::Location loc = static_cast<Extension::Location>(i);
// INVALID is not a valid location.
if (loc == Extension::INVALID)
continue;
// Comparing a location that has no rank will hit a CHECK. Do a
// compare with every valid location, to be sure each one is covered.
// Check that no install source can override a componenet extension.
ASSERT_EQ(Extension::COMPONENT,
Extension::GetHigherPriorityLocation(Extension::COMPONENT, loc));
ASSERT_EQ(Extension::COMPONENT,
Extension::GetHigherPriorityLocation(loc, Extension::COMPONENT));
// Check that any source can override a user install. This might change
// in the future, in which case this test should be updated.
ASSERT_EQ(loc,
Extension::GetHigherPriorityLocation(Extension::INTERNAL, loc));
ASSERT_EQ(loc,
Extension::GetHigherPriorityLocation(loc, Extension::INTERNAL));
}
// Check a few interesting cases that we know can happen:
ASSERT_EQ(Extension::EXTERNAL_POLICY_DOWNLOAD,
Extension::GetHigherPriorityLocation(
Extension::EXTERNAL_POLICY_DOWNLOAD,
Extension::EXTERNAL_PREF));
ASSERT_EQ(Extension::EXTERNAL_PREF,
Extension::GetHigherPriorityLocation(
Extension::INTERNAL,
Extension::EXTERNAL_PREF));
}
// Please don't put any more manifest tests here!!
// Move them to extension_manifest_unittest.cc instead and make them use the
// more data-driven style there instead.
// Bug: http://crbug.com/38462
TEST(ExtensionTest, InitFromValueInvalid) {
#if defined(OS_WIN)
FilePath path(FILE_PATH_LITERAL("c:\\foo"));
#elif defined(OS_POSIX)
FilePath path(FILE_PATH_LITERAL("/foo"));
#endif
scoped_refptr<Extension> extension_ptr(new Extension(path,
Extension::INVALID));
Extension& extension = *extension_ptr;
int error_code = 0;
std::string error;
// Start with a valid extension manifest
FilePath extensions_path;
ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path));
extensions_path = extensions_path.AppendASCII("extensions")
.AppendASCII("good")
.AppendASCII("Extensions")
.AppendASCII("behllobkkfkfnphdnhnkndlbkcpglgmj")
.AppendASCII("1.0.0.0")
.Append(Extension::kManifestFilename);
JSONFileValueSerializer serializer(extensions_path);
scoped_ptr<DictionaryValue> valid_value(
static_cast<DictionaryValue*>(serializer.Deserialize(&error_code,
&error)));
EXPECT_EQ("", error);
EXPECT_EQ(0, error_code);
ASSERT_TRUE(valid_value.get());
ASSERT_TRUE(extension.InitFromValue(*valid_value, Extension::REQUIRE_KEY,
&error));
ASSERT_EQ("", error);
EXPECT_EQ("en_US", extension.default_locale());
scoped_ptr<DictionaryValue> input_value;
// Test missing and invalid versions
input_value.reset(valid_value->DeepCopy());
input_value->Remove(keys::kVersion, NULL);
EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
&error));
EXPECT_EQ(errors::kInvalidVersion, error);
input_value->SetInteger(keys::kVersion, 42);
EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
&error));
EXPECT_EQ(errors::kInvalidVersion, error);
// Test missing and invalid names.
input_value.reset(valid_value->DeepCopy());
input_value->Remove(keys::kName, NULL);
EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
&error));
EXPECT_EQ(errors::kInvalidName, error);
input_value->SetInteger(keys::kName, 42);
EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
&error));
EXPECT_EQ(errors::kInvalidName, error);
// Test invalid description
input_value.reset(valid_value->DeepCopy());
input_value->SetInteger(keys::kDescription, 42);
EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
&error));
EXPECT_EQ(errors::kInvalidDescription, error);
// Test invalid icons
input_value.reset(valid_value->DeepCopy());
input_value->SetInteger(keys::kIcons, 42);
EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
&error));
EXPECT_EQ(errors::kInvalidIcons, error);
// Test invalid icon paths
input_value.reset(valid_value->DeepCopy());
DictionaryValue* icons = NULL;
input_value->GetDictionary(keys::kIcons, &icons);
ASSERT_FALSE(NULL == icons);
icons->SetInteger(base::IntToString(128), 42);
EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
&error));
EXPECT_TRUE(MatchPattern(error, errors::kInvalidIconPath));
// Test invalid user scripts list
input_value.reset(valid_value->DeepCopy());
input_value->SetInteger(keys::kContentScripts, 42);
EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
&error));
EXPECT_EQ(errors::kInvalidContentScriptsList, error);
// Test invalid user script item
input_value.reset(valid_value->DeepCopy());
ListValue* content_scripts = NULL;
input_value->GetList(keys::kContentScripts, &content_scripts);
ASSERT_FALSE(NULL == content_scripts);
content_scripts->Set(0, Value::CreateIntegerValue(42));
EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
&error));
EXPECT_TRUE(MatchPattern(error, errors::kInvalidContentScript));
// Test missing and invalid matches array
input_value.reset(valid_value->DeepCopy());
input_value->GetList(keys::kContentScripts, &content_scripts);
DictionaryValue* user_script = NULL;
content_scripts->GetDictionary(0, &user_script);
user_script->Remove(keys::kMatches, NULL);
EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
&error));
EXPECT_TRUE(MatchPattern(error, errors::kInvalidMatches));
user_script->Set(keys::kMatches, Value::CreateIntegerValue(42));
EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
&error));
EXPECT_TRUE(MatchPattern(error, errors::kInvalidMatches));
ListValue* matches = new ListValue;
user_script->Set(keys::kMatches, matches);
EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
&error));
EXPECT_TRUE(MatchPattern(error, errors::kInvalidMatchCount));
// Test invalid match element
matches->Set(0, Value::CreateIntegerValue(42));
EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
&error));
EXPECT_TRUE(MatchPattern(error, errors::kInvalidMatch));
matches->Set(0, Value::CreateStringValue("chrome://*/*"));
EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
&error));
EXPECT_TRUE(MatchPattern(error, errors::kInvalidMatch));
// Test missing and invalid files array
input_value.reset(valid_value->DeepCopy());
input_value->GetList(keys::kContentScripts, &content_scripts);
content_scripts->GetDictionary(0, &user_script);
user_script->Remove(keys::kJs, NULL);
user_script->Remove(keys::kCss, NULL);
EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
&error));
EXPECT_TRUE(MatchPattern(error, errors::kMissingFile));
user_script->Set(keys::kJs, Value::CreateIntegerValue(42));
EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
&error));
EXPECT_TRUE(MatchPattern(error, errors::kInvalidJsList));
user_script->Set(keys::kCss, new ListValue);
user_script->Set(keys::kJs, new ListValue);
EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
&error));
EXPECT_TRUE(MatchPattern(error, errors::kMissingFile));
user_script->Remove(keys::kCss, NULL);
ListValue* files = new ListValue;
user_script->Set(keys::kJs, files);
EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
&error));
EXPECT_TRUE(MatchPattern(error, errors::kMissingFile));
// Test invalid file element
files->Set(0, Value::CreateIntegerValue(42));
EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
&error));
EXPECT_TRUE(MatchPattern(error, errors::kInvalidJs));
user_script->Remove(keys::kJs, NULL);
// Test the css element
user_script->Set(keys::kCss, Value::CreateIntegerValue(42));
EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
&error));
EXPECT_TRUE(MatchPattern(error, errors::kInvalidCssList));
// Test invalid file element
ListValue* css_files = new ListValue;
user_script->Set(keys::kCss, css_files);
css_files->Set(0, Value::CreateIntegerValue(42));
EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
&error));
EXPECT_TRUE(MatchPattern(error, errors::kInvalidCss));
// Test missing and invalid permissions array
input_value.reset(valid_value->DeepCopy());
EXPECT_TRUE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
&error));
ListValue* permissions = NULL;
input_value->GetList(keys::kPermissions, &permissions);
ASSERT_FALSE(NULL == permissions);
permissions = new ListValue;
input_value->Set(keys::kPermissions, permissions);
EXPECT_TRUE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
&error));
input_value->Set(keys::kPermissions, Value::CreateIntegerValue(9));
EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
&error));
EXPECT_TRUE(MatchPattern(error, errors::kInvalidPermissions));
input_value.reset(valid_value->DeepCopy());
input_value->GetList(keys::kPermissions, &permissions);
permissions->Set(0, Value::CreateIntegerValue(24));
EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
&error));
EXPECT_TRUE(MatchPattern(error, errors::kInvalidPermission));
// We allow unknown API permissions, so this will be valid until we better
// distinguish between API and host permissions.
permissions->Set(0, Value::CreateStringValue("www.google.com"));
EXPECT_TRUE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
&error));
// Multiple page actions are not allowed.
input_value.reset(valid_value->DeepCopy());
DictionaryValue* action = new DictionaryValue;
action->SetString(keys::kPageActionId, "MyExtensionActionId");
action->SetString(keys::kName, "MyExtensionActionName");
ListValue* action_list = new ListValue;
action_list->Append(action->DeepCopy());
action_list->Append(action);
input_value->Set(keys::kPageActions, action_list);
EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
&error));
EXPECT_STREQ(errors::kInvalidPageActionsListSize, error.c_str());
// Test invalid options page url.
input_value.reset(valid_value->DeepCopy());
input_value->Set(keys::kOptionsPage, Value::CreateNullValue());
EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
&error));
EXPECT_TRUE(MatchPattern(error, errors::kInvalidOptionsPage));
// Test invalid/empty default locale.
input_value.reset(valid_value->DeepCopy());
input_value->Set(keys::kDefaultLocale, Value::CreateIntegerValue(5));
EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
&error));
EXPECT_TRUE(MatchPattern(error, errors::kInvalidDefaultLocale));
input_value->Set(keys::kDefaultLocale, Value::CreateStringValue(""));
EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
&error));
EXPECT_TRUE(MatchPattern(error, errors::kInvalidDefaultLocale));
// Test invalid minimum_chrome_version.
input_value.reset(valid_value->DeepCopy());
input_value->Set(keys::kMinimumChromeVersion, Value::CreateIntegerValue(42));
EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
&error));
EXPECT_TRUE(MatchPattern(error, errors::kInvalidMinimumChromeVersion));
#if !defined(OS_MACOSX)
// TODO(aa): The version isn't stamped into the unit test binary on mac.
input_value->Set(keys::kMinimumChromeVersion,
Value::CreateStringValue("88.8"));
EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
&error));
EXPECT_TRUE(MatchPattern(error, errors::kChromeVersionTooLow));
#endif
}
TEST(ExtensionTest, InitFromValueValid) {
#if defined(OS_WIN)
FilePath path(FILE_PATH_LITERAL("C:\\foo"));
#elif defined(OS_POSIX)
FilePath path(FILE_PATH_LITERAL("/foo"));
#endif
scoped_refptr<Extension> extension_ptr(new Extension(path,
Extension::INVALID));
Extension& extension = *extension_ptr;
std::string error;
DictionaryValue input_value;
// Test minimal extension
input_value.SetString(keys::kVersion, "1.0.0.0");
input_value.SetString(keys::kName, "my extension");
EXPECT_TRUE(extension.InitFromValue(input_value, Extension::NO_FLAGS,
&error));
EXPECT_EQ("", error);
EXPECT_TRUE(Extension::IdIsValid(extension.id()));
EXPECT_EQ("1.0.0.0", extension.VersionString());
EXPECT_EQ("my extension", extension.name());
EXPECT_EQ(extension.id(), extension.url().host());
EXPECT_EQ(path.value(), extension.path().value());
// Test permissions scheme.
ListValue* permissions = new ListValue;
permissions->Set(0, Value::CreateStringValue("file:///C:/foo.txt"));
input_value.Set(keys::kPermissions, permissions);
// We allow unknown API permissions, so this will be valid until we better
// distinguish between API and host permissions.
EXPECT_TRUE(extension.InitFromValue(input_value, Extension::NO_FLAGS,
&error));
input_value.Remove(keys::kPermissions, NULL);
// Test with an options page.
input_value.SetString(keys::kOptionsPage, "options.html");
EXPECT_TRUE(extension.InitFromValue(input_value, Extension::NO_FLAGS,
&error));
EXPECT_EQ("", error);
EXPECT_EQ("chrome-extension", extension.options_url().scheme());
EXPECT_EQ("/options.html", extension.options_url().path());
// Test that an empty list of page actions does not stop a browser action
// from being loaded.
ListValue* empty_list = new ListValue;
input_value.Set(keys::kPageActions, empty_list);
EXPECT_TRUE(extension.InitFromValue(input_value, Extension::NO_FLAGS,
&error));
EXPECT_EQ("", error);
#if !defined(OS_MACOSX)
// TODO(aa): The version isn't stamped into the unit test binary on mac.
// Test with a minimum_chrome_version.
input_value.SetString(keys::kMinimumChromeVersion, "1.0");
EXPECT_TRUE(extension.InitFromValue(input_value, Extension::NO_FLAGS,
&error));
EXPECT_EQ("", error);
// The minimum chrome version is not stored in the Extension object.
#endif
}
TEST(ExtensionTest, InitFromValueValidNameInRTL) {
#if defined(TOOLKIT_GTK)
GtkTextDirection gtk_dir = gtk_widget_get_default_direction();
gtk_widget_set_default_direction(GTK_TEXT_DIR_RTL);
#else
std::string locale = l10n_util::GetApplicationLocale("");
base::i18n::SetICUDefaultLocale("he");
#endif
#if defined(OS_WIN)
FilePath path(FILE_PATH_LITERAL("C:\\foo"));
#elif defined(OS_POSIX)
FilePath path(FILE_PATH_LITERAL("/foo"));
#endif
scoped_refptr<Extension> extension_ptr(new Extension(path,
Extension::INVALID));
Extension& extension = *extension_ptr;
std::string error;
DictionaryValue input_value;
input_value.SetString(keys::kVersion, "1.0.0.0");
// No strong RTL characters in name.
std::wstring name(L"Dictionary (by Google)");
input_value.SetString(keys::kName, WideToUTF16Hack(name));
EXPECT_TRUE(extension.InitFromValue(input_value, Extension::NO_FLAGS,
&error));
EXPECT_EQ("", error);
std::wstring localized_name(name);
base::i18n::AdjustStringForLocaleDirection(&localized_name);
EXPECT_EQ(localized_name, UTF8ToWide(extension.name()));
// Strong RTL characters in name.
name = L"Dictionary (\x05D1\x05D2"L" Google)";
input_value.SetString(keys::kName, WideToUTF16Hack(name));
EXPECT_TRUE(extension.InitFromValue(input_value, Extension::NO_FLAGS,
&error));
EXPECT_EQ("", error);
localized_name = name;
base::i18n::AdjustStringForLocaleDirection(&localized_name);
EXPECT_EQ(localized_name, UTF8ToWide(extension.name()));
// Reset locale.
#if defined(TOOLKIT_GTK)
gtk_widget_set_default_direction(gtk_dir);
#else
base::i18n::SetICUDefaultLocale(locale);
#endif
}
TEST(ExtensionTest, GetResourceURLAndPath) {
#if defined(OS_WIN)
FilePath path(FILE_PATH_LITERAL("C:\\foo"));
#elif defined(OS_POSIX)
FilePath path(FILE_PATH_LITERAL("/foo"));
#endif
DictionaryValue input_value;
input_value.SetString(keys::kVersion, "1.0.0.0");
input_value.SetString(keys::kName, "my extension");
scoped_refptr<Extension> extension(Extension::Create(path,
Extension::INVALID, input_value, Extension::STRICT_ERROR_CHECKS, NULL));
EXPECT_TRUE(extension.get());
EXPECT_EQ(extension->url().spec() + "bar/baz.js",
Extension::GetResourceURL(extension->url(), "bar/baz.js").spec());
EXPECT_EQ(extension->url().spec() + "baz.js",
Extension::GetResourceURL(extension->url(),
"bar/../baz.js").spec());
EXPECT_EQ(extension->url().spec() + "baz.js",
Extension::GetResourceURL(extension->url(), "../baz.js").spec());
}
TEST(ExtensionTest, LoadPageActionHelper) {
#if defined(OS_WIN)
FilePath path(base::StringPrintf(L"c:\\extension"));
#else
FilePath path(base::StringPrintf("/extension"));
#endif
scoped_refptr<Extension> extension_ptr(new Extension(path,
Extension::INVALID));
Extension& extension = *extension_ptr;
std::string error_msg;
scoped_ptr<ExtensionAction> action;
DictionaryValue input;
// First try with an empty dictionary.
action.reset(extension.LoadExtensionActionHelper(&input, &error_msg));
ASSERT_TRUE(action != NULL);
ASSERT_TRUE(error_msg.empty());
// Now setup some values to use in the action.
const std::string id("MyExtensionActionId");
const std::string name("MyExtensionActionName");
std::string img1("image1.png");
std::string img2("image2.png");
// Add the dictionary for the contextual action.
input.SetString(keys::kPageActionId, id);
input.SetString(keys::kName, name);
ListValue* icons = new ListValue;
icons->Set(0, Value::CreateStringValue(img1));
icons->Set(1, Value::CreateStringValue(img2));
input.Set(keys::kPageActionIcons, icons);
// Parse and read back the values from the object.
action.reset(extension.LoadExtensionActionHelper(&input, &error_msg));
ASSERT_TRUE(NULL != action.get());
ASSERT_TRUE(error_msg.empty());
ASSERT_EQ(id, action->id());
// No title, so fall back to name.
ASSERT_EQ(name, action->GetTitle(1));
ASSERT_EQ(2u, action->icon_paths()->size());
ASSERT_EQ(img1, (*action->icon_paths())[0]);
ASSERT_EQ(img2, (*action->icon_paths())[1]);
// Explicitly set the same type and parse again.
input.SetString(keys::kType, values::kPageActionTypeTab);
action.reset(extension.LoadExtensionActionHelper(&input, &error_msg));
ASSERT_TRUE(NULL != action.get());
ASSERT_TRUE(error_msg.empty());
// Make a deep copy of the input and remove one key at a time and see if we
// get the right error.
scoped_ptr<DictionaryValue> copy;
// First remove id key.
copy.reset(input.DeepCopy());
copy->Remove(keys::kPageActionId, NULL);
action.reset(extension.LoadExtensionActionHelper(copy.get(), &error_msg));
ASSERT_TRUE(NULL != action.get());
// Then remove the name key. It's optional, so no error.
copy.reset(input.DeepCopy());
copy->Remove(keys::kName, NULL);
action.reset(extension.LoadExtensionActionHelper(copy.get(), &error_msg));
ASSERT_TRUE(NULL != action.get());
ASSERT_TRUE(action->GetTitle(1).empty());
ASSERT_TRUE(error_msg.empty());
// Then remove the icon paths key.
copy.reset(input.DeepCopy());
copy->Remove(keys::kPageActionIcons, NULL);
action.reset(extension.LoadExtensionActionHelper(copy.get(), &error_msg));
ASSERT_TRUE(NULL != action.get());
error_msg = "";
// Now test that we can parse the new format for page actions.
// Now setup some values to use in the page action.
const std::string kTitle("MyExtensionActionTitle");
const std::string kIcon("image1.png");
const std::string kPopupHtmlFile("a_popup.html");
// Add the dictionary for the contextual action.
input.Clear();
input.SetString(keys::kPageActionDefaultTitle, kTitle);
input.SetString(keys::kPageActionDefaultIcon, kIcon);
// Parse and read back the values from the object.
action.reset(extension.LoadExtensionActionHelper(&input, &error_msg));
ASSERT_TRUE(action.get());
ASSERT_TRUE(error_msg.empty());
ASSERT_EQ(kTitle, action->GetTitle(1));
ASSERT_EQ(0u, action->icon_paths()->size());
// Invalid title should give an error even with a valid name.
input.Clear();
input.SetInteger(keys::kPageActionDefaultTitle, 42);
input.SetString(keys::kName, name);
action.reset(extension.LoadExtensionActionHelper(&input, &error_msg));
ASSERT_TRUE(NULL == action.get());
ASSERT_STREQ(errors::kInvalidPageActionDefaultTitle, error_msg.c_str());
error_msg = "";
// Invalid name should give an error only with no title.
input.SetString(keys::kPageActionDefaultTitle, kTitle);
input.SetInteger(keys::kName, 123);
action.reset(extension.LoadExtensionActionHelper(&input, &error_msg));
ASSERT_TRUE(NULL != action.get());
ASSERT_EQ(kTitle, action->GetTitle(1));
ASSERT_TRUE(error_msg.empty());
input.Remove(keys::kPageActionDefaultTitle, NULL);
action.reset(extension.LoadExtensionActionHelper(&input, &error_msg));
ASSERT_TRUE(NULL == action.get());
ASSERT_STREQ(errors::kInvalidPageActionName, error_msg.c_str());
error_msg = "";
// Test that keys "popup" and "default_popup" both work, but can not
// be used at the same time.
input.Clear();
input.SetString(keys::kPageActionDefaultTitle, kTitle);
input.SetString(keys::kPageActionDefaultIcon, kIcon);
// LoadExtensionActionHelper expects the extension member |extension_url|
// to be set.
extension.extension_url_ =
GURL(std::string(chrome::kExtensionScheme) +
chrome::kStandardSchemeSeparator +
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/");
// Add key "popup", expect success.
input.SetString(keys::kPageActionPopup, kPopupHtmlFile);
action.reset(extension.LoadExtensionActionHelper(&input, &error_msg));
ASSERT_TRUE(NULL != action.get());
ASSERT_TRUE(error_msg.empty());
ASSERT_STREQ(
extension.url().Resolve(kPopupHtmlFile).spec().c_str(),
action->GetPopupUrl(ExtensionAction::kDefaultTabId).spec().c_str());
// Add key "default_popup", expect failure.
input.SetString(keys::kPageActionDefaultPopup, kPopupHtmlFile);
action.reset(extension.LoadExtensionActionHelper(&input, &error_msg));
ASSERT_TRUE(NULL == action.get());
ASSERT_STREQ(
ExtensionErrorUtils::FormatErrorMessage(
errors::kInvalidPageActionOldAndNewKeys,
keys::kPageActionDefaultPopup,
keys::kPageActionPopup).c_str(),
error_msg.c_str());
error_msg = "";
// Remove key "popup", expect success.
input.Remove(keys::kPageActionPopup, NULL);
action.reset(extension.LoadExtensionActionHelper(&input, &error_msg));
ASSERT_TRUE(NULL != action.get());
ASSERT_TRUE(error_msg.empty());
ASSERT_STREQ(
extension.url().Resolve(kPopupHtmlFile).spec().c_str(),
action->GetPopupUrl(ExtensionAction::kDefaultTabId).spec().c_str());
// Setting default_popup to "" is the same as having no popup.
input.Remove(keys::kPageActionDefaultPopup, NULL);
input.SetString(keys::kPageActionDefaultPopup, "");
action.reset(extension.LoadExtensionActionHelper(&input, &error_msg));
ASSERT_TRUE(NULL != action.get());
ASSERT_TRUE(error_msg.empty());
EXPECT_FALSE(action->HasPopup(ExtensionAction::kDefaultTabId));
ASSERT_STREQ(
"",
action->GetPopupUrl(ExtensionAction::kDefaultTabId).spec().c_str());
// Setting popup to "" is the same as having no popup.
input.Remove(keys::kPageActionDefaultPopup, NULL);
input.SetString(keys::kPageActionPopup, "");
action.reset(extension.LoadExtensionActionHelper(&input, &error_msg));
ASSERT_TRUE(NULL != action.get());
ASSERT_TRUE(error_msg.empty());
EXPECT_FALSE(action->HasPopup(ExtensionAction::kDefaultTabId));
ASSERT_STREQ(
"",
action->GetPopupUrl(ExtensionAction::kDefaultTabId).spec().c_str());
}
TEST(ExtensionTest, IdIsValid) {
EXPECT_TRUE(Extension::IdIsValid("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"));
EXPECT_TRUE(Extension::IdIsValid("pppppppppppppppppppppppppppppppp"));
EXPECT_TRUE(Extension::IdIsValid("abcdefghijklmnopabcdefghijklmnop"));
EXPECT_TRUE(Extension::IdIsValid("ABCDEFGHIJKLMNOPABCDEFGHIJKLMNOP"));
EXPECT_FALSE(Extension::IdIsValid("abcdefghijklmnopabcdefghijklmno"));
EXPECT_FALSE(Extension::IdIsValid("abcdefghijklmnopabcdefghijklmnopa"));
EXPECT_FALSE(Extension::IdIsValid("0123456789abcdef0123456789abcdef"));
EXPECT_FALSE(Extension::IdIsValid("abcdefghijklmnopabcdefghijklmnoq"));
EXPECT_FALSE(Extension::IdIsValid("abcdefghijklmnopabcdefghijklmno0"));
}
TEST(ExtensionTest, GenerateID) {
const uint8 public_key_info[] = {
0x30, 0x81, 0x9f, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x81, 0x8d, 0x00, 0x30, 0x81,
0x89, 0x02, 0x81, 0x81, 0x00, 0xb8, 0x7f, 0x2b, 0x20, 0xdc, 0x7c, 0x9b,
0x0c, 0xdc, 0x51, 0x61, 0x99, 0x0d, 0x36, 0x0f, 0xd4, 0x66, 0x88, 0x08,
0x55, 0x84, 0xd5, 0x3a, 0xbf, 0x2b, 0xa4, 0x64, 0x85, 0x7b, 0x0c, 0x04,
0x13, 0x3f, 0x8d, 0xf4, 0xbc, 0x38, 0x0d, 0x49, 0xfe, 0x6b, 0xc4, 0x5a,
0xb0, 0x40, 0x53, 0x3a, 0xd7, 0x66, 0x09, 0x0f, 0x9e, 0x36, 0x74, 0x30,
0xda, 0x8a, 0x31, 0x4f, 0x1f, 0x14, 0x50, 0xd7, 0xc7, 0x20, 0x94, 0x17,
0xde, 0x4e, 0xb9, 0x57, 0x5e, 0x7e, 0x0a, 0xe5, 0xb2, 0x65, 0x7a, 0x89,
0x4e, 0xb6, 0x47, 0xff, 0x1c, 0xbd, 0xb7, 0x38, 0x13, 0xaf, 0x47, 0x85,
0x84, 0x32, 0x33, 0xf3, 0x17, 0x49, 0xbf, 0xe9, 0x96, 0xd0, 0xd6, 0x14,
0x6f, 0x13, 0x8d, 0xc5, 0xfc, 0x2c, 0x72, 0xba, 0xac, 0xea, 0x7e, 0x18,
0x53, 0x56, 0xa6, 0x83, 0xa2, 0xce, 0x93, 0x93, 0xe7, 0x1f, 0x0f, 0xe6,
0x0f, 0x02, 0x03, 0x01, 0x00, 0x01
};
std::string extension_id;
EXPECT_TRUE(
Extension::GenerateId(
std::string(reinterpret_cast<const char*>(&public_key_info[0]),
arraysize(public_key_info)),
&extension_id));
EXPECT_EQ("melddjfinppjdikinhbgehiennejpfhp", extension_id);
}
TEST(ExtensionTest, UpdateUrls) {
// Test several valid update urls
std::vector<std::string> valid;
valid.push_back("http://test.com");
valid.push_back("http://test.com/");
valid.push_back("http://test.com/update");
valid.push_back("http://test.com/update?check=true");
for (size_t i = 0; i < valid.size(); i++) {
GURL url(valid[i]);
EXPECT_TRUE(url.is_valid());
DictionaryValue input_value;
#if defined(OS_WIN)
// (Why %Iu below? This is the single file in the whole code base that
// might make use of a WidePRIuS; let's not encourage any more.)
FilePath path(base::StringPrintf(L"c:\\extension%Iu", i));
#else
FilePath path(base::StringPrintf("/extension%" PRIuS, i));
#endif
std::string error;
input_value.SetString(keys::kVersion, "1.0");
input_value.SetString(keys::kName, "Test");
input_value.SetString(keys::kUpdateURL, url.spec());
scoped_refptr<Extension> extension(Extension::Create(
path, Extension::INVALID, input_value, Extension::STRICT_ERROR_CHECKS,
&error));
EXPECT_TRUE(extension.get()) << error;
}
// Test some invalid update urls
std::vector<std::string> invalid;
invalid.push_back("");
invalid.push_back("test.com");
valid.push_back("http://test.com/update#whatever");
for (size_t i = 0; i < invalid.size(); i++) {
DictionaryValue input_value;
#if defined(OS_WIN)
// (Why %Iu below? This is the single file in the whole code base that
// might make use of a WidePRIuS; let's not encourage any more.)
FilePath path(base::StringPrintf(L"c:\\extension%Iu", i));
#else
FilePath path(base::StringPrintf("/extension%" PRIuS, i));
#endif
std::string error;
input_value.SetString(keys::kVersion, "1.0");
input_value.SetString(keys::kName, "Test");
input_value.SetString(keys::kUpdateURL, invalid[i]);
scoped_refptr<Extension> extension(Extension::Create(
path, Extension::INVALID, input_value, Extension::STRICT_ERROR_CHECKS,
&error));
EXPECT_FALSE(extension.get());
EXPECT_TRUE(MatchPattern(error, errors::kInvalidUpdateURL));
}
}
// This test ensures that the mimetype sniffing code stays in sync with the
// actual crx files that we test other parts of the system with.
TEST(ExtensionTest, MimeTypeSniffing) {
FilePath path;
ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &path));
path = path.AppendASCII("extensions").AppendASCII("good.crx");
std::string data;
ASSERT_TRUE(file_util::ReadFileToString(path, &data));
std::string result;
EXPECT_TRUE(net::SniffMimeType(data.c_str(), data.size(),
GURL("http://www.example.com/foo.crx"), "", &result));
EXPECT_EQ(std::string(Extension::kMimeType), result);
data.clear();
result.clear();
path = path.DirName().AppendASCII("bad_magic.crx");
ASSERT_TRUE(file_util::ReadFileToString(path, &data));
EXPECT_TRUE(net::SniffMimeType(data.c_str(), data.size(),
GURL("http://www.example.com/foo.crx"), "", &result));
EXPECT_EQ("application/octet-stream", result);
}
static scoped_refptr<Extension> LoadManifest(const std::string& dir,
const std::string& test_file,
int extra_flags) {
FilePath path;
PathService::Get(chrome::DIR_TEST_DATA, &path);
path = path.AppendASCII("extensions")
.AppendASCII(dir)
.AppendASCII(test_file);
JSONFileValueSerializer serializer(path);
std::string error;
scoped_ptr<Value> result(serializer.Deserialize(NULL, &error));
if (!result.get()) {
EXPECT_EQ("", error);
return NULL;
}
scoped_refptr<Extension> extension = Extension::Create(
path.DirName(), Extension::INVALID,
*static_cast<DictionaryValue*>(result.get()),
Extension::STRICT_ERROR_CHECKS | extra_flags, &error);
EXPECT_TRUE(extension) << error;
return extension;
}
static scoped_refptr<Extension> LoadManifest(const std::string& dir,
const std::string& test_file) {
return LoadManifest(dir, test_file, Extension::NO_FLAGS);
}
TEST(ExtensionTest, EffectiveHostPermissions) {
scoped_refptr<Extension> extension;
ExtensionExtent hosts;
extension = LoadManifest("effective_host_permissions", "empty.json");
EXPECT_EQ(0u, extension->GetEffectiveHostPermissions().patterns().size());
EXPECT_FALSE(hosts.ContainsURL(GURL("http://www.google.com")));
EXPECT_FALSE(extension->HasEffectiveAccessToAllHosts());
extension = LoadManifest("effective_host_permissions", "one_host.json");
hosts = extension->GetEffectiveHostPermissions();
EXPECT_TRUE(hosts.ContainsURL(GURL("http://www.google.com")));
EXPECT_FALSE(hosts.ContainsURL(GURL("https://www.google.com")));
EXPECT_FALSE(extension->HasEffectiveAccessToAllHosts());
extension = LoadManifest("effective_host_permissions",
"one_host_wildcard.json");
hosts = extension->GetEffectiveHostPermissions();
EXPECT_TRUE(hosts.ContainsURL(GURL("http://google.com")));
EXPECT_TRUE(hosts.ContainsURL(GURL("http://foo.google.com")));
EXPECT_FALSE(extension->HasEffectiveAccessToAllHosts());
extension = LoadManifest("effective_host_permissions", "two_hosts.json");
hosts = extension->GetEffectiveHostPermissions();
EXPECT_TRUE(hosts.ContainsURL(GURL("http://www.google.com")));
EXPECT_TRUE(hosts.ContainsURL(GURL("http://www.reddit.com")));
EXPECT_FALSE(extension->HasEffectiveAccessToAllHosts());
extension = LoadManifest("effective_host_permissions",
"https_not_considered.json");
hosts = extension->GetEffectiveHostPermissions();
EXPECT_TRUE(hosts.ContainsURL(GURL("http://google.com")));
EXPECT_TRUE(hosts.ContainsURL(GURL("https://google.com")));
EXPECT_FALSE(extension->HasEffectiveAccessToAllHosts());
extension = LoadManifest("effective_host_permissions",
"two_content_scripts.json");
hosts = extension->GetEffectiveHostPermissions();
EXPECT_TRUE(hosts.ContainsURL(GURL("http://google.com")));
EXPECT_TRUE(hosts.ContainsURL(GURL("http://www.reddit.com")));
EXPECT_TRUE(hosts.ContainsURL(GURL("http://news.ycombinator.com")));
EXPECT_FALSE(extension->HasEffectiveAccessToAllHosts());
extension = LoadManifest("effective_host_permissions", "all_hosts.json");
hosts = extension->GetEffectiveHostPermissions();
EXPECT_TRUE(hosts.ContainsURL(GURL("http://test/")));
EXPECT_FALSE(hosts.ContainsURL(GURL("https://test/")));
EXPECT_TRUE(hosts.ContainsURL(GURL("http://www.google.com")));
EXPECT_TRUE(extension->HasEffectiveAccessToAllHosts());
extension = LoadManifest("effective_host_permissions", "all_hosts2.json");
hosts = extension->GetEffectiveHostPermissions();
EXPECT_TRUE(hosts.ContainsURL(GURL("http://test/")));
EXPECT_TRUE(hosts.ContainsURL(GURL("http://www.google.com")));
EXPECT_TRUE(extension->HasEffectiveAccessToAllHosts());
extension = LoadManifest("effective_host_permissions", "all_hosts3.json");
hosts = extension->GetEffectiveHostPermissions();
EXPECT_FALSE(hosts.ContainsURL(GURL("http://test/")));
EXPECT_TRUE(hosts.ContainsURL(GURL("https://test/")));
EXPECT_TRUE(hosts.ContainsURL(GURL("http://www.google.com")));
EXPECT_TRUE(extension->HasEffectiveAccessToAllHosts());
}
TEST(ExtensionTest, IsPrivilegeIncrease) {
const struct {
const char* base_name;
// Increase these sizes if you have more than 10.
const char* granted_apis[10];
const char* granted_hosts[10];
bool full_access;
bool expect_increase;
} kTests[] = {
{ "allhosts1", {NULL}, {"http://*/", NULL}, false,
false }, // all -> all
{ "allhosts2", {NULL}, {"http://*/", NULL}, false,
false }, // all -> one
{ "allhosts3", {NULL}, {NULL}, false, true }, // one -> all
{ "hosts1", {NULL},
{"http://www.google.com/", "http://www.reddit.com/", NULL}, false,
false }, // http://a,http://b -> http://a,http://b
{ "hosts2", {NULL},
{"http://www.google.com/", "http://www.reddit.com/", NULL}, false,
true }, // http://a,http://b -> https://a,http://*.b
{ "hosts3", {NULL},
{"http://www.google.com/", "http://www.reddit.com/", NULL}, false,
false }, // http://a,http://b -> http://a
{ "hosts4", {NULL},
{"http://www.google.com/", NULL}, false,
true }, // http://a -> http://a,http://b
{ "hosts5", {"tabs", "notifications", NULL},
{"http://*.example.com/", "http://*.example.com/*",
"http://*.example.co.uk/*", "http://*.example.com.au/*",
NULL}, false,
false }, // http://a,b,c -> http://a,b,c + https://a,b,c
{ "hosts6", {"tabs", "notifications", NULL},
{"http://*.example.com/", "http://*.example.com/*", NULL}, false,
false }, // http://a.com -> http://a.com + http://a.co.uk
{ "permissions1", {"tabs", NULL},
{NULL}, false, false }, // tabs -> tabs
{ "permissions2", {"tabs", NULL},
{NULL}, false, true }, // tabs -> tabs,bookmarks
{ "permissions3", {NULL},
{"http://*/*", NULL},
false, true }, // http://a -> http://a,tabs
{ "permissions5", {"bookmarks", NULL},
{NULL}, false, true }, // bookmarks -> bookmarks,history
#if !defined(OS_CHROMEOS) // plugins aren't allowed in ChromeOS
{ "permissions4", {NULL},
{NULL}, true, false }, // plugin -> plugin,tabs
{ "plugin1", {NULL},
{NULL}, true, false }, // plugin -> plugin
{ "plugin2", {NULL},
{NULL}, true, false }, // plugin -> none
{ "plugin3", {NULL},
{NULL}, false, true }, // none -> plugin
#endif
{ "storage", {NULL},
{NULL}, false, false }, // none -> storage
{ "notifications", {NULL},
{NULL}, false, false } // none -> notifications
};
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTests); ++i) {
scoped_refptr<Extension> old_extension(
LoadManifest("allow_silent_upgrade",
std::string(kTests[i].base_name) + "_old.json"));
scoped_refptr<Extension> new_extension(
LoadManifest("allow_silent_upgrade",
std::string(kTests[i].base_name) + "_new.json"));
std::set<std::string> granted_apis;
for (size_t j = 0; kTests[i].granted_apis[j] != NULL; ++j)
granted_apis.insert(kTests[i].granted_apis[j]);
ExtensionExtent granted_hosts;
for (size_t j = 0; kTests[i].granted_hosts[j] != NULL; ++j)
AddPattern(&granted_hosts, kTests[i].granted_hosts[j]);
EXPECT_TRUE(new_extension.get()) << kTests[i].base_name << "_new.json";
if (!new_extension.get())
continue;
EXPECT_EQ(kTests[i].expect_increase,
Extension::IsPrivilegeIncrease(kTests[i].full_access,
granted_apis,
granted_hosts,
new_extension.get()))
<< kTests[i].base_name;
}
}
TEST(ExtensionTest, PermissionMessages) {
// Ensure that all permissions that needs to show install UI actually have
// strings associated with them.
std::set<std::string> skip;
// These are considered "nuisance" or "trivial" permissions that don't need
// a prompt.
skip.insert(Extension::kContextMenusPermission);
skip.insert(Extension::kIdlePermission);
skip.insert(Extension::kNotificationPermission);
skip.insert(Extension::kUnlimitedStoragePermission);
skip.insert(Extension::kContentSettingsPermission);
// TODO(erikkay) add a string for this permission.
skip.insert(Extension::kBackgroundPermission);
// The cookie permission does nothing unless you have associated host
// permissions.
skip.insert(Extension::kCookiePermission);
// The proxy permission is warned as part of host permission checks.
skip.insert(Extension::kProxyPermission);
// This permission requires explicit user action (context menu handler)
// so we won't prompt for it for now.
skip.insert(Extension::kFileBrowserHandlerPermission);
// If you've turned on the experimental command-line flag, we don't need
// to warn you further.
skip.insert(Extension::kExperimentalPermission);
// These are only usable by component extensions.
skip.insert(Extension::kWebstorePrivatePermission);
skip.insert(Extension::kFileBrowserPrivatePermission);
skip.insert(Extension::kChromeosInfoPrivatePermissions);
const Extension::PermissionMessage::MessageId ID_NONE =
Extension::PermissionMessage::ID_NONE;
for (size_t i = 0; i < Extension::kNumPermissions; ++i) {
Extension::Permission permission = Extension::kPermissions[i];
if (skip.count(permission.name)) {
EXPECT_EQ(ID_NONE, permission.message_id)
<< "unexpected message_id for " << permission.name;
} else {
EXPECT_NE(ID_NONE, permission.message_id)
<< "missing message_id for " << permission.name;
}
}
}
// Returns a copy of |source| resized to |size| x |size|.
static SkBitmap ResizedCopy(const SkBitmap& source, int size) {
return skia::ImageOperations::Resize(source,
skia::ImageOperations::RESIZE_LANCZOS3,
size,
size);
}
static bool SizeEquals(const SkBitmap& bitmap, const gfx::Size& size) {
return bitmap.width() == size.width() && bitmap.height() == size.height();
}
TEST(ExtensionTest, ImageCaching) {
FilePath path;
ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &path));
path = path.AppendASCII("extensions");
// Initialize the Extension.
std::string errors;
DictionaryValue values;
values.SetString(keys::kName, "test");
values.SetString(keys::kVersion, "0.1");
scoped_refptr<Extension> extension(Extension::Create(
path, Extension::INVALID, values, Extension::STRICT_ERROR_CHECKS,
&errors));
ASSERT_TRUE(extension.get());
// Create an ExtensionResource pointing at an icon.
FilePath icon_relative_path(FILE_PATH_LITERAL("icon3.png"));
ExtensionResource resource(extension->id(),
extension->path(),
icon_relative_path);
// Read in the icon file.
FilePath icon_absolute_path = extension->path().Append(icon_relative_path);
std::string raw_png;
ASSERT_TRUE(file_util::ReadFileToString(icon_absolute_path, &raw_png));
SkBitmap image;
ASSERT_TRUE(gfx::PNGCodec::Decode(
reinterpret_cast<const unsigned char*>(raw_png.data()),
raw_png.length(),
&image));
// Make sure the icon file is the size we expect.
gfx::Size original_size(66, 66);
ASSERT_EQ(image.width(), original_size.width());
ASSERT_EQ(image.height(), original_size.height());
// Create two resized versions at size 16x16 and 24x24.
SkBitmap image16 = ResizedCopy(image, 16);
SkBitmap image24 = ResizedCopy(image, 24);
gfx::Size size16(16, 16);
gfx::Size size24(24, 24);
// Cache the 16x16 copy.
EXPECT_FALSE(extension->HasCachedImage(resource, size16));
extension->SetCachedImage(resource, image16, original_size);
EXPECT_TRUE(extension->HasCachedImage(resource, size16));
EXPECT_TRUE(SizeEquals(extension->GetCachedImage(resource, size16), size16));
EXPECT_FALSE(extension->HasCachedImage(resource, size24));
EXPECT_FALSE(extension->HasCachedImage(resource, original_size));
// Cache the 24x24 copy.
extension->SetCachedImage(resource, image24, original_size);
EXPECT_TRUE(extension->HasCachedImage(resource, size24));
EXPECT_TRUE(SizeEquals(extension->GetCachedImage(resource, size24), size24));
EXPECT_FALSE(extension->HasCachedImage(resource, original_size));
// Cache the original, and verify that it gets returned when we ask for a
// max_size that is larger than the original.
gfx::Size size128(128, 128);
EXPECT_TRUE(image.width() < size128.width() &&
image.height() < size128.height());
extension->SetCachedImage(resource, image, original_size);
EXPECT_TRUE(extension->HasCachedImage(resource, original_size));
EXPECT_TRUE(extension->HasCachedImage(resource, size128));
EXPECT_TRUE(SizeEquals(extension->GetCachedImage(resource, original_size),
original_size));
EXPECT_TRUE(SizeEquals(extension->GetCachedImage(resource, size128),
original_size));
EXPECT_EQ(extension->GetCachedImage(resource, original_size).getPixels(),
extension->GetCachedImage(resource, size128).getPixels());
}
// Tests that the old permission name "unlimited_storage" still works for
// backwards compatibility (we renamed it to "unlimitedStorage").
TEST(ExtensionTest, OldUnlimitedStoragePermission) {
ScopedTempDir directory;
ASSERT_TRUE(directory.CreateUniqueTempDir());
FilePath extension_path = directory.path();
DictionaryValue dictionary;
// The two required keys.
dictionary.SetString(extension_manifest_keys::kName, "test");
dictionary.SetString(extension_manifest_keys::kVersion, "0.1");
// Create a permissions list containing "unlimited_storage" and add it.
ListValue* permissions = new ListValue();
const char* old_unlimited = "unlimited_storage";
EXPECT_STREQ(old_unlimited, Extension::kOldUnlimitedStoragePermission);
permissions->Append(Value::CreateStringValue(old_unlimited));
dictionary.Set(extension_manifest_keys::kPermissions, permissions);
// Initialize the extension and make sure the permission for unlimited storage
// is present.
std::string errors;
scoped_refptr<Extension> extension(Extension::Create(
extension_path, Extension::INVALID, dictionary,
Extension::STRICT_ERROR_CHECKS, &errors));
EXPECT_TRUE(extension.get());
EXPECT_TRUE(extension->HasApiPermission(
Extension::kUnlimitedStoragePermission));
}
// This tests the API permissions with an empty manifest (one that just
// specifies a name and a version and nothing else).
TEST(ExtensionTest, ApiPermissions) {
const struct {
const char* permission_name;
bool expect_success;
} kTests[] = {
// Negative test.
{ "non_existing_permission", false },
// Test default module/package permission.
{ "browserAction", true },
{ "browserActions", true },
{ "devtools", true },
{ "extension", true },
{ "i18n", true },
{ "pageAction", true },
{ "pageActions", true },
{ "test", true },
// Some negative tests.
{ "bookmarks", false },
{ "cookies", false },
{ "history", false },
{ "tabs.onUpdated", false },
// Make sure we find the module name after stripping '.' and '/'.
{ "browserAction/abcd/onClick", true },
{ "browserAction.abcd.onClick", true },
// Test Tabs functions.
{ "tabs.create", true},
{ "tabs.update", true},
{ "tabs.getSelected", false},
};
scoped_refptr<Extension> extension;
extension = LoadManifest("empty_manifest", "empty.json");
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTests); ++i) {
EXPECT_EQ(kTests[i].expect_success,
extension->HasApiPermission(kTests[i].permission_name))
<< "Permission being tested: " << kTests[i].permission_name;
}
}
TEST(ExtensionTest, GetHostPermissionMessages_ManyHosts) {
scoped_refptr<Extension> extension;
extension = LoadManifest("permissions", "many-hosts.json");
std::vector<string16> warnings = extension->GetPermissionMessageStrings();
ASSERT_EQ(1u, warnings.size());
EXPECT_EQ("Your data on www.google.com and encrypted.google.com",
UTF16ToUTF8(warnings[0]));
}
TEST(ExtensionTest, GetPermissionMessages_Plugins) {
scoped_refptr<Extension> extension;
extension = LoadManifest("permissions", "plugins.json");
std::vector<string16> warnings = extension->GetPermissionMessageStrings();
// We don't parse the plugins key on Chrome OS, so it should not ask for any
// permissions.
#if defined(OS_CHROMEOS)
ASSERT_EQ(0u, warnings.size());
#else
ASSERT_EQ(1u, warnings.size());
EXPECT_EQ("All data on your computer and the websites you visit",
UTF16ToUTF8(warnings[0]));
#endif
}
TEST(ExtensionTest, WantsFileAccess) {
scoped_refptr<Extension> extension;
GURL file_url("file:///etc/passwd");
// <all_urls> permission
extension = LoadManifest("permissions", "permissions_all_urls.json");
EXPECT_TRUE(extension->wants_file_access());
EXPECT_FALSE(extension->CanExecuteScriptOnPage(file_url, NULL, NULL));
extension = LoadManifest(
"permissions", "permissions_all_urls.json", Extension::ALLOW_FILE_ACCESS);
EXPECT_TRUE(extension->wants_file_access());
EXPECT_TRUE(extension->CanExecuteScriptOnPage(file_url, NULL, NULL));
// file:///* permission
extension = LoadManifest("permissions", "permissions_file_scheme.json");
EXPECT_TRUE(extension->wants_file_access());
EXPECT_FALSE(extension->CanExecuteScriptOnPage(file_url, NULL, NULL));
extension = LoadManifest("permissions", "permissions_file_scheme.json",
Extension::ALLOW_FILE_ACCESS);
EXPECT_TRUE(extension->wants_file_access());
EXPECT_TRUE(extension->CanExecuteScriptOnPage(file_url, NULL, NULL));
// http://* permission
extension = LoadManifest("permissions", "permissions_http_scheme.json");
EXPECT_FALSE(extension->wants_file_access());
EXPECT_FALSE(extension->CanExecuteScriptOnPage(file_url, NULL, NULL));
extension = LoadManifest("permissions", "permissions_http_scheme.json",
Extension::ALLOW_FILE_ACCESS);
EXPECT_FALSE(extension->wants_file_access());
EXPECT_FALSE(extension->CanExecuteScriptOnPage(file_url, NULL, NULL));
// <all_urls> content script match
extension = LoadManifest("permissions", "content_script_all_urls.json");
EXPECT_TRUE(extension->wants_file_access());
EXPECT_FALSE(extension->CanExecuteScriptOnPage(
file_url, &extension->content_scripts()[0], NULL));
extension = LoadManifest("permissions", "content_script_all_urls.json",
Extension::ALLOW_FILE_ACCESS);
EXPECT_TRUE(extension->wants_file_access());
EXPECT_TRUE(extension->CanExecuteScriptOnPage(
file_url, &extension->content_scripts()[0], NULL));
// file:///* content script match
extension = LoadManifest("permissions", "content_script_file_scheme.json");
EXPECT_TRUE(extension->wants_file_access());
EXPECT_FALSE(extension->CanExecuteScriptOnPage(
file_url, &extension->content_scripts()[0], NULL));
extension = LoadManifest("permissions", "content_script_file_scheme.json",
Extension::ALLOW_FILE_ACCESS);
EXPECT_TRUE(extension->wants_file_access());
EXPECT_TRUE(extension->CanExecuteScriptOnPage(
file_url, &extension->content_scripts()[0], NULL));
// http://* content script match
extension = LoadManifest("permissions", "content_script_http_scheme.json");
EXPECT_FALSE(extension->wants_file_access());
EXPECT_FALSE(extension->CanExecuteScriptOnPage(
file_url, &extension->content_scripts()[0], NULL));
extension = LoadManifest("permissions", "content_script_http_scheme.json",
Extension::ALLOW_FILE_ACCESS);
EXPECT_FALSE(extension->wants_file_access());
EXPECT_FALSE(extension->CanExecuteScriptOnPage(
file_url, &extension->content_scripts()[0], NULL));
}
// Base class for testing the CanExecuteScriptOnPage and CanCaptureVisiblePage
// methods of Extension for extensions with various permissions.
class ExtensionScriptAndCaptureVisibleTest : public testing::Test {
public:
ExtensionScriptAndCaptureVisibleTest() {
PathService::Get(chrome::DIR_TEST_DATA, &dirpath_);
}
scoped_refptr<Extension> MakeExtension(const std::string& permissions,
Extension::Location location) {
// Replace single-quotes with double-quotes in permissions, since JSON
// mandates double-quotes.
std::string munged_permissions = permissions;
ReplaceSubstringsAfterOffset(&munged_permissions, 0, "'", "\"");
DictionaryValue dictionary;
dictionary.SetString(keys::kName, "permission test");
dictionary.SetString(keys::kVersion, "1");
std::string error;
JSONStringValueSerializer serializer(munged_permissions);
scoped_ptr<Value> permission_value(serializer.Deserialize(NULL, &error));
EXPECT_EQ("", error);
if (!permission_value.get())
return NULL;
EXPECT_TRUE(permission_value->IsType(Value::TYPE_LIST));
dictionary.Set(keys::kPermissions, permission_value.release());
FilePath dirpath;
PathService::Get(chrome::DIR_TEST_DATA, &dirpath);
dirpath = dirpath.AppendASCII("extensions").AppendASCII("permissions");
scoped_refptr<Extension> extension = Extension::Create(
dirpath,
location,
dictionary,
Extension::STRICT_ERROR_CHECKS,
&error);
if (!extension)
VLOG(1) << error;
return extension;
}
bool Allowed(const Extension* extension, const GURL& url) {
return (extension->CanExecuteScriptOnPage(url, NULL, NULL) &&
extension->CanCaptureVisiblePage(url, NULL));
}
bool CaptureOnly(const Extension* extension, const GURL& url) {
return !extension->CanExecuteScriptOnPage(url, NULL, NULL) &&
extension->CanCaptureVisiblePage(url, NULL);
}
bool Blocked(const Extension* extension, const GURL& url) {
return !(extension->CanExecuteScriptOnPage(url, NULL, NULL) ||
extension->CanCaptureVisiblePage(url, NULL));
}
protected:
FilePath dirpath_;
};
TEST_F(ExtensionScriptAndCaptureVisibleTest, Permissions) {
scoped_refptr<Extension> extension;
// URLs that are "safe" to provide scripting and capture visible tab access
// to if the permissions allow it.
GURL http_url("http://www.google.com");
GURL https_url("https://www.google.com");
GURL file_url("file:///foo/bar");
// We should allow host permission but not scripting permission for favicon
// urls.
GURL favicon_url("chrome://favicon/http://www.google.com");
std::string dummy_id =
Extension::GenerateIdForPath(FilePath(FILE_PATH_LITERAL("whatever")));
// URLs that regular extensions should never get access to.
GURL extension_url("chrome-extension://" + dummy_id);
GURL settings_url("chrome://settings");
GURL about_url("about:flags");
// Test <all_urls> for regular extensions.
extension = MakeExtension("['tabs','<all_urls>']", Extension::INTERNAL);
EXPECT_TRUE(Allowed(extension, http_url));
EXPECT_TRUE(Allowed(extension, https_url));
EXPECT_TRUE(Blocked(extension, file_url));
EXPECT_TRUE(Blocked(extension, settings_url));
EXPECT_TRUE(CaptureOnly(extension, favicon_url));
EXPECT_TRUE(Blocked(extension, about_url));
EXPECT_TRUE(Blocked(extension, extension_url));
EXPECT_FALSE(extension->HasHostPermission(settings_url));
EXPECT_FALSE(extension->HasHostPermission(about_url));
EXPECT_TRUE(extension->HasHostPermission(favicon_url));
// Test * for scheme, which implies just the http/https schemes.
extension = MakeExtension("['tabs','*://*/']", Extension::INTERNAL);
EXPECT_TRUE(Allowed(extension, http_url));
EXPECT_TRUE(Allowed(extension, https_url));
EXPECT_TRUE(Blocked(extension, settings_url));
EXPECT_TRUE(Blocked(extension, about_url));
EXPECT_TRUE(Blocked(extension, file_url));
EXPECT_TRUE(Blocked(extension, favicon_url));
extension = MakeExtension("['tabs','*://settings/*']", Extension::INTERNAL);
EXPECT_TRUE(Blocked(extension, settings_url));
// Having chrome://*/ should not work for regular extensions. Note that
// for favicon access, we require the explicit pattern chrome://favicon/*.
extension = MakeExtension("['tabs','chrome://*/']",
Extension::INTERNAL);
EXPECT_TRUE(extension == NULL);
// Having chrome://favicon/* should not give you chrome://*
extension = MakeExtension("['tabs','chrome://favicon/*']",
Extension::INTERNAL);
EXPECT_TRUE(Blocked(extension, settings_url));
EXPECT_TRUE(CaptureOnly(extension, favicon_url));
EXPECT_TRUE(Blocked(extension, about_url));
EXPECT_TRUE(extension->HasHostPermission(favicon_url));
// Having http://favicon should not give you chrome://favicon
extension = MakeExtension("['tabs', 'http://favicon/']", Extension::INTERNAL);
EXPECT_TRUE(Blocked(extension, settings_url));
EXPECT_TRUE(Blocked(extension, favicon_url));
// Component extensions with <all_urls> should get everything.
extension = MakeExtension("['tabs','<all_urls>']", Extension::COMPONENT);
EXPECT_TRUE(Allowed(extension, http_url));
EXPECT_TRUE(Allowed(extension, https_url));
EXPECT_TRUE(Allowed(extension, settings_url));
EXPECT_TRUE(Allowed(extension, about_url));
EXPECT_TRUE(Allowed(extension, favicon_url));
EXPECT_TRUE(extension->HasHostPermission(favicon_url));
// Component extensions should only get access to what they ask for.
extension = MakeExtension("['tabs', 'http://www.google.com/']",
Extension::COMPONENT);
EXPECT_TRUE(Allowed(extension, http_url));
EXPECT_TRUE(Blocked(extension, https_url));
EXPECT_TRUE(Blocked(extension, file_url));
EXPECT_TRUE(Blocked(extension, settings_url));
EXPECT_TRUE(Blocked(extension, favicon_url));
EXPECT_TRUE(Blocked(extension, about_url));
EXPECT_TRUE(Blocked(extension, extension_url));
EXPECT_FALSE(extension->HasHostPermission(settings_url));
}
TEST(ExtensionTest, GetDistinctHostsForDisplay) {
std::vector<std::string> expected;
expected.push_back("www.foo.com");
expected.push_back("www.bar.com");
expected.push_back("www.baz.com");
URLPatternList actual;
{
SCOPED_TRACE("no dupes");
// Simple list with no dupes.
actual.push_back(
URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.com/path"));
actual.push_back(
URLPattern(URLPattern::SCHEME_HTTP, "http://www.bar.com/path"));
actual.push_back(
URLPattern(URLPattern::SCHEME_HTTP, "http://www.baz.com/path"));
CompareLists(expected,
Extension::GetDistinctHostsForDisplay(actual));
}
{
SCOPED_TRACE("two dupes");
// Add some dupes.
actual.push_back(
URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.com/path"));
actual.push_back(
URLPattern(URLPattern::SCHEME_HTTP, "http://www.baz.com/path"));
CompareLists(expected,
Extension::GetDistinctHostsForDisplay(actual));
}
{
SCOPED_TRACE("schemes differ");
// Add a pattern that differs only by scheme. This should be filtered out.
actual.push_back(
URLPattern(URLPattern::SCHEME_HTTPS, "https://www.bar.com/path"));
CompareLists(expected,
Extension::GetDistinctHostsForDisplay(actual));
}
{
SCOPED_TRACE("paths differ");
// Add some dupes by path.
actual.push_back(
URLPattern(URLPattern::SCHEME_HTTP, "http://www.bar.com/pathypath"));
CompareLists(expected,
Extension::GetDistinctHostsForDisplay(actual));
}
{
SCOPED_TRACE("subdomains differ");
// We don't do anything special for subdomains.
actual.push_back(
URLPattern(URLPattern::SCHEME_HTTP, "http://monkey.www.bar.com/path"));
actual.push_back(
URLPattern(URLPattern::SCHEME_HTTP, "http://bar.com/path"));
expected.push_back("monkey.www.bar.com");
expected.push_back("bar.com");
CompareLists(expected,
Extension::GetDistinctHostsForDisplay(actual));
}
{
SCOPED_TRACE("RCDs differ");
// Now test for RCD uniquing.
actual.push_back(
URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.com/path"));
actual.push_back(
URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.co.uk/path"));
actual.push_back(
URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.de/path"));
actual.push_back(
URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.ca.us/path"));
actual.push_back(
URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.net/path"));
actual.push_back(
URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.com.my/path"));
// This is an unknown RCD, which shouldn't be uniqued out.
actual.push_back(
URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.xyzzy/path"));
// But it should only occur once.
actual.push_back(
URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.xyzzy/path"));
expected.push_back("www.foo.xyzzy");
CompareLists(expected,
Extension::GetDistinctHostsForDisplay(actual));
}
{
SCOPED_TRACE("wildcards");
actual.push_back(
URLPattern(URLPattern::SCHEME_HTTP, "http://*.google.com/*"));
expected.push_back("*.google.com");
CompareLists(expected,
Extension::GetDistinctHostsForDisplay(actual));
}
}
TEST(ExtensionTest, GetDistinctHostsForDisplay_ComIsBestRcd) {
URLPatternList actual;
actual.push_back(
URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.ca/path"));
actual.push_back(
URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.org/path"));
actual.push_back(
URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.co.uk/path"));
actual.push_back(
URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.net/path"));
actual.push_back(
URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.jp/path"));
actual.push_back(
URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.com/path"));
std::vector<std::string> expected;
expected.push_back("www.foo.com");
CompareLists(expected,
Extension::GetDistinctHostsForDisplay(actual));
}
TEST(ExtensionTest, GetDistinctHostsForDisplay_NetIs2ndBestRcd) {
URLPatternList actual;
actual.push_back(
URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.ca/path"));
actual.push_back(
URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.org/path"));
actual.push_back(
URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.co.uk/path"));
actual.push_back(
URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.net/path"));
actual.push_back(
URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.jp/path"));
// No http://www.foo.com/path
std::vector<std::string> expected;
expected.push_back("www.foo.net");
CompareLists(expected,
Extension::GetDistinctHostsForDisplay(actual));
}
TEST(ExtensionTest, GetDistinctHostsForDisplay_OrgIs3rdBestRcd) {
URLPatternList actual;
actual.push_back(
URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.ca/path"));
actual.push_back(
URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.org/path"));
actual.push_back(
URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.co.uk/path"));
// No http://www.foo.net/path
actual.push_back(
URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.jp/path"));
// No http://www.foo.com/path
std::vector<std::string> expected;
expected.push_back("www.foo.org");
CompareLists(expected,
Extension::GetDistinctHostsForDisplay(actual));
}
TEST(ExtensionTest, GetDistinctHostsForDisplay_FirstInListIs4thBestRcd) {
URLPatternList actual;
actual.push_back(
URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.ca/path"));
// No http://www.foo.org/path
actual.push_back(
URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.co.uk/path"));
// No http://www.foo.net/path
actual.push_back(
URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.jp/path"));
// No http://www.foo.com/path
std::vector<std::string> expected;
expected.push_back("www.foo.ca");
CompareLists(expected,
Extension::GetDistinctHostsForDisplay(actual));
}
TEST(ExtensionTest, IsElevatedHostList) {
URLPatternList list1;
URLPatternList list2;
list1.push_back(
URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com.hk/path"));
list1.push_back(
URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com/path"));
// Test that the host order does not matter.
list2.push_back(
URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com/path"));
list2.push_back(
URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com.hk/path"));
EXPECT_FALSE(Extension::IsElevatedHostList(list1, list2));
EXPECT_FALSE(Extension::IsElevatedHostList(list2, list1));
// Test that paths are ignored.
list2.clear();
list2.push_back(
URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com/*"));
EXPECT_FALSE(Extension::IsElevatedHostList(list1, list2));
EXPECT_FALSE(Extension::IsElevatedHostList(list2, list1));
// Test that RCDs are ignored.
list2.clear();
list2.push_back(
URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com.hk/*"));
EXPECT_FALSE(Extension::IsElevatedHostList(list1, list2));
EXPECT_FALSE(Extension::IsElevatedHostList(list2, list1));
// Test that subdomain wildcards are handled properly.
list2.clear();
list2.push_back(
URLPattern(URLPattern::SCHEME_HTTP, "http://*.google.com.hk/*"));
EXPECT_TRUE(Extension::IsElevatedHostList(list1, list2));
//TODO(jstritar): Does not match subdomains properly. http://crbug.com/65337
//EXPECT_FALSE(Extension::IsElevatedHostList(list2, list1));
// Test that different domains count as different hosts.
list2.clear();
list2.push_back(
URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com/path"));
list2.push_back(
URLPattern(URLPattern::SCHEME_HTTP, "http://www.example.org/path"));
EXPECT_TRUE(Extension::IsElevatedHostList(list1, list2));
EXPECT_FALSE(Extension::IsElevatedHostList(list2, list1));
// Test that different subdomains count as different hosts.
list2.clear();
list2.push_back(
URLPattern(URLPattern::SCHEME_HTTP, "http://mail.google.com/*"));
EXPECT_TRUE(Extension::IsElevatedHostList(list1, list2));
EXPECT_TRUE(Extension::IsElevatedHostList(list2, list1));
}
TEST(ExtensionTest, GenerateId) {
std::string result;
EXPECT_TRUE(Extension::GenerateId("", &result));
EXPECT_TRUE(Extension::GenerateId("test", &result));
EXPECT_EQ(result, "jpignaibiiemhngfjkcpokkamffknabf");
EXPECT_TRUE(Extension::GenerateId("_", &result));
EXPECT_EQ(result, "ncocknphbhhlhkikpnnlmbcnbgdempcd");
EXPECT_TRUE(Extension::GenerateId(
"this_string_is_longer_than_a_single_sha256_hash_digest", &result));
EXPECT_EQ(result, "jimneklojkjdibfkgiiophfhjhbdgcfi");
}