Commit 7e06e6a3 authored by Devlin Cronin's avatar Devlin Cronin Committed by Commit Bot

[Extensions] Add support for matching data URLs in manifest parsing

Add support for a "match_data_urls" field in content scripts specified
in an extension's manifest. This CL adds the parsing logic and related
unittests, behind a base::Feature. Future CLs will add support for
the actual injection of scripts into these data URLs, as well as
expanding the support to include dynamic script injections.

Bug: 55084

Change-Id: I288c66dfb901e9cb77d09562cf943a223adcb8d7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2292697Reviewed-by: default avatarKaran Bhatia <karandeepb@chromium.org>
Commit-Queue: Devlin <rdevlin.cronin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#791068}
parent 2112dd99
...@@ -7,11 +7,13 @@ ...@@ -7,11 +7,13 @@
#include "base/path_service.h" #include "base/path_service.h"
#include "base/stl_util.h" #include "base/stl_util.h"
#include "base/strings/string_number_conversions.h" #include "base/strings/string_number_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/common/chrome_paths.h" #include "chrome/common/chrome_paths.h"
#include "chrome/common/extensions/manifest_tests/chrome_manifest_test.h" #include "chrome/common/extensions/manifest_tests/chrome_manifest_test.h"
#include "chrome/common/webui_url_constants.h" #include "chrome/common/webui_url_constants.h"
#include "extensions/common/error_utils.h" #include "extensions/common/error_utils.h"
#include "extensions/common/extension.h" #include "extensions/common/extension.h"
#include "extensions/common/extension_features.h"
#include "extensions/common/file_util.h" #include "extensions/common/file_util.h"
#include "extensions/common/manifest_constants.h" #include "extensions/common/manifest_constants.h"
#include "extensions/common/manifest_handlers/content_scripts_handler.h" #include "extensions/common/manifest_handlers/content_scripts_handler.h"
...@@ -102,4 +104,43 @@ TEST_F(ContentScriptsManifestTest, FailLoadingNonUTF8Scripts) { ...@@ -102,4 +104,43 @@ TEST_F(ContentScriptsManifestTest, FailLoadingNonUTF8Scripts) {
error.c_str()); error.c_str());
} }
TEST_F(ContentScriptsManifestTest, MatchDataURLs_FeatureEnabled) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(
extensions_features::kContentScriptsOnDataUrls);
scoped_refptr<const Extension> extension =
LoadAndExpectSuccess("content_script_match_data_urls.json");
ASSERT_TRUE(extension);
const UserScriptList& user_scripts =
ContentScriptsInfo::GetContentScripts(extension.get());
ASSERT_EQ(3u, user_scripts.size());
// The first script specifies `"match_data_urls": true`.
EXPECT_TRUE(user_scripts[0]->match_data_urls());
// The second specifies `"match_data_urls": false`.
EXPECT_FALSE(user_scripts[1]->match_data_urls());
// The third doesn't specify a value for "match_data_urls"; it should
// default to false.
EXPECT_FALSE(user_scripts[2]->match_data_urls());
}
TEST_F(ContentScriptsManifestTest, MatchDataURLs_FeatureDisabled) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndDisableFeature(
extensions_features::kContentScriptsOnDataUrls);
scoped_refptr<const Extension> extension =
LoadAndExpectSuccess("content_script_match_data_urls.json");
ASSERT_TRUE(extension);
const UserScriptList& user_scripts =
ContentScriptsInfo::GetContentScripts(extension.get());
ASSERT_EQ(3u, user_scripts.size());
// Without the feature enabled, match_data_urls should always be false.
EXPECT_FALSE(user_scripts[0]->match_data_urls());
EXPECT_FALSE(user_scripts[1]->match_data_urls());
EXPECT_FALSE(user_scripts[2]->match_data_urls());
}
} // namespace extensions } // namespace extensions
{
"name": "Content scripts matching data urls",
"description": "ContentScriptsManifestTest.MatchDataURLs*",
"version": "0.1",
"manifest_version": 2,
"content_scripts": [{
"matches": ["https://example.com/*"],
"match_data_urls": true,
"js": ["file.js"]
}, {
"matches": ["https://foo.example/*"],
"match_data_urls": false,
"js": ["file.js"]
}, {
"matches": ["https://bar.example/*"],
"js": ["file.js"]
}]
}
...@@ -43,6 +43,10 @@ const base::Feature kAllowWithholdingExtensionPermissionsOnInstall{ ...@@ -43,6 +43,10 @@ const base::Feature kAllowWithholdingExtensionPermissionsOnInstall{
"AllowWithholdingExtensionPermissionsOnInstall", "AllowWithholdingExtensionPermissionsOnInstall",
base::FEATURE_DISABLED_BY_DEFAULT}; base::FEATURE_DISABLED_BY_DEFAULT};
// Enables content scripts on data URLs.
const base::Feature kContentScriptsOnDataUrls{
"ContentScriptsOnDataUrls", base::FEATURE_DISABLED_BY_DEFAULT};
// Reports Extensions.WebRequest.KeepaliveRequestFinished when enabled. // Reports Extensions.WebRequest.KeepaliveRequestFinished when enabled.
const base::Feature kReportKeepaliveUkm{"ReportKeepaliveUkm", const base::Feature kReportKeepaliveUkm{"ReportKeepaliveUkm",
base::FEATURE_ENABLED_BY_DEFAULT}; base::FEATURE_ENABLED_BY_DEFAULT};
......
...@@ -25,6 +25,8 @@ extern const base::Feature kForceWebRequestProxyForTest; ...@@ -25,6 +25,8 @@ extern const base::Feature kForceWebRequestProxyForTest;
extern const base::Feature kAllowWithholdingExtensionPermissionsOnInstall; extern const base::Feature kAllowWithholdingExtensionPermissionsOnInstall;
extern const base::Feature kContentScriptsOnDataUrls;
extern const base::Feature kReportKeepaliveUkm; extern const base::Feature kReportKeepaliveUkm;
} // namespace extensions_features } // namespace extensions_features
......
...@@ -105,6 +105,7 @@ const char kLinkedAppIconURL[] = "url"; ...@@ -105,6 +105,7 @@ const char kLinkedAppIconURL[] = "url";
const char kLinkedAppIconSize[] = "size"; const char kLinkedAppIconSize[] = "size";
const char kManifestVersion[] = "manifest_version"; const char kManifestVersion[] = "manifest_version";
const char kMatchAboutBlank[] = "match_about_blank"; const char kMatchAboutBlank[] = "match_about_blank";
const char kMatchDataUrls[] = "match_data_urls";
const char kMatches[] = "matches"; const char kMatches[] = "matches";
const char kMinimumChromeVersion[] = "minimum_chrome_version"; const char kMinimumChromeVersion[] = "minimum_chrome_version";
const char kMinimumVersion[] = "minimum_version"; const char kMinimumVersion[] = "minimum_version";
...@@ -568,6 +569,8 @@ const char kInvalidMatch[] = ...@@ -568,6 +569,8 @@ const char kInvalidMatch[] =
"Invalid value for 'content_scripts[*].matches[*]': *"; "Invalid value for 'content_scripts[*].matches[*]': *";
const char kInvalidMatchAboutBlank[] = const char kInvalidMatchAboutBlank[] =
"Invalid value for 'content_scripts[*].match_about_blank'."; "Invalid value for 'content_scripts[*].match_about_blank'.";
const char kInvalidMatchDataUrls[] =
"Invalid value for 'content_scripts[*].match_data_urls'.";
const char kInvalidMatchCount[] = const char kInvalidMatchCount[] =
"Invalid value for 'content_scripts[*].matches'. There must be at least " "Invalid value for 'content_scripts[*].matches'. There must be at least "
"one match specified."; "one match specified.";
......
...@@ -108,6 +108,7 @@ extern const char kLinkedAppIconURL[]; ...@@ -108,6 +108,7 @@ extern const char kLinkedAppIconURL[];
extern const char kLinkedAppIconSize[]; extern const char kLinkedAppIconSize[];
extern const char kManifestVersion[]; extern const char kManifestVersion[];
extern const char kMatchAboutBlank[]; extern const char kMatchAboutBlank[];
extern const char kMatchDataUrls[];
extern const char kMatches[]; extern const char kMatches[];
extern const char kMIMETypes[]; extern const char kMIMETypes[];
extern const char kMimeTypesHandler[]; extern const char kMimeTypesHandler[];
...@@ -418,6 +419,7 @@ extern const char kInvalidManifestVersion[]; ...@@ -418,6 +419,7 @@ extern const char kInvalidManifestVersion[];
extern const char kInvalidManifestVersionOld[]; extern const char kInvalidManifestVersionOld[];
extern const char kInvalidMatch[]; extern const char kInvalidMatch[];
extern const char kInvalidMatchAboutBlank[]; extern const char kInvalidMatchAboutBlank[];
extern const char kInvalidMatchDataUrls[];
extern const char kInvalidMatchCount[]; extern const char kInvalidMatchCount[];
extern const char kInvalidMatches[]; extern const char kInvalidMatches[];
extern const char kInvalidMIMETypes[]; extern const char kInvalidMIMETypes[];
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
#include "content/public/common/url_constants.h" #include "content/public/common/url_constants.h"
#include "extensions/common/error_utils.h" #include "extensions/common/error_utils.h"
#include "extensions/common/extension.h" #include "extensions/common/extension.h"
#include "extensions/common/extension_features.h"
#include "extensions/common/extension_resource.h" #include "extensions/common/extension_resource.h"
#include "extensions/common/host_id.h" #include "extensions/common/host_id.h"
#include "extensions/common/manifest_constants.h" #include "extensions/common/manifest_constants.h"
...@@ -126,6 +127,22 @@ std::unique_ptr<UserScript> LoadUserScriptFromDictionary( ...@@ -126,6 +127,22 @@ std::unique_ptr<UserScript> LoadUserScriptFromDictionary(
result->set_match_about_blank(match_about_blank->GetBool()); result->set_match_about_blank(match_about_blank->GetBool());
} }
// match data urls
if (base::FeatureList::IsEnabled(
extensions_features::kContentScriptsOnDataUrls)) {
const base::Value* match_data_urls =
content_script.FindKey(keys::kMatchDataUrls);
if (match_data_urls) {
if (!match_data_urls->is_bool()) {
*error = ErrorUtils::FormatErrorMessageUTF16(
errors::kInvalidMatchDataUrls,
base::NumberToString(definition_index));
return nullptr;
}
result->set_match_data_urls(match_data_urls->GetBool());
}
}
// matches (required) // matches (required)
const base::Value* matches = const base::Value* matches =
content_script.FindKeyOfType(keys::kMatches, base::Value::Type::LIST); content_script.FindKeyOfType(keys::kMatches, base::Value::Type::LIST);
......
...@@ -98,6 +98,7 @@ UserScript::UserScript() ...@@ -98,6 +98,7 @@ UserScript::UserScript()
emulate_greasemonkey_(false), emulate_greasemonkey_(false),
match_all_frames_(false), match_all_frames_(false),
match_about_blank_(false), match_about_blank_(false),
match_data_urls_(false),
incognito_enabled_(false) {} incognito_enabled_(false) {}
UserScript::~UserScript() { UserScript::~UserScript() {
...@@ -132,6 +133,7 @@ std::unique_ptr<UserScript> UserScript::CopyMetadataFrom( ...@@ -132,6 +133,7 @@ std::unique_ptr<UserScript> UserScript::CopyMetadataFrom(
script->emulate_greasemonkey_ = other.emulate_greasemonkey_; script->emulate_greasemonkey_ = other.emulate_greasemonkey_;
script->match_all_frames_ = other.match_all_frames_; script->match_all_frames_ = other.match_all_frames_;
script->match_about_blank_ = other.match_about_blank_; script->match_about_blank_ = other.match_about_blank_;
script->match_data_urls_ = other.match_data_urls_;
script->incognito_enabled_ = other.incognito_enabled_; script->incognito_enabled_ = other.incognito_enabled_;
return script; return script;
...@@ -198,6 +200,7 @@ void UserScript::Pickle(base::Pickle* pickle) const { ...@@ -198,6 +200,7 @@ void UserScript::Pickle(base::Pickle* pickle) const {
pickle->WriteBool(emulate_greasemonkey()); pickle->WriteBool(emulate_greasemonkey());
pickle->WriteBool(match_all_frames()); pickle->WriteBool(match_all_frames());
pickle->WriteBool(match_about_blank()); pickle->WriteBool(match_about_blank());
pickle->WriteBool(match_data_urls());
pickle->WriteBool(is_incognito_enabled()); pickle->WriteBool(is_incognito_enabled());
PickleHostID(pickle, host_id_); PickleHostID(pickle, host_id_);
...@@ -253,6 +256,7 @@ void UserScript::Unpickle(const base::Pickle& pickle, ...@@ -253,6 +256,7 @@ void UserScript::Unpickle(const base::Pickle& pickle,
CHECK(iter->ReadBool(&emulate_greasemonkey_)); CHECK(iter->ReadBool(&emulate_greasemonkey_));
CHECK(iter->ReadBool(&match_all_frames_)); CHECK(iter->ReadBool(&match_all_frames_));
CHECK(iter->ReadBool(&match_about_blank_)); CHECK(iter->ReadBool(&match_about_blank_));
CHECK(iter->ReadBool(&match_data_urls_));
CHECK(iter->ReadBool(&incognito_enabled_)); CHECK(iter->ReadBool(&incognito_enabled_));
UnpickleHostID(pickle, iter, &host_id_); UnpickleHostID(pickle, iter, &host_id_);
......
...@@ -174,6 +174,10 @@ class UserScript { ...@@ -174,6 +174,10 @@ class UserScript {
bool match_about_blank() const { return match_about_blank_; } bool match_about_blank() const { return match_about_blank_; }
void set_match_about_blank(bool val) { match_about_blank_ = val; } void set_match_about_blank(bool val) { match_about_blank_ = val; }
// Whether to match data:-scheme URLs.
bool match_data_urls() const { return match_data_urls_; }
void set_match_data_urls(bool val) { match_data_urls_ = val; }
// The globs, if any, that determine which pages this script runs against. // The globs, if any, that determine which pages this script runs against.
// These are only used with "standalone" Greasemonkey-like user scripts. // These are only used with "standalone" Greasemonkey-like user scripts.
const std::vector<std::string>& globs() const { return globs_; } const std::vector<std::string>& globs() const { return globs_; }
...@@ -231,9 +235,9 @@ class UserScript { ...@@ -231,9 +235,9 @@ class UserScript {
bool MatchesURL(const GURL& url) const; bool MatchesURL(const GURL& url) const;
// Returns true if the script should be applied to the given // Returns true if the script should be applied to the given
// |effective_document_url| (calculated by the caller based on // |effective_document_url|. It is the caller's responsibility to calculate
// match_about_blank()| while also taking into account whether the document's // |effective_document_url| based on match_about_blank() and
// frame |is_subframe| and what the |top_level_origin| is. // match_data_urls().
bool MatchesDocument(const GURL& effective_document_url, bool MatchesDocument(const GURL& effective_document_url,
bool is_subframe) const; bool is_subframe) const;
...@@ -326,6 +330,10 @@ class UserScript { ...@@ -326,6 +330,10 @@ class UserScript {
// Defaults to false. // Defaults to false.
bool match_about_blank_; bool match_about_blank_;
// Whether the user script should run in data:-scheme frames.
// Defaults to false.
bool match_data_urls_;
// True if the script should be injected into an incognito tab. // True if the script should be injected into an incognito tab.
bool incognito_enabled_; bool incognito_enabled_;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment