Commit 09de3648 authored by Kelvin Jiang's avatar Kelvin Jiang Committed by Commit Bot

[DNR] Implement upgrade scheme actions

Implement a new upgradeScheme action type for DNR rules. These rules
are evaluated along with redirect rules as they are matched based on
their specified priority.

When an http or ftp request matches with an upgradeScheme rule, the
request's scheme is changed to https.

Bug: 974388
Change-Id: I0d9524e1c7d405a413868626da99e805c26ac601
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1674748
Commit-Queue: Kelvin Jiang <kelvinjiang@chromium.org>
Reviewed-by: default avatarKaran Bhatia <karandeepb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#678502}
parent 89762a9e
......@@ -1337,6 +1337,100 @@ IN_PROC_BROWSER_TEST_P(DeclarativeNetRequestBrowserTest, RedirectPriority) {
}
}
// Test that upgradeScheme rules will change the scheme of matching requests to
// https.
IN_PROC_BROWSER_TEST_P(DeclarativeNetRequestBrowserTest, UpgradeRules) {
auto get_url_for_host = [this](std::string hostname, const char* scheme) {
GURL url = embedded_test_server()->GetURL(hostname,
"/pages_with_script/index.html");
url::Replacements<char> replacements;
replacements.SetScheme(scheme, url::Component(0, strlen(scheme)));
return url.ReplaceComponents(replacements);
};
GURL google_url = get_url_for_host("google.com", url::kHttpScheme);
struct {
std::string url_filter;
int id;
int priority;
std::string action_type;
base::Optional<std::string> redirect_url;
} rules_data[] = {
{"exa*", 1, 4, "upgradeScheme", base::nullopt},
{"|http:*yahoo", 2, 100, "redirect", "http://other.com"},
// Since the test server can only display http requests, redirect all
// https requests to google.com in the end.
// TODO(crbug.com/985104): Add a https test server to display https pages
// so this redirect rule can be removed.
{"|https*", 3, 6, "redirect", google_url.spec()},
{"exact.com", 4, 1, "block", base::nullopt},
};
// Load the extension.
std::vector<TestRule> rules;
for (const auto& rule_data : rules_data) {
TestRule rule = CreateGenericRule();
rule.condition->url_filter = rule_data.url_filter;
rule.id = rule_data.id;
rule.priority = rule_data.priority;
rule.condition->resource_types = std::vector<std::string>({"main_frame"});
rule.action->type = rule_data.action_type;
rule.action->redirect_url = rule_data.redirect_url;
rules.push_back(rule);
}
ASSERT_NO_FATAL_FAILURE(LoadExtensionWithRules(
rules, "test_extension", {URLPattern::kAllUrlsPattern}));
// Now load an extension with another ruleset, except this extension has no
// host permissions.
TestRule upgrade_rule = CreateGenericRule();
upgrade_rule.condition->url_filter = "yahoo";
upgrade_rule.id = kMinValidID;
upgrade_rule.priority = kMinValidPriority;
upgrade_rule.condition->resource_types =
std::vector<std::string>({"main_frame"});
upgrade_rule.action->type = "upgradeScheme";
ASSERT_NO_FATAL_FAILURE(LoadExtensionWithRules(
std::vector<TestRule>({upgrade_rule}), "test_extension_2", {}));
struct {
std::string hostname;
const char* scheme;
// |expected_final_url| is null if the request is expected to be blocked.
base::Optional<GURL> expected_final_url;
} test_cases[] = {
{"exact.com", url::kHttpScheme, base::nullopt},
// http://example.com -> https://example.com/ -> http://google.com
{"example.com", url::kHttpScheme, google_url},
// test_extension_2 should upgrade the scheme for http://yahoo.com
// despite having no host permissions. Note that this request is not
// matched with test_extension_1's ruleset as test_extension_2 is
// installed more recently.
// http://yahoo.com -> https://yahoo.com/ -> http://google.com
{"yahoo.com", url::kHttpScheme, google_url},
};
for (const auto& test_case : test_cases) {
GURL url = get_url_for_host(test_case.hostname, test_case.scheme);
SCOPED_TRACE(base::StringPrintf("Testing %s", url.spec().c_str()));
ui_test_utils::NavigateToURL(browser(), url);
if (!test_case.expected_final_url) {
EXPECT_EQ(content::PAGE_TYPE_ERROR, GetPageType());
} else {
EXPECT_EQ(content::PAGE_TYPE_NORMAL, GetPageType());
const GURL& final_url = web_contents()->GetLastCommittedURL();
EXPECT_EQ(*test_case.expected_final_url, final_url);
}
}
}
// Tests that only extensions enabled in incognito mode affect network requests
// from an incognito context.
IN_PROC_BROWSER_TEST_P(DeclarativeNetRequestBrowserTest,
......
......@@ -39,21 +39,21 @@ namespace declarative_net_request {
// with gtest.
bool operator==(const RulesetManager::Action& lhs,
const RulesetManager::Action& rhs) {
static_assert(flat::ActionIndex_count == 6,
"Modify this method to ensure it stays updated as new actions "
"are added.");
auto are_vectors_equal = [](std::vector<const char*> a,
std::vector<const char*> b) {
return std::set<base::StringPiece>(a.begin(), a.end()) ==
std::set<base::StringPiece>(b.begin(), b.end());
};
return lhs.type == rhs.type && lhs.redirect_url == rhs.redirect_url &&
are_vectors_equal(lhs.request_headers_to_remove,
rhs.request_headers_to_remove) &&
are_vectors_equal(lhs.response_headers_to_remove,
rhs.response_headers_to_remove);
static_assert(flat::ActionIndex_count == 7,
"Modify this method to ensure it stays updated as new actions "
"are added.");
auto are_vectors_equal = [](std::vector<const char*> a,
std::vector<const char*> b) {
return std::set<base::StringPiece>(a.begin(), a.end()) ==
std::set<base::StringPiece>(b.begin(), b.end());
};
return lhs.type == rhs.type && lhs.redirect_url == rhs.redirect_url &&
are_vectors_equal(lhs.request_headers_to_remove,
rhs.request_headers_to_remove) &&
are_vectors_equal(lhs.response_headers_to_remove,
rhs.response_headers_to_remove);
}
namespace {
......
......@@ -9,10 +9,14 @@
#include <utility>
#include "base/metrics/histogram_macros.h"
#include "extensions/browser/api/declarative_net_request/flat/extension_ruleset_generated.h"
#include "extensions/browser/api/declarative_net_request/utils.h"
namespace extensions {
namespace declarative_net_request {
namespace flat_rule = url_pattern_index::flat;
using PageAccess = PermissionsData::PageAccess;
using RedirectAction = CompositeMatcher::RedirectAction;
namespace {
......@@ -46,8 +50,46 @@ bool HasMatchingAllowRule(const RulesetMatcher* matcher,
return params.allow_rule_cache[matcher];
}
// Upgrades the url's scheme to HTTPS.
GURL GetUpgradedUrl(const GURL& url) {
DCHECK(url.SchemeIs(url::kHttpScheme) || url.SchemeIs(url::kFtpScheme));
GURL::Replacements replacements;
replacements.SetSchemeStr(url::kHttpsScheme);
return url.ReplaceComponents(replacements);
}
// Compares |redirect_rule| and |upgrade_rule| and determines the redirect URL
// based on the rule with the higher priority.
GURL GetUrlByRulePriority(const flat_rule::UrlRule* redirect_rule,
const flat_rule::UrlRule* upgrade_rule,
const GURL& request_url,
GURL redirect_rule_url) {
DCHECK(upgrade_rule || redirect_rule);
if (!upgrade_rule)
return redirect_rule_url;
if (!redirect_rule)
return GetUpgradedUrl(request_url);
return upgrade_rule->priority() > redirect_rule->priority()
? GetUpgradedUrl(request_url)
: redirect_rule_url;
}
} // namespace
RedirectAction::RedirectAction(base::Optional<GURL> redirect_url,
bool notify_request_withheld)
: redirect_url(std::move(redirect_url)),
notify_request_withheld(notify_request_withheld) {}
RedirectAction::~RedirectAction() = default;
RedirectAction::RedirectAction(RedirectAction&&) = default;
RedirectAction& RedirectAction::operator=(RedirectAction&& other) = default;
CompositeMatcher::CompositeMatcher(MatcherList matchers)
: matchers_(std::move(matchers)) {
SortMatchersByPriority();
......@@ -97,21 +139,53 @@ bool CompositeMatcher::ShouldBlockRequest(const RequestParams& params) const {
return false;
}
bool CompositeMatcher::ShouldRedirectRequest(const RequestParams& params,
GURL* redirect_url) const {
RedirectAction CompositeMatcher::ShouldRedirectRequest(
const RequestParams& params,
PageAccess page_access) const {
// TODO(karandeepb): change this to report time in micro-seconds.
SCOPED_UMA_HISTOGRAM_TIMER(
"Extensions.DeclarativeNetRequest.ShouldRedirectRequestTime."
"SingleExtension");
bool notify_request_withheld = false;
for (const auto& matcher : matchers_) {
if (HasMatchingAllowRule(matcher.get(), params))
return false;
if (matcher->HasMatchingRedirectRule(params, redirect_url))
return true;
if (HasMatchingAllowRule(matcher.get(), params)) {
return RedirectAction(base::nullopt /* redirect_url */,
false /* notify_request_withheld */);
}
if (page_access == PageAccess::kAllowed) {
GURL redirect_rule_url;
const flat_rule::UrlRule* redirect_rule =
matcher->GetRedirectRule(params, &redirect_rule_url);
const flat_rule::UrlRule* upgrade_rule = matcher->GetUpgradeRule(params);
if (!upgrade_rule && !redirect_rule)
continue;
GURL redirect_url =
GetUrlByRulePriority(redirect_rule, upgrade_rule, *params.url,
std::move(redirect_rule_url));
return RedirectAction(std::move(redirect_url),
false /* notify_request_withheld */);
}
// If the extension has no host permissions for the request, it can still
// upgrade the request.
if (matcher->GetUpgradeRule(params)) {
return RedirectAction(GetUpgradedUrl(*params.url),
false /* notify_request_withheld */);
}
GURL redirect_url;
notify_request_withheld |=
(page_access == PageAccess::kWithheld &&
matcher->GetRedirectRule(params, &redirect_url));
}
return false;
return RedirectAction(base::nullopt /* redirect_url */,
notify_request_withheld);
}
uint8_t CompositeMatcher::GetRemoveHeadersMask(const RequestParams& params,
......
......@@ -10,7 +10,9 @@
#include <vector>
#include "base/macros.h"
#include "base/optional.h"
#include "extensions/browser/api/declarative_net_request/ruleset_matcher.h"
#include "extensions/common/permissions/permissions_data.h"
namespace extensions {
namespace declarative_net_request {
......@@ -19,6 +21,24 @@ namespace declarative_net_request {
// while respecting their priorities.
class CompositeMatcher {
public:
struct RedirectAction {
RedirectAction(base::Optional<GURL> redirect_url, bool notify);
~RedirectAction();
RedirectAction(RedirectAction&& other);
RedirectAction& operator=(RedirectAction&& other);
// The URL the request will be redirected to. The request should not be
// redirected if this is not specified.
base::Optional<GURL> redirect_url;
// Whether the extension should be notified that the request was unable to
// be redirected as the extension lacks the appropriate host permission for
// the request.
bool notify_request_withheld = false;
DISALLOW_COPY_AND_ASSIGN(RedirectAction);
};
using MatcherList = std::vector<std::unique_ptr<RulesetMatcher>>;
// Each RulesetMatcher should have a distinct ID and priority.
......@@ -33,10 +53,12 @@ class CompositeMatcher {
// blocked.
bool ShouldBlockRequest(const RequestParams& params) const;
// Returns whether the network request as specified by |params| should be
// redirected along with the |redirect_url|. |redirect_url| must not be null.
bool ShouldRedirectRequest(const RequestParams& params,
GURL* redirect_url) const;
// Returns a RedirectAction struct containing a redirect URL if the request
// is to be redirected, and whether the extension should be notified if its
// access to the request is withheld.
RedirectAction ShouldRedirectRequest(
const RequestParams& params,
PermissionsData::PageAccess page_access) const;
// Returns the bitmask of headers to remove from the request. The bitmask
// corresponds to RemoveHeadersMask type. |current_mask| denotes the current
......
......@@ -12,6 +12,9 @@ const char kErrorResourceTypeDuplicated[] =
const char kErrorEmptyRedirectRuleKey[] =
"Rule with id * does not specify the value for * key. This is required "
"for redirect rules.";
const char kErrorEmptyUpgradeRulePriority[] =
"Rule with id * does not specify the value for priority key. This is "
"required for upgradeScheme rules.";
const char kErrorInvalidRuleKey[] =
"Rule with id * has an invalid value for * key. This should be greater "
"than or equal to *.";
......
......@@ -18,9 +18,11 @@ enum class ParseResult {
SUCCESS,
ERROR_RESOURCE_TYPE_DUPLICATED,
ERROR_EMPTY_REDIRECT_RULE_PRIORITY,
ERROR_EMPTY_UPGRADE_RULE_PRIORITY,
ERROR_EMPTY_REDIRECT_URL,
ERROR_INVALID_RULE_ID,
ERROR_INVALID_REDIRECT_RULE_PRIORITY,
ERROR_INVALID_UPGRADE_RULE_PRIORITY,
ERROR_NO_APPLICABLE_RESOURCE_TYPES,
ERROR_EMPTY_DOMAINS_LIST,
ERROR_EMPTY_RESOURCE_TYPES_LIST,
......@@ -81,6 +83,7 @@ enum RemoveHeadersMask : uint8_t {
// Rule parsing errors.
extern const char kErrorResourceTypeDuplicated[];
extern const char kErrorEmptyRedirectRuleKey[];
extern const char kErrorEmptyUpgradeRulePriority[];
extern const char kErrorInvalidRuleKey[];
extern const char kErrorNoApplicableResourceTypes[];
extern const char kErrorEmptyList[];
......
......@@ -25,6 +25,7 @@ enum ActionIndex : ubyte {
block = 0,
allow,
redirect,
upgrade_scheme,
remove_cookie_header,
remove_referer_header,
remove_set_cookie_header,
......
......@@ -134,6 +134,8 @@ FlatRulesetIndexer::GetBuilders(const IndexedRule& indexed_rule) {
return {index_builders_[flat::ActionIndex_redirect].get()};
case dnr_api::RULE_ACTION_TYPE_REMOVEHEADERS:
return GetRemoveHeaderBuilders(indexed_rule.remove_headers_set);
case dnr_api::RULE_ACTION_TYPE_UPGRADESCHEME:
return {index_builders_[flat::ActionIndex_upgrade_scheme].get()};
case dnr_api::RULE_ACTION_TYPE_NONE:
break;
}
......
......@@ -278,6 +278,19 @@ ParseResult IndexedRule::CreateIndexedRule(dnr_api::Rule parsed_rule,
const bool is_redirect_rule =
parsed_rule.action.type == dnr_api::RULE_ACTION_TYPE_REDIRECT;
const bool is_upgrade_rule =
parsed_rule.action.type == dnr_api::RULE_ACTION_TYPE_UPGRADESCHEME;
if (is_redirect_rule || is_upgrade_rule) {
if (!parsed_rule.priority)
return is_redirect_rule ? ParseResult::ERROR_EMPTY_REDIRECT_RULE_PRIORITY
: ParseResult::ERROR_EMPTY_UPGRADE_RULE_PRIORITY;
if (*parsed_rule.priority < kMinValidPriority)
return is_redirect_rule
? ParseResult::ERROR_INVALID_REDIRECT_RULE_PRIORITY
: ParseResult::ERROR_INVALID_UPGRADE_RULE_PRIORITY;
}
if (is_redirect_rule) {
if (!parsed_rule.action.redirect_url ||
parsed_rule.action.redirect_url->empty()) {
......@@ -288,10 +301,6 @@ ParseResult IndexedRule::CreateIndexedRule(dnr_api::Rule parsed_rule,
!GURL(*parsed_rule.action.redirect_url).is_valid()) {
return ParseResult::ERROR_INVALID_REDIRECT_URL;
}
if (!parsed_rule.priority)
return ParseResult::ERROR_EMPTY_REDIRECT_RULE_PRIORITY;
if (*parsed_rule.priority < kMinValidPriority)
return ParseResult::ERROR_INVALID_REDIRECT_RULE_PRIORITY;
}
if (parsed_rule.condition.domains && parsed_rule.condition.domains->empty())
......@@ -312,7 +321,8 @@ ParseResult IndexedRule::CreateIndexedRule(dnr_api::Rule parsed_rule,
indexed_rule->action_type = parsed_rule.action.type;
indexed_rule->id = base::checked_cast<uint32_t>(parsed_rule.id);
indexed_rule->priority = base::checked_cast<uint32_t>(
is_redirect_rule ? *parsed_rule.priority : kDefaultPriority);
(is_redirect_rule || is_upgrade_rule) ? *parsed_rule.priority
: kDefaultPriority);
indexed_rule->options = GetOptionsMask(parsed_rule);
indexed_rule->activation_types = GetActivationTypes(parsed_rule);
......
......@@ -78,28 +78,43 @@ TEST_F(IndexedRuleTest, IDParsing) {
TEST_F(IndexedRuleTest, PriorityParsing) {
struct {
dnr_api::RuleActionType action_type;
std::unique_ptr<int> priority;
const ParseResult expected_result;
// Only valid if |expected_result| is SUCCESS.
const uint32_t expected_priority;
} cases[] = {
{std::make_unique<int>(kMinValidPriority - 1),
{dnr_api::RULE_ACTION_TYPE_REDIRECT,
std::make_unique<int>(kMinValidPriority - 1),
ParseResult::ERROR_INVALID_REDIRECT_RULE_PRIORITY, kDefaultPriority},
{std::make_unique<int>(kMinValidPriority), ParseResult::SUCCESS,
{dnr_api::RULE_ACTION_TYPE_REDIRECT,
std::make_unique<int>(kMinValidPriority), ParseResult::SUCCESS,
kMinValidPriority},
{std::make_unique<int>(kMinValidPriority + 1), ParseResult::SUCCESS,
{dnr_api::RULE_ACTION_TYPE_REDIRECT,
std::make_unique<int>(kMinValidPriority + 1), ParseResult::SUCCESS,
kMinValidPriority + 1},
{nullptr, ParseResult::ERROR_EMPTY_REDIRECT_RULE_PRIORITY,
kDefaultPriority},
{dnr_api::RULE_ACTION_TYPE_REDIRECT, nullptr,
ParseResult::ERROR_EMPTY_REDIRECT_RULE_PRIORITY, kDefaultPriority},
{dnr_api::RULE_ACTION_TYPE_UPGRADESCHEME,
std::make_unique<int>(kMinValidPriority - 1),
ParseResult::ERROR_INVALID_UPGRADE_RULE_PRIORITY, kDefaultPriority},
{dnr_api::RULE_ACTION_TYPE_UPGRADESCHEME,
std::make_unique<int>(kMinValidPriority), ParseResult::SUCCESS,
kMinValidPriority},
{dnr_api::RULE_ACTION_TYPE_UPGRADESCHEME, nullptr,
ParseResult::ERROR_EMPTY_UPGRADE_RULE_PRIORITY, kDefaultPriority},
};
for (size_t i = 0; i < base::size(cases); ++i) {
SCOPED_TRACE(base::StringPrintf("Testing case[%" PRIuS "]", i));
dnr_api::Rule rule = CreateGenericParsedRule();
rule.priority = std::move(cases[i].priority);
rule.action.type = dnr_api::RULE_ACTION_TYPE_REDIRECT;
rule.action.redirect_url =
std::make_unique<std::string>("http://google.com");
rule.action.type = cases[i].action_type;
if (cases[i].action_type == dnr_api::RULE_ACTION_TYPE_REDIRECT) {
rule.action.redirect_url =
std::make_unique<std::string>("http://google.com");
}
IndexedRule indexed_rule;
ParseResult result = IndexedRule::CreateIndexedRule(
......
......@@ -28,6 +28,7 @@ enum ActionIndex : ubyte {
block = 0,
allow,
redirect,
upgrade_scheme,
remove_cookie_header,
remove_referer_header,
remove_set_cookie_header,
......@@ -103,7 +104,7 @@ TEST_F(IndexedRulesetFormatVersionTest, CheckVersionUpdated) {
EXPECT_EQ(StripCommentsAndWhitespace(kFlatbufferSchemaExpected),
StripCommentsAndWhitespace(flatbuffer_schema))
<< "Schema change detected; update this test and the schema version.";
EXPECT_EQ(7, GetIndexedRulesetFormatVersionForTesting())
EXPECT_EQ(8, GetIndexedRulesetFormatVersionForTesting())
<< "Update this test if you update the schema version.";
}
......
......@@ -5,6 +5,7 @@
#include "extensions/browser/api/declarative_net_request/parse_info.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "extensions/common/error_utils.h"
namespace extensions {
......@@ -28,74 +29,82 @@ std::string ParseInfo::GetErrorDescription() const {
break;
case ParseResult::ERROR_RESOURCE_TYPE_DUPLICATED:
error = ErrorUtils::FormatErrorMessage(kErrorResourceTypeDuplicated,
std::to_string(*rule_id_));
base::NumberToString(*rule_id_));
break;
case ParseResult::ERROR_EMPTY_REDIRECT_RULE_PRIORITY:
error = ErrorUtils::FormatErrorMessage(
kErrorEmptyRedirectRuleKey, std::to_string(*rule_id_), kPriorityKey);
error = ErrorUtils::FormatErrorMessage(kErrorEmptyRedirectRuleKey,
base::NumberToString(*rule_id_),
kPriorityKey);
break;
case ParseResult::ERROR_EMPTY_UPGRADE_RULE_PRIORITY:
error = ErrorUtils::FormatErrorMessage(kErrorEmptyUpgradeRulePriority,
base::NumberToString(*rule_id_));
break;
case ParseResult::ERROR_EMPTY_REDIRECT_URL:
error = ErrorUtils::FormatErrorMessage(kErrorEmptyRedirectRuleKey,
std::to_string(*rule_id_),
base::NumberToString(*rule_id_),
kRedirectUrlKey);
break;
case ParseResult::ERROR_INVALID_RULE_ID:
error = ErrorUtils::FormatErrorMessage(kErrorInvalidRuleKey,
std::to_string(*rule_id_), kIDKey,
std::to_string(kMinValidID));
error = ErrorUtils::FormatErrorMessage(
kErrorInvalidRuleKey, base::NumberToString(*rule_id_), kIDKey,
base::NumberToString(kMinValidID));
break;
case ParseResult::ERROR_INVALID_REDIRECT_RULE_PRIORITY:
case ParseResult::ERROR_INVALID_UPGRADE_RULE_PRIORITY:
error = ErrorUtils::FormatErrorMessage(
kErrorInvalidRuleKey, std::to_string(*rule_id_), kPriorityKey,
std::to_string(kMinValidPriority));
kErrorInvalidRuleKey, base::NumberToString(*rule_id_), kPriorityKey,
base::NumberToString(kMinValidPriority));
break;
case ParseResult::ERROR_NO_APPLICABLE_RESOURCE_TYPES:
error = ErrorUtils::FormatErrorMessage(kErrorNoApplicableResourceTypes,
std::to_string(*rule_id_));
base::NumberToString(*rule_id_));
break;
case ParseResult::ERROR_EMPTY_DOMAINS_LIST:
error = ErrorUtils::FormatErrorMessage(
kErrorEmptyList, std::to_string(*rule_id_), kDomainsKey);
kErrorEmptyList, base::NumberToString(*rule_id_), kDomainsKey);
break;
case ParseResult::ERROR_EMPTY_RESOURCE_TYPES_LIST:
error = ErrorUtils::FormatErrorMessage(
kErrorEmptyList, std::to_string(*rule_id_), kResourceTypesKey);
kErrorEmptyList, base::NumberToString(*rule_id_), kResourceTypesKey);
break;
case ParseResult::ERROR_EMPTY_URL_FILTER:
error = ErrorUtils::FormatErrorMessage(
kErrorEmptyUrlFilter, std::to_string(*rule_id_), kUrlFilterKey);
kErrorEmptyUrlFilter, base::NumberToString(*rule_id_), kUrlFilterKey);
break;
case ParseResult::ERROR_INVALID_REDIRECT_URL:
error = ErrorUtils::FormatErrorMessage(
kErrorInvalidRedirectUrl, std::to_string(*rule_id_), kRedirectUrlKey);
error = ErrorUtils::FormatErrorMessage(kErrorInvalidRedirectUrl,
base::NumberToString(*rule_id_),
kRedirectUrlKey);
break;
case ParseResult::ERROR_DUPLICATE_IDS:
error = ErrorUtils::FormatErrorMessage(kErrorDuplicateIDs,
std::to_string(*rule_id_));
base::NumberToString(*rule_id_));
break;
case ParseResult::ERROR_PERSISTING_RULESET:
error = kErrorPersisting;
break;
case ParseResult::ERROR_NON_ASCII_URL_FILTER:
error = ErrorUtils::FormatErrorMessage(
kErrorNonAscii, std::to_string(*rule_id_), kUrlFilterKey);
kErrorNonAscii, base::NumberToString(*rule_id_), kUrlFilterKey);
break;
case ParseResult::ERROR_NON_ASCII_DOMAIN:
error = ErrorUtils::FormatErrorMessage(
kErrorNonAscii, std::to_string(*rule_id_), kDomainsKey);
kErrorNonAscii, base::NumberToString(*rule_id_), kDomainsKey);
break;
case ParseResult::ERROR_NON_ASCII_EXCLUDED_DOMAIN:
error = ErrorUtils::FormatErrorMessage(
kErrorNonAscii, std::to_string(*rule_id_), kExcludedDomainsKey);
kErrorNonAscii, base::NumberToString(*rule_id_), kExcludedDomainsKey);
break;
case ParseResult::ERROR_INVALID_URL_FILTER:
error = ErrorUtils::FormatErrorMessage(
kErrorInvalidUrlFilter, std::to_string(*rule_id_), kUrlFilterKey);
error = ErrorUtils::FormatErrorMessage(kErrorInvalidUrlFilter,
base::NumberToString(*rule_id_),
kUrlFilterKey);
break;
case ParseResult::ERROR_EMPTY_REMOVE_HEADERS_LIST:
error = ErrorUtils::FormatErrorMessage(kErrorEmptyRemoveHeadersList,
std::to_string(*rule_id_),
base::NumberToString(*rule_id_),
kRemoveHeadersListKey);
break;
}
......
......@@ -360,7 +360,7 @@ bool RulesetManager::HasExtraHeadersMatcherForRequest(
// We only support removing a subset of extra headers currently. If that
// changes, the implementation here should change as well.
static_assert(flat::ActionIndex_count == 6,
static_assert(flat::ActionIndex_count == 7,
"Modify this method to ensure HasExtraHeadersMatcherForRequest "
"is updated as new actions are added.");
......@@ -408,8 +408,12 @@ base::Optional<RulesetManager::Action> RulesetManager::GetBlockOrCollapseAction(
return base::nullopt;
}
base::Optional<RulesetManager::Action> RulesetManager::GetRedirectAction(
base::Optional<RulesetManager::Action>
RulesetManager::GetRedirectOrUpgradeAction(
const std::vector<const ExtensionRulesetData*>& rulesets,
const WebRequestInfo& request,
const int tab_id,
const bool crosses_incognito,
const RequestParams& params) const {
DCHECK(std::is_sorted(rulesets.begin(), rulesets.end(),
[](const ExtensionRulesetData* a,
......@@ -423,12 +427,28 @@ base::Optional<RulesetManager::Action> RulesetManager::GetRedirectAction(
// more recently installed extensions get higher priority in choosing the
// redirect url.
for (const ExtensionRulesetData* ruleset : rulesets) {
GURL redirect_url;
if (ruleset->matcher->ShouldRedirectRequest(params, &redirect_url)) {
Action action(Action::Type::REDIRECT);
action.redirect_url = std::move(redirect_url);
return action;
PageAccess page_access = WebRequestPermissions::CanExtensionAccessURL(
info_map_, ruleset->extension_id, request.url, tab_id,
crosses_incognito,
WebRequestPermissions::REQUIRE_HOST_PERMISSION_FOR_URL_AND_INITIATOR,
request.initiator, request.type);
CompositeMatcher::RedirectAction redirect_action =
ruleset->matcher->ShouldRedirectRequest(params, page_access);
DCHECK(!(redirect_action.redirect_url &&
redirect_action.notify_request_withheld));
if (redirect_action.notify_request_withheld) {
NotifyRequestWithheld(ruleset->extension_id, request);
continue;
}
if (!redirect_action.redirect_url)
continue;
Action action(Action::Type::REDIRECT);
action.redirect_url = std::move(redirect_action.redirect_url);
return action;
}
return base::nullopt;
......@@ -507,37 +527,10 @@ RulesetManager::Action RulesetManager::EvaluateRequestInternal(
if (action)
return std::move(*action);
// Redirecting a request requires host permissions to the request url and
// its initiator.
std::vector<const ExtensionRulesetData*>
rulesets_to_evaluate_with_host_permissions;
for (const ExtensionRulesetData* ruleset : rulesets_to_evaluate) {
PageAccess page_access = WebRequestPermissions::CanExtensionAccessURL(
info_map_, ruleset->extension_id, request.url, tab_id,
crosses_incognito,
WebRequestPermissions::REQUIRE_HOST_PERMISSION_FOR_URL_AND_INITIATOR,
request.initiator, request.type);
if (page_access != PageAccess::kAllowed) {
// Notify the web request was withheld if the extension would have
// redirected the request.
// Note: The following check should be modified if more actions needing
// host permissions are added.
GURL ignore;
if (page_access == PageAccess::kWithheld &&
ruleset->matcher->ShouldRedirectRequest(params, &ignore)) {
NotifyRequestWithheld(ruleset->extension_id, request);
}
continue;
}
rulesets_to_evaluate_with_host_permissions.push_back(ruleset);
}
// If the request is redirected, no further modifications can happen. A new
// request will be created and subsequently evaluated.
action =
GetRedirectAction(rulesets_to_evaluate_with_host_permissions, params);
action = GetRedirectOrUpgradeAction(rulesets_to_evaluate, request, tab_id,
crosses_incognito, params);
if (action)
return std::move(*action);
......
......@@ -14,6 +14,7 @@
#include "base/optional.h"
#include "base/sequence_checker.h"
#include "base/time/time.h"
#include "extensions/browser/api/declarative_net_request/utils.h"
#include "extensions/common/extension_id.h"
#include "extensions/common/permissions/permissions_data.h"
#include "extensions/common/url_pattern_set.h"
......@@ -141,8 +142,11 @@ class RulesetManager {
base::Optional<Action> GetBlockOrCollapseAction(
const std::vector<const ExtensionRulesetData*>& rulesets,
const RequestParams& params) const;
base::Optional<Action> GetRedirectAction(
base::Optional<Action> GetRedirectOrUpgradeAction(
const std::vector<const ExtensionRulesetData*>& rulesets,
const WebRequestInfo& request,
const int tab_id,
const bool crosses_incognito,
const RequestParams& params) const;
base::Optional<Action> GetRemoveHeadersAction(
const std::vector<const ExtensionRulesetData*>& rulesets,
......
......@@ -127,7 +127,7 @@ bool IsExtraHeadersMatcherInternal(
const flat::ExtensionIndexedRuleset& ruleset) {
// We only support removing a subset of extra headers currently. If that
// changes, the implementation here should change as well.
static_assert(flat::ActionIndex_count == 6,
static_assert(flat::ActionIndex_count == 7,
"Modify this method to ensure IsExtraHeadersMatcherInternal is "
"updated as new actions are added.");
static const flat::ActionIndex extra_header_indices[] = {
......@@ -237,15 +237,16 @@ uint8_t RulesetMatcher::GetRemoveHeadersMask(const RequestParams& params,
return mask;
}
bool RulesetMatcher::HasMatchingRedirectRule(const RequestParams& params,
GURL* redirect_url) const {
const flat_rule::UrlRule* RulesetMatcher::GetRedirectRule(
const RequestParams& params,
GURL* redirect_url) const {
DCHECK(redirect_url);
DCHECK_NE(flat_rule::ElementType_WEBSOCKET, params.element_type);
const flat_rule::UrlRule* rule = GetMatchingRule(
params, flat::ActionIndex_redirect, FindRuleStrategy::kHighestPriority);
if (!rule)
return false;
return nullptr;
// Find the UrlRuleMetadata corresponding to |rule|. Since |metadata_list_| is
// sorted by rule id, use LookupByKey which binary searches for fast lookup.
......@@ -260,7 +261,17 @@ bool RulesetMatcher::HasMatchingRedirectRule(const RequestParams& params,
metadata->redirect_url()->size()));
DCHECK(redirect_url->is_valid());
// Prevent a redirect loop where a URL continuously redirects to itself.
return *params.url != *redirect_url;
return *params.url == *redirect_url ? nullptr : rule;
}
const flat_rule::UrlRule* RulesetMatcher::GetUpgradeRule(
const RequestParams& params) const {
const bool is_upgradeable = params.url->SchemeIs(url::kHttpScheme) ||
params.url->SchemeIs(url::kFtpScheme);
return is_upgradeable
? GetMatchingRule(params, flat::ActionIndex_upgrade_scheme,
FindRuleStrategy::kHighestPriority)
: nullptr;
}
RulesetMatcher::RulesetMatcher(std::string ruleset_data,
......
......@@ -110,10 +110,17 @@ class RulesetMatcher {
uint8_t GetRemoveHeadersMask(const RequestParams& params,
uint8_t current_mask) const;
// Returns whether the ruleset has a matching redirect rule. Populates
// |redirect_url| on returning true. |redirect_url| must not be null.
bool HasMatchingRedirectRule(const RequestParams& params,
GURL* redirect_url) const;
// Returns the ruleset's matching redirect rule and populates
// |redirect_url| if there is a matching redirect rule, otherwise returns
// nullptr.
const url_pattern_index::flat::UrlRule* GetRedirectRule(
const RequestParams& params,
GURL* redirect_url) const;
// Returns the ruleset's matching upgrade scheme rule or nullptr if no
// matching rule is found or if the request's scheme is not upgradeable.
const url_pattern_index::flat::UrlRule* GetUpgradeRule(
const RequestParams& params) const;
// Returns whether this modifies "extraHeaders".
bool IsExtraHeadersMatcher() const { return is_extra_headers_matcher_; }
......
......@@ -76,7 +76,7 @@ TEST_F(RulesetMatcherTest, RedirectRule) {
auto should_redirect_request = [&matcher](const RequestParams& params,
GURL* redirect_url) {
return matcher->HasMatchingRedirectRule(params, redirect_url);
return matcher->GetRedirectRule(params, redirect_url) != nullptr;
};
GURL google_url("http://google.com");
......@@ -113,7 +113,39 @@ TEST_F(RulesetMatcherTest, PreventSelfRedirect) {
params.is_third_party = true;
GURL redirect_url;
EXPECT_FALSE(matcher->HasMatchingRedirectRule(params, &redirect_url));
EXPECT_FALSE(matcher->GetRedirectRule(params, &redirect_url));
}
// Tests a simple upgrade scheme rule.
TEST_F(RulesetMatcherTest, UpgradeRule) {
TestRule rule = CreateGenericRule();
rule.condition->url_filter = std::string("google.com");
rule.priority = kMinValidPriority;
rule.action->type = std::string("upgradeScheme");
std::unique_ptr<RulesetMatcher> matcher;
ASSERT_TRUE(CreateVerifiedMatcher({rule}, CreateTemporarySource(), &matcher));
auto should_upgrade_request = [&matcher](const RequestParams& params) {
return matcher->GetUpgradeRule(params) != nullptr;
};
GURL google_url("http://google.com");
GURL yahoo_url("http://yahoo.com");
GURL non_upgradeable_url("https://google.com");
RequestParams params;
params.url = &google_url;
params.element_type = url_pattern_index::flat::ElementType_SUBDOCUMENT;
params.is_third_party = true;
EXPECT_TRUE(should_upgrade_request(params));
params.url = &yahoo_url;
EXPECT_FALSE(should_upgrade_request(params));
params.url = &non_upgradeable_url;
EXPECT_FALSE(should_upgrade_request(params));
}
// Tests that a modified ruleset file fails verification.
......
......@@ -33,7 +33,7 @@ namespace {
// url_pattern_index.fbs. Whenever an extension with an indexed ruleset format
// version different from the one currently used by Chrome is loaded, the
// extension ruleset will be reindexed.
constexpr int kIndexedRulesetFormatVersion = 7;
constexpr int kIndexedRulesetFormatVersion = 8;
// This static assert is meant to catch cases where
// url_pattern_index::kUrlPatternIndexFormatVersion is incremented without
......
......@@ -49,6 +49,9 @@ namespace declarativeNetRequest {
// Allow the network request. The request won't be intercepted if there is
// an allow rule which matches it.
allow,
// Upgrade the network request url's scheme to https if the request is http
// or ftp.
upgradeScheme,
// Remove request/response headers from the network request.
removeHeaders
};
......@@ -139,8 +142,9 @@ namespace declarativeNetRequest {
// An id which uniquely identifies a rule. Mandatory and should be >= 1.
long id;
// Rule priority. Mandatory for redirect rules and should be >= 1. This is
// used to break ties between multiple matching redirect rules.
// Rule priority. Mandatory for redirect and upgradeScheme rules and should
// be >= 1. This is used to break ties between multiple matching redirect
// rules.
long? priority;
// The condition under which this rule is triggered.
......
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