[go: nahoru, domu]

blob: f7607bce40919338bbdd9d990da96239ae3f3ac3 [file] [log] [blame]
// Copyright 2017 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 "extensions/browser/api/declarative_net_request/indexed_rule.h"
#include <memory>
#include <utility>
#include "base/format_macros.h"
#include "base/json/json_reader.h"
#include "base/macros.h"
#include "base/numerics/safe_conversions.h"
#include "base/stl_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "extensions/browser/api/declarative_net_request/constants.h"
#include "extensions/browser/api/declarative_net_request/test_utils.h"
#include "extensions/common/api/declarative_net_request.h"
#include "extensions/common/api/declarative_net_request/constants.h"
#include "extensions/common/extension.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace extensions {
namespace declarative_net_request {
namespace {
namespace flat_rule = url_pattern_index::flat;
namespace dnr_api = extensions::api::declarative_net_request;
constexpr const char* kTestExtensionId = "extensionid";
GURL GetBaseURL() {
return Extension::GetBaseURLFromExtensionId(kTestExtensionId);
}
std::unique_ptr<dnr_api::Redirect> MakeRedirectUrl(const char* redirect_url) {
auto redirect = std::make_unique<dnr_api::Redirect>();
redirect->url = std::make_unique<std::string>(redirect_url);
return redirect;
}
dnr_api::Rule CreateGenericParsedRule() {
dnr_api::Rule rule;
rule.priority = std::make_unique<int>(kMinValidPriority);
rule.id = kMinValidID;
rule.condition.url_filter = std::make_unique<std::string>("filter");
rule.action.type = dnr_api::RULE_ACTION_TYPE_BLOCK;
return rule;
}
using IndexedRuleTest = ::testing::Test;
TEST_F(IndexedRuleTest, IDParsing) {
struct {
const int id;
const ParseResult expected_result;
} cases[] = {
{kMinValidID - 1, ParseResult::ERROR_INVALID_RULE_ID},
{kMinValidID, ParseResult::SUCCESS},
{kMinValidID + 1, ParseResult::SUCCESS},
};
for (size_t i = 0; i < base::size(cases); ++i) {
SCOPED_TRACE(base::StringPrintf("Testing case[%" PRIuS "]", i));
dnr_api::Rule rule = CreateGenericParsedRule();
rule.id = cases[i].id;
IndexedRule indexed_rule;
ParseResult result = IndexedRule::CreateIndexedRule(
std::move(rule), GetBaseURL(), &indexed_rule);
EXPECT_EQ(cases[i].expected_result, result);
if (result == ParseResult::SUCCESS)
EXPECT_EQ(base::checked_cast<uint32_t>(cases[i].id), indexed_rule.id);
}
}
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[] = {
{dnr_api::RULE_ACTION_TYPE_REDIRECT,
std::make_unique<int>(kMinValidPriority - 1),
ParseResult::ERROR_INVALID_RULE_PRIORITY, kDefaultPriority},
{dnr_api::RULE_ACTION_TYPE_REDIRECT,
std::make_unique<int>(kMinValidPriority), ParseResult::SUCCESS,
kMinValidPriority},
{dnr_api::RULE_ACTION_TYPE_REDIRECT, nullptr, ParseResult::SUCCESS,
kDefaultPriority},
{dnr_api::RULE_ACTION_TYPE_REDIRECT,
std::make_unique<int>(kMinValidPriority + 1), ParseResult::SUCCESS,
kMinValidPriority + 1},
{dnr_api::RULE_ACTION_TYPE_UPGRADESCHEME,
std::make_unique<int>(kMinValidPriority - 1),
ParseResult::ERROR_INVALID_RULE_PRIORITY, kDefaultPriority},
{dnr_api::RULE_ACTION_TYPE_UPGRADESCHEME,
std::make_unique<int>(kMinValidPriority), ParseResult::SUCCESS,
kMinValidPriority},
{dnr_api::RULE_ACTION_TYPE_BLOCK,
std::make_unique<int>(kMinValidPriority - 1),
ParseResult::ERROR_INVALID_RULE_PRIORITY, kDefaultPriority},
{dnr_api::RULE_ACTION_TYPE_BLOCK,
std::make_unique<int>(kMinValidPriority), ParseResult::SUCCESS,
kMinValidPriority},
};
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 = cases[i].action_type;
if (cases[i].action_type == dnr_api::RULE_ACTION_TYPE_REDIRECT) {
rule.action.redirect = MakeRedirectUrl("http://google.com");
}
IndexedRule indexed_rule;
ParseResult result = IndexedRule::CreateIndexedRule(
std::move(rule), GetBaseURL(), &indexed_rule);
EXPECT_EQ(cases[i].expected_result, result);
if (result == ParseResult::SUCCESS)
EXPECT_EQ(ComputeIndexedRulePriority(cases[i].expected_priority,
cases[i].action_type),
indexed_rule.priority);
}
}
TEST_F(IndexedRuleTest, OptionsParsing) {
struct {
const dnr_api::DomainType domain_type;
const dnr_api::RuleActionType action_type;
std::unique_ptr<bool> is_url_filter_case_sensitive;
const uint8_t expected_options;
} cases[] = {
{dnr_api::DOMAIN_TYPE_NONE, dnr_api::RULE_ACTION_TYPE_BLOCK, nullptr,
flat_rule::OptionFlag_APPLIES_TO_THIRD_PARTY |
flat_rule::OptionFlag_APPLIES_TO_FIRST_PARTY},
{dnr_api::DOMAIN_TYPE_FIRSTPARTY, dnr_api::RULE_ACTION_TYPE_ALLOW,
std::make_unique<bool>(true),
flat_rule::OptionFlag_IS_ALLOWLIST |
flat_rule::OptionFlag_APPLIES_TO_FIRST_PARTY},
{dnr_api::DOMAIN_TYPE_FIRSTPARTY, dnr_api::RULE_ACTION_TYPE_ALLOW,
std::make_unique<bool>(false),
flat_rule::OptionFlag_IS_ALLOWLIST |
flat_rule::OptionFlag_APPLIES_TO_FIRST_PARTY |
flat_rule::OptionFlag_IS_CASE_INSENSITIVE},
};
for (size_t i = 0; i < base::size(cases); ++i) {
SCOPED_TRACE(base::StringPrintf("Testing case[%" PRIuS "]", i));
dnr_api::Rule rule = CreateGenericParsedRule();
rule.condition.domain_type = cases[i].domain_type;
rule.action.type = cases[i].action_type;
rule.condition.is_url_filter_case_sensitive =
std::move(cases[i].is_url_filter_case_sensitive);
IndexedRule indexed_rule;
ParseResult result = IndexedRule::CreateIndexedRule(
std::move(rule), GetBaseURL(), &indexed_rule);
EXPECT_EQ(ParseResult::SUCCESS, result);
EXPECT_EQ(cases[i].expected_options, indexed_rule.options);
}
}
TEST_F(IndexedRuleTest, ResourceTypesParsing) {
using ResourceTypeVec = std::vector<dnr_api::ResourceType>;
struct {
std::unique_ptr<ResourceTypeVec> resource_types;
std::unique_ptr<ResourceTypeVec> excluded_resource_types;
const ParseResult expected_result;
// Only valid if |expected_result| is SUCCESS.
const uint16_t expected_element_types;
} cases[] = {
{nullptr, nullptr, ParseResult::SUCCESS,
flat_rule::ElementType_ANY & ~flat_rule::ElementType_MAIN_FRAME},
{nullptr,
std::make_unique<ResourceTypeVec>(
ResourceTypeVec({dnr_api::RESOURCE_TYPE_SCRIPT})),
ParseResult::SUCCESS,
flat_rule::ElementType_ANY & ~flat_rule::ElementType_SCRIPT},
{std::make_unique<ResourceTypeVec>(ResourceTypeVec(
{dnr_api::RESOURCE_TYPE_SCRIPT, dnr_api::RESOURCE_TYPE_IMAGE})),
nullptr, ParseResult::SUCCESS,
flat_rule::ElementType_SCRIPT | flat_rule::ElementType_IMAGE},
{std::make_unique<ResourceTypeVec>(ResourceTypeVec(
{dnr_api::RESOURCE_TYPE_SCRIPT, dnr_api::RESOURCE_TYPE_IMAGE})),
std::make_unique<ResourceTypeVec>(
ResourceTypeVec({dnr_api::RESOURCE_TYPE_SCRIPT})),
ParseResult::ERROR_RESOURCE_TYPE_DUPLICATED,
flat_rule::ElementType_NONE},
{nullptr,
std::make_unique<ResourceTypeVec>(ResourceTypeVec(
{dnr_api::RESOURCE_TYPE_MAIN_FRAME, dnr_api::RESOURCE_TYPE_SUB_FRAME,
dnr_api::RESOURCE_TYPE_STYLESHEET, dnr_api::RESOURCE_TYPE_SCRIPT,
dnr_api::RESOURCE_TYPE_IMAGE, dnr_api::RESOURCE_TYPE_FONT,
dnr_api::RESOURCE_TYPE_OBJECT,
dnr_api::RESOURCE_TYPE_XMLHTTPREQUEST, dnr_api::RESOURCE_TYPE_PING,
dnr_api::RESOURCE_TYPE_CSP_REPORT, dnr_api::RESOURCE_TYPE_MEDIA,
dnr_api::RESOURCE_TYPE_WEBSOCKET, dnr_api::RESOURCE_TYPE_OTHER})),
ParseResult::ERROR_NO_APPLICABLE_RESOURCE_TYPES,
flat_rule::ElementType_NONE},
{std::make_unique<ResourceTypeVec>(), std::make_unique<ResourceTypeVec>(),
ParseResult::ERROR_EMPTY_RESOURCE_TYPES_LIST,
flat_rule::ElementType_NONE},
{std::make_unique<ResourceTypeVec>(
ResourceTypeVec({dnr_api::RESOURCE_TYPE_SCRIPT})),
std::make_unique<ResourceTypeVec>(ResourceTypeVec()),
ParseResult::SUCCESS, flat_rule::ElementType_SCRIPT},
};
for (size_t i = 0; i < base::size(cases); ++i) {
SCOPED_TRACE(base::StringPrintf("Testing case[%" PRIuS "]", i));
dnr_api::Rule rule = CreateGenericParsedRule();
rule.condition.resource_types = std::move(cases[i].resource_types);
rule.condition.excluded_resource_types =
std::move(cases[i].excluded_resource_types);
IndexedRule indexed_rule;
ParseResult result = IndexedRule::CreateIndexedRule(
std::move(rule), GetBaseURL(), &indexed_rule);
EXPECT_EQ(cases[i].expected_result, result);
if (result == ParseResult::SUCCESS)
EXPECT_EQ(cases[i].expected_element_types, indexed_rule.element_types);
}
}
TEST_F(IndexedRuleTest, UrlFilterParsing) {
struct {
std::unique_ptr<std::string> input_url_filter;
// Only valid if |expected_result| is SUCCESS.
const flat_rule::UrlPatternType expected_url_pattern_type;
const flat_rule::AnchorType expected_anchor_left;
const flat_rule::AnchorType expected_anchor_right;
const std::string expected_url_pattern;
const ParseResult expected_result;
} cases[] = {
{nullptr, flat_rule::UrlPatternType_SUBSTRING, flat_rule::AnchorType_NONE,
flat_rule::AnchorType_NONE, "", ParseResult::SUCCESS},
{std::make_unique<std::string>(""), flat_rule::UrlPatternType_SUBSTRING,
flat_rule::AnchorType_NONE, flat_rule::AnchorType_NONE, "",
ParseResult::ERROR_EMPTY_URL_FILTER},
{std::make_unique<std::string>("|"), flat_rule::UrlPatternType_SUBSTRING,
flat_rule::AnchorType_BOUNDARY, flat_rule::AnchorType_NONE, "",
ParseResult::SUCCESS},
{std::make_unique<std::string>("||"), flat_rule::UrlPatternType_SUBSTRING,
flat_rule::AnchorType_SUBDOMAIN, flat_rule::AnchorType_NONE, "",
ParseResult::SUCCESS},
{std::make_unique<std::string>("|||"),
flat_rule::UrlPatternType_SUBSTRING, flat_rule::AnchorType_SUBDOMAIN,
flat_rule::AnchorType_BOUNDARY, "", ParseResult::SUCCESS},
{std::make_unique<std::string>("|*|||"),
flat_rule::UrlPatternType_WILDCARDED, flat_rule::AnchorType_BOUNDARY,
flat_rule::AnchorType_BOUNDARY, "*||", ParseResult::SUCCESS},
{std::make_unique<std::string>("|xyz|"),
flat_rule::UrlPatternType_SUBSTRING, flat_rule::AnchorType_BOUNDARY,
flat_rule::AnchorType_BOUNDARY, "xyz", ParseResult::SUCCESS},
{std::make_unique<std::string>("||x^yz"),
flat_rule::UrlPatternType_WILDCARDED, flat_rule::AnchorType_SUBDOMAIN,
flat_rule::AnchorType_NONE, "x^yz", ParseResult::SUCCESS},
{std::make_unique<std::string>("||xyz|"),
flat_rule::UrlPatternType_SUBSTRING, flat_rule::AnchorType_SUBDOMAIN,
flat_rule::AnchorType_BOUNDARY, "xyz", ParseResult::SUCCESS},
{std::make_unique<std::string>("x*y|z"),
flat_rule::UrlPatternType_WILDCARDED, flat_rule::AnchorType_NONE,
flat_rule::AnchorType_NONE, "x*y|z", ParseResult::SUCCESS},
{std::make_unique<std::string>("**^"),
flat_rule::UrlPatternType_WILDCARDED, flat_rule::AnchorType_NONE,
flat_rule::AnchorType_NONE, "**^", ParseResult::SUCCESS},
{std::make_unique<std::string>("||google.com"),
flat_rule::UrlPatternType_SUBSTRING, flat_rule::AnchorType_SUBDOMAIN,
flat_rule::AnchorType_NONE, "google.com", ParseResult::SUCCESS},
// Url pattern with non-ascii characters -â±´ase.com.
{std::make_unique<std::string>(base::WideToUTF8(L"\x2c74"
L"ase.com")),
flat_rule::UrlPatternType_SUBSTRING, flat_rule::AnchorType_NONE,
flat_rule::AnchorType_NONE, "", ParseResult::ERROR_NON_ASCII_URL_FILTER},
// Url pattern starting with the domain anchor followed by a wildcard.
{std::make_unique<std::string>("||*xyz"),
flat_rule::UrlPatternType_WILDCARDED, flat_rule::AnchorType_SUBDOMAIN,
flat_rule::AnchorType_NONE, "", ParseResult::ERROR_INVALID_URL_FILTER}};
for (size_t i = 0; i < base::size(cases); ++i) {
SCOPED_TRACE(base::StringPrintf("Testing case[%" PRIuS "]", i));
dnr_api::Rule rule = CreateGenericParsedRule();
rule.condition.url_filter = std::move(cases[i].input_url_filter);
IndexedRule indexed_rule;
ParseResult result = IndexedRule::CreateIndexedRule(
std::move(rule), GetBaseURL(), &indexed_rule);
if (result != ParseResult::SUCCESS)
continue;
EXPECT_EQ(cases[i].expected_result, result);
EXPECT_EQ(cases[i].expected_url_pattern_type,
indexed_rule.url_pattern_type);
EXPECT_EQ(cases[i].expected_anchor_left, indexed_rule.anchor_left);
EXPECT_EQ(cases[i].expected_anchor_right, indexed_rule.anchor_right);
EXPECT_EQ(cases[i].expected_url_pattern, indexed_rule.url_pattern);
}
}
// Ensure case-insensitive patterns are lower-cased as required by
// url_pattern_index.
TEST_F(IndexedRuleTest, CaseInsensitiveLowerCased) {
const std::string kPattern = "/QUERY";
struct {
std::unique_ptr<bool> is_url_filter_case_sensitive;
std::string expected_pattern;
} test_cases[] = {
{std::make_unique<bool>(false), "/query"},
{std::make_unique<bool>(true), "/QUERY"},
{nullptr, "/QUERY"} // By default patterns are case sensitive.
};
for (auto& test_case : test_cases) {
dnr_api::Rule rule = CreateGenericParsedRule();
rule.condition.url_filter = std::make_unique<std::string>(kPattern);
rule.condition.is_url_filter_case_sensitive =
std::move(test_case.is_url_filter_case_sensitive);
IndexedRule indexed_rule;
ASSERT_EQ(ParseResult::SUCCESS,
IndexedRule::CreateIndexedRule(std::move(rule), GetBaseURL(),
&indexed_rule));
EXPECT_EQ(test_case.expected_pattern, indexed_rule.url_pattern);
}
}
TEST_F(IndexedRuleTest, DomainsParsing) {
using DomainVec = std::vector<std::string>;
struct {
std::unique_ptr<DomainVec> domains;
std::unique_ptr<DomainVec> excluded_domains;
const ParseResult expected_result;
// Only valid if |expected_result| is SUCCESS.
const DomainVec expected_domains;
const DomainVec expected_excluded_domains;
} cases[] = {
{nullptr, nullptr, ParseResult::SUCCESS, {}, {}},
{std::make_unique<DomainVec>(),
nullptr,
ParseResult::ERROR_EMPTY_DOMAINS_LIST,
{},
{}},
{nullptr, std::make_unique<DomainVec>(), ParseResult::SUCCESS, {}, {}},
{std::make_unique<DomainVec>(DomainVec({"a.com", "b.com", "a.com"})),
std::make_unique<DomainVec>(
DomainVec({"g.com", "XY.COM", "zzz.com", "a.com", "google.com"})),
ParseResult::SUCCESS,
{"a.com", "a.com", "b.com"},
{"google.com", "zzz.com", "xy.com", "a.com", "g.com"}},
// Domain with non-ascii characters.
{std::make_unique<DomainVec>(
DomainVec({base::WideToUTF8(L"abc\x2010" /*hyphen*/ L"def.com")})),
nullptr,
ParseResult::ERROR_NON_ASCII_DOMAIN,
{},
{}},
// Excluded domain with non-ascii characters.
{nullptr,
std::make_unique<DomainVec>(
DomainVec({base::WideToUTF8(L"36\x00b0"
L"c.com" /*36°c.com*/)})),
ParseResult::ERROR_NON_ASCII_EXCLUDED_DOMAIN,
{},
{}},
// Internationalized domain in punycode.
{std::make_unique<DomainVec>(
DomainVec({"xn--36c-tfa.com" /* punycode for 36°c.com*/})),
nullptr,
ParseResult::SUCCESS,
{"xn--36c-tfa.com"},
{}},
};
for (size_t i = 0; i < base::size(cases); ++i) {
SCOPED_TRACE(base::StringPrintf("Testing case[%" PRIuS "]", i));
dnr_api::Rule rule = CreateGenericParsedRule();
rule.condition.domains = std::move(cases[i].domains);
rule.condition.excluded_domains = std::move(cases[i].excluded_domains);
IndexedRule indexed_rule;
ParseResult result = IndexedRule::CreateIndexedRule(
std::move(rule), GetBaseURL(), &indexed_rule);
EXPECT_EQ(cases[i].expected_result, result);
if (result == ParseResult::SUCCESS) {
EXPECT_EQ(cases[i].expected_domains, indexed_rule.domains);
EXPECT_EQ(cases[i].expected_excluded_domains,
indexed_rule.excluded_domains);
}
}
}
TEST_F(IndexedRuleTest, RedirectUrlParsing) {
struct {
const char* redirect_url;
const ParseResult expected_result;
// Only valid if |expected_result| is SUCCESS.
const std::string expected_redirect_url;
} cases[] = {
{"", ParseResult::ERROR_INVALID_REDIRECT_URL, ""},
{"http://google.com", ParseResult::SUCCESS, "http://google.com"},
{"/relative/url?q=1", ParseResult::ERROR_INVALID_REDIRECT_URL, ""},
{"abc", ParseResult::ERROR_INVALID_REDIRECT_URL, ""}};
for (size_t i = 0; i < base::size(cases); ++i) {
SCOPED_TRACE(base::StringPrintf("Testing case[%" PRIuS "]", i));
dnr_api::Rule rule = CreateGenericParsedRule();
rule.action.redirect = MakeRedirectUrl(cases[i].redirect_url);
rule.action.type = dnr_api::RULE_ACTION_TYPE_REDIRECT;
IndexedRule indexed_rule;
ParseResult result = IndexedRule::CreateIndexedRule(
std::move(rule), GetBaseURL(), &indexed_rule);
EXPECT_EQ(cases[i].expected_result, result) << static_cast<int>(result);
if (result == ParseResult::SUCCESS)
EXPECT_EQ(cases[i].expected_redirect_url, indexed_rule.redirect_url);
}
}
TEST_F(IndexedRuleTest, RedirectParsing) {
struct {
std::string redirect_dictionary_json;
ParseResult expected_result;
base::Optional<std::string> expected_redirect_url;
} cases[] = {
// clang-format off
{
"{}",
ParseResult::ERROR_INVALID_REDIRECT,
base::nullopt
},
{
R"({"url": "xyz"})",
ParseResult::ERROR_INVALID_REDIRECT_URL,
base::nullopt
},
{
R"({"url": "javascript:window.alert(\"hello,world\");"})",
ParseResult::ERROR_JAVASCRIPT_REDIRECT,
base::nullopt
},
{
R"({"url": "http://google.com"})",
ParseResult::SUCCESS,
std::string("http://google.com")
},
{
R"({"extensionPath": "foo/xyz/"})",
ParseResult::ERROR_INVALID_EXTENSION_PATH,
base::nullopt
},
{
R"({"extensionPath": "/foo/xyz?q=1"})",
ParseResult::SUCCESS,
GetBaseURL().Resolve("/foo/xyz?q=1").spec()
},
{
R"(
{
"transform": {
"scheme": "",
"host": "foo.com"
}
})", ParseResult::ERROR_INVALID_TRANSFORM_SCHEME, base::nullopt
},
{
R"(
{
"transform": {
"scheme": "javascript",
"host": "foo.com"
}
})", ParseResult::ERROR_INVALID_TRANSFORM_SCHEME, base::nullopt
},
{
R"(
{
"transform": {
"scheme": "http",
"port": "-1"
}
})", ParseResult::ERROR_INVALID_TRANSFORM_PORT, base::nullopt
},
{
R"(
{
"transform": {
"scheme": "http",
"query": "abc"
}
})", ParseResult::ERROR_INVALID_TRANSFORM_QUERY, base::nullopt
},
{
R"({"transform": {"path": "abc"}})",
ParseResult::SUCCESS,
base::nullopt
},
{
R"({"transform": {"fragment": "abc"}})",
ParseResult::ERROR_INVALID_TRANSFORM_FRAGMENT,
base::nullopt
},
{
R"({"transform": {"path": ""}})",
ParseResult::SUCCESS,
base::nullopt
},
{
R"(
{
"transform": {
"scheme": "http",
"query": "?abc",
"queryTransform": {
"removeParams": ["abc"]
}
}
})", ParseResult::ERROR_QUERY_AND_TRANSFORM_BOTH_SPECIFIED, base::nullopt
},
{
R"(
{
"transform": {
"scheme": "https",
"host": "foo.com",
"port": "80",
"path": "/foo",
"queryTransform": {
"removeParams": ["x1", "x2"],
"addOrReplaceParams": [
{"key": "y1", "value": "foo"}
]
},
"fragment": "",
"username": "user"
}
})", ParseResult::SUCCESS, base::nullopt
}
};
// clang-format on
for (size_t i = 0; i < base::size(cases); ++i) {
SCOPED_TRACE(base::StringPrintf("Testing case[%" PRIuS "]", i));
dnr_api::Rule rule = CreateGenericParsedRule();
rule.action.type = dnr_api::RULE_ACTION_TYPE_REDIRECT;
base::Optional<base::Value> redirect_val =
base::JSONReader::Read(cases[i].redirect_dictionary_json);
ASSERT_TRUE(redirect_val);
std::u16string error;
rule.action.redirect = dnr_api::Redirect::FromValue(*redirect_val, &error);
ASSERT_TRUE(rule.action.redirect);
ASSERT_TRUE(error.empty());
IndexedRule indexed_rule;
ParseResult result = IndexedRule::CreateIndexedRule(
std::move(rule), GetBaseURL(), &indexed_rule);
EXPECT_EQ(cases[i].expected_result, result) << static_cast<int>(result);
if (result != ParseResult::SUCCESS)
continue;
EXPECT_TRUE(indexed_rule.url_transform || indexed_rule.redirect_url);
EXPECT_FALSE(indexed_rule.url_transform && indexed_rule.redirect_url);
EXPECT_EQ(cases[i].expected_redirect_url, indexed_rule.redirect_url);
}
}
TEST_F(IndexedRuleTest, RegexFilterParsing) {
struct {
std::string regex_filter;
ParseResult result;
} cases[] = {{"", ParseResult::ERROR_EMPTY_REGEX_FILTER},
// Filter with non-ascii characters.
{"αcd", ParseResult::ERROR_NON_ASCII_REGEX_FILTER},
// Invalid regex: Unterminated character class.
{"x[ab", ParseResult::ERROR_INVALID_REGEX_FILTER},
// Invalid regex: Incomplete capturing group.
{"x(", ParseResult::ERROR_INVALID_REGEX_FILTER},
// Invalid regex: Invalid escape sequence \x.
{R"(ij\x1)", ParseResult::ERROR_INVALID_REGEX_FILTER},
{R"(ij\\x1)", ParseResult::SUCCESS},
{R"(^http://www\.(abc|def)\.xyz\.com/)", ParseResult::SUCCESS}};
for (const auto& test_case : cases) {
SCOPED_TRACE(test_case.regex_filter);
dnr_api::Rule rule = CreateGenericParsedRule();
rule.condition.url_filter.reset();
rule.condition.regex_filter =
std::make_unique<std::string>(test_case.regex_filter);
IndexedRule indexed_rule;
ParseResult result = IndexedRule::CreateIndexedRule(
std::move(rule), GetBaseURL(), &indexed_rule);
EXPECT_EQ(result, test_case.result);
if (result == ParseResult::SUCCESS) {
EXPECT_EQ(indexed_rule.url_pattern, test_case.regex_filter);
EXPECT_EQ(flat_rule::UrlPatternType_REGEXP,
indexed_rule.url_pattern_type);
}
}
}
TEST_F(IndexedRuleTest, MultipleFiltersSpecified) {
dnr_api::Rule rule = CreateGenericParsedRule();
rule.condition.url_filter = std::make_unique<std::string>("google");
rule.condition.regex_filter = std::make_unique<std::string>("example");
IndexedRule indexed_rule;
ParseResult result = IndexedRule::CreateIndexedRule(
std::move(rule), GetBaseURL(), &indexed_rule);
EXPECT_EQ(ParseResult::ERROR_MULTIPLE_FILTERS_SPECIFIED, result);
}
TEST_F(IndexedRuleTest, RegexSubstitutionParsing) {
struct {
// |regex_filter| may be null.
const char* regex_filter;
std::string regex_substitution;
ParseResult result;
} cases[] = {
{nullptr, "http://google.com",
ParseResult::ERROR_REGEX_SUBSTITUTION_WITHOUT_FILTER},
// \0 in |regex_substitution| refers to the entire matching text.
{R"(^http://(.*)\.com/)", R"(https://redirect.com?referrer=\0)",
ParseResult::SUCCESS},
{R"(^http://google\.com?q1=(.*)&q2=(.*))",
R"(https://redirect.com?&q1=\0&q2=\2)", ParseResult::SUCCESS},
// Referencing invalid capture group.
{R"(^http://google\.com?q1=(.*)&q2=(.*))",
R"(https://redirect.com?&q1=\1&q2=\3)",
ParseResult::ERROR_INVALID_REGEX_SUBSTITUTION},
// Empty substitution.
{R"(^http://(.*)\.com/)", "",
ParseResult::ERROR_INVALID_REGEX_SUBSTITUTION},
// '\' must always be followed by a '\' or a digit.
{R"(^http://(.*)\.com/)", R"(https://example.com?q=\a)",
ParseResult::ERROR_INVALID_REGEX_SUBSTITUTION},
};
for (const auto& test_case : cases) {
SCOPED_TRACE(test_case.regex_substitution);
dnr_api::Rule rule = CreateGenericParsedRule();
rule.condition.url_filter.reset();
if (test_case.regex_filter) {
rule.condition.regex_filter =
std::make_unique<std::string>(test_case.regex_filter);
}
rule.priority = std::make_unique<int>(kMinValidPriority);
rule.action.type = dnr_api::RULE_ACTION_TYPE_REDIRECT;
rule.action.redirect = std::make_unique<dnr_api::Redirect>();
rule.action.redirect->regex_substitution =
std::make_unique<std::string>(test_case.regex_substitution);
IndexedRule indexed_rule;
ParseResult result = IndexedRule::CreateIndexedRule(
std::move(rule), GetBaseURL(), &indexed_rule);
EXPECT_EQ(test_case.result, result);
if (result == ParseResult::SUCCESS) {
EXPECT_EQ(flat_rule::UrlPatternType_REGEXP,
indexed_rule.url_pattern_type);
ASSERT_TRUE(indexed_rule.regex_substitution);
EXPECT_EQ(test_case.regex_substitution, *indexed_rule.regex_substitution);
}
}
}
// Tests the parsing behavior when multiple keys in "Redirect" dictionary are
// specified.
TEST_F(IndexedRuleTest, MultipleRedirectKeys) {
dnr_api::Rule rule = CreateGenericParsedRule();
rule.priority = std::make_unique<int>(kMinValidPriority);
rule.condition.url_filter.reset();
rule.condition.regex_filter = std::make_unique<std::string>("\\.*");
rule.action.type = dnr_api::RULE_ACTION_TYPE_REDIRECT;
rule.action.redirect = std::make_unique<dnr_api::Redirect>();
dnr_api::Redirect& redirect = *rule.action.redirect;
redirect.url = std::make_unique<std::string>("http://google.com");
redirect.regex_substitution =
std::make_unique<std::string>("http://example.com");
redirect.transform = std::make_unique<dnr_api::URLTransform>();
redirect.transform->scheme = std::make_unique<std::string>("https");
IndexedRule indexed_rule;
ParseResult result = IndexedRule::CreateIndexedRule(
std::move(rule), GetBaseURL(), &indexed_rule);
EXPECT_EQ(ParseResult::SUCCESS, result);
// The redirect "url" is given preference in this case.
EXPECT_FALSE(indexed_rule.url_transform);
EXPECT_FALSE(indexed_rule.regex_substitution);
EXPECT_EQ("http://google.com", indexed_rule.redirect_url);
}
TEST_F(IndexedRuleTest, InvalidAllowAllRequestsResourceType) {
using ResourceTypeVec = std::vector<dnr_api::ResourceType>;
struct {
ResourceTypeVec resource_types;
ResourceTypeVec excluded_resource_types;
const ParseResult expected_result;
// Only valid if |expected_result| is SUCCESS.
const uint16_t expected_element_types;
} cases[] = {
{{}, {}, ParseResult::ERROR_INVALID_ALLOW_ALL_REQUESTS_RESOURCE_TYPE, 0},
{{dnr_api::RESOURCE_TYPE_SUB_FRAME},
{dnr_api::RESOURCE_TYPE_SCRIPT},
ParseResult::SUCCESS,
flat_rule::ElementType_SUBDOCUMENT},
{{dnr_api::RESOURCE_TYPE_SCRIPT, dnr_api::RESOURCE_TYPE_MAIN_FRAME},
{},
ParseResult::ERROR_INVALID_ALLOW_ALL_REQUESTS_RESOURCE_TYPE,
0},
{{dnr_api::RESOURCE_TYPE_MAIN_FRAME, dnr_api::RESOURCE_TYPE_SUB_FRAME},
{},
ParseResult::SUCCESS,
flat_rule::ElementType_MAIN_FRAME | flat_rule::ElementType_SUBDOCUMENT},
{{dnr_api::RESOURCE_TYPE_MAIN_FRAME},
{},
ParseResult::SUCCESS,
flat_rule::ElementType_MAIN_FRAME},
};
for (size_t i = 0; i < base::size(cases); ++i) {
SCOPED_TRACE(base::StringPrintf("Testing case[%" PRIuS "]", i));
dnr_api::Rule rule = CreateGenericParsedRule();
if (cases[i].resource_types.empty())
rule.condition.resource_types = nullptr;
else
rule.condition.resource_types =
std::make_unique<ResourceTypeVec>(cases[i].resource_types);
rule.condition.excluded_resource_types =
std::make_unique<ResourceTypeVec>(cases[i].excluded_resource_types);
rule.action.type = dnr_api::RULE_ACTION_TYPE_ALLOWALLREQUESTS;
IndexedRule indexed_rule;
ParseResult result = IndexedRule::CreateIndexedRule(
std::move(rule), GetBaseURL(), &indexed_rule);
EXPECT_EQ(cases[i].expected_result, result);
if (result == ParseResult::SUCCESS)
EXPECT_EQ(cases[i].expected_element_types, indexed_rule.element_types);
}
}
TEST_F(IndexedRuleTest, ModifyHeadersParsing) {
struct RawHeaderInfo {
dnr_api::HeaderOperation operation;
std::string header;
base::Optional<std::string> value;
};
using RawHeaderInfoList = std::vector<RawHeaderInfo>;
using ModifyHeaderInfoList = std::vector<dnr_api::ModifyHeaderInfo>;
// A copy-able version of dnr_api::ModifyHeaderInfo is used for ease of
// specifying test cases because elements are copied when initializing a
// vector from an array.
struct {
base::Optional<RawHeaderInfoList> request_headers;
base::Optional<RawHeaderInfoList> response_headers;
ParseResult expected_result;
} cases[] = {
// Raise an error if no headers are specified.
{base::nullopt, base::nullopt, ParseResult::ERROR_NO_HEADERS_SPECIFIED},
// Raise an error if the request or response headers list is specified,
// but empty.
{RawHeaderInfoList(),
RawHeaderInfoList(
{{dnr_api::HEADER_OPERATION_REMOVE, "set-cookie", base::nullopt}}),
ParseResult::ERROR_EMPTY_REQUEST_HEADERS_LIST},
{base::nullopt, RawHeaderInfoList(),
ParseResult::ERROR_EMPTY_RESPONSE_HEADERS_LIST},
// Raise an error if a header list contains an empty or invalid header
// name.
{base::nullopt,
RawHeaderInfoList(
{{dnr_api::HEADER_OPERATION_REMOVE, "", base::nullopt}}),
ParseResult::ERROR_INVALID_HEADER_NAME},
{base::nullopt,
RawHeaderInfoList(
{{dnr_api::HEADER_OPERATION_REMOVE, "<<invalid>>", base::nullopt}}),
ParseResult::ERROR_INVALID_HEADER_NAME},
// Raise an error if a header list contains an invalid header value.
{base::nullopt,
RawHeaderInfoList({{dnr_api::HEADER_OPERATION_APPEND, "set-cookie",
"invalid\nvalue"}}),
ParseResult::ERROR_INVALID_HEADER_VALUE},
// Raise an error if a header value is specified for a remove rule.
{RawHeaderInfoList(
{{dnr_api::HEADER_OPERATION_REMOVE, "cookie", "remove"}}),
base::nullopt, ParseResult::ERROR_HEADER_VALUE_PRESENT},
// Raise an error if no header value is specified for an append or set
// rule.
{RawHeaderInfoList(
{{dnr_api::HEADER_OPERATION_SET, "cookie", base::nullopt}}),
base::nullopt, ParseResult::ERROR_HEADER_VALUE_NOT_SPECIFIED},
{base::nullopt,
RawHeaderInfoList(
{{dnr_api::HEADER_OPERATION_APPEND, "set-cookie", base::nullopt}}),
ParseResult::ERROR_HEADER_VALUE_NOT_SPECIFIED},
// Raise an error if a rule specifies a request header to be appended.
{RawHeaderInfoList(
{{dnr_api::HEADER_OPERATION_APPEND, "cookie", "cookie-value"}}),
base::nullopt, ParseResult::ERROR_APPEND_REQUEST_HEADER_UNSUPPORTED},
{RawHeaderInfoList(
{{dnr_api::HEADER_OPERATION_REMOVE, "cookie", base::nullopt},
{dnr_api::HEADER_OPERATION_SET, "referer", ""}}),
base::nullopt, ParseResult::SUCCESS},
{RawHeaderInfoList(
{{dnr_api::HEADER_OPERATION_REMOVE, "referer", base::nullopt}}),
RawHeaderInfoList(
{{dnr_api::HEADER_OPERATION_APPEND, "set-cookie", "abcd"}}),
ParseResult::SUCCESS},
};
for (size_t i = 0; i < base::size(cases); ++i) {
SCOPED_TRACE(base::StringPrintf("Testing case[%" PRIuS "]", i));
dnr_api::Rule rule = CreateGenericParsedRule();
rule.action.type = dnr_api::RULE_ACTION_TYPE_MODIFYHEADERS;
ModifyHeaderInfoList expected_request_headers;
if (cases[i].request_headers) {
rule.action.request_headers = std::make_unique<ModifyHeaderInfoList>();
for (auto header : *cases[i].request_headers) {
rule.action.request_headers->push_back(CreateModifyHeaderInfo(
header.operation, header.header, header.value));
expected_request_headers.push_back(CreateModifyHeaderInfo(
header.operation, header.header, header.value));
}
}
ModifyHeaderInfoList expected_response_headers;
if (cases[i].response_headers) {
rule.action.response_headers = std::make_unique<ModifyHeaderInfoList>();
for (auto header : *cases[i].response_headers) {
rule.action.response_headers->push_back(CreateModifyHeaderInfo(
header.operation, header.header, header.value));
expected_response_headers.push_back(CreateModifyHeaderInfo(
header.operation, header.header, header.value));
}
}
IndexedRule indexed_rule;
ParseResult result = IndexedRule::CreateIndexedRule(
std::move(rule), GetBaseURL(), &indexed_rule);
EXPECT_EQ(cases[i].expected_result, result);
if (result != ParseResult::SUCCESS)
continue;
EXPECT_EQ(dnr_api::RULE_ACTION_TYPE_MODIFYHEADERS,
indexed_rule.action_type);
EXPECT_TRUE(std::equal(
expected_request_headers.begin(), expected_request_headers.end(),
indexed_rule.request_headers.begin(), EqualsForTesting));
EXPECT_TRUE(std::equal(
expected_response_headers.begin(), expected_response_headers.end(),
indexed_rule.response_headers.begin(), EqualsForTesting));
}
}
} // namespace
} // namespace declarative_net_request
} // namespace extensions