blob: 51304181680c691737c90a1597b9c619e0d78e27 [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/macros.h"
#include "base/memory/ptr_util.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/stringprintf.h"
#include "components/version_info/version_info.h"
#include "extensions/browser/api/declarative_net_request/constants.h"
#include "extensions/common/api/declarative_net_request.h"
#include "extensions/common/features/feature_channel.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;
std::unique_ptr<dnr_api::Rule> CreateGenericParsedRule() {
auto rule = std::make_unique<dnr_api::Rule>();
rule->id = kMinValidID;
rule->condition.url_filter = std::make_unique<std::string>("filter");
rule->action.type = dnr_api::RULE_ACTION_TYPE_BLACKLIST;
return rule;
}
class IndexedRuleTest : public testing::Test {
public:
IndexedRuleTest() : channel_(::version_info::Channel::UNKNOWN) {}
private:
ScopedCurrentChannel channel_;
DISALLOW_COPY_AND_ASSIGN(IndexedRuleTest);
};
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 < arraysize(cases); ++i) {
SCOPED_TRACE(base::StringPrintf("Testing case[%" PRIuS "]", i));
std::unique_ptr<dnr_api::Rule> rule = CreateGenericParsedRule();
rule->id = cases[i].id;
IndexedRule indexed_rule;
ParseResult result =
IndexedRule::CreateIndexedRule(std::move(rule), &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 {
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),
ParseResult::ERROR_INVALID_REDIRECT_RULE_PRIORITY, kDefaultPriority},
{std::make_unique<int>(kMinValidPriority), ParseResult::SUCCESS,
kMinValidPriority},
{std::make_unique<int>(kMinValidPriority + 1), ParseResult::SUCCESS,
kMinValidPriority + 1},
{nullptr, ParseResult::ERROR_EMPTY_REDIRECT_RULE_PRIORITY,
kDefaultPriority},
};
for (size_t i = 0; i < arraysize(cases); ++i) {
SCOPED_TRACE(base::StringPrintf("Testing case[%" PRIuS "]", i));
std::unique_ptr<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");
IndexedRule indexed_rule;
ParseResult result =
IndexedRule::CreateIndexedRule(std::move(rule), &indexed_rule);
EXPECT_EQ(cases[i].expected_result, result);
if (result == ParseResult::SUCCESS)
EXPECT_EQ(cases[i].expected_priority, indexed_rule.priority);
}
// Ensure priority is ignored for non-redirect rules.
{
std::unique_ptr<dnr_api::Rule> rule = CreateGenericParsedRule();
rule->priority = std::make_unique<int>(5);
IndexedRule indexed_rule;
ParseResult result =
IndexedRule::CreateIndexedRule(std::move(rule), &indexed_rule);
EXPECT_EQ(ParseResult::SUCCESS, result);
EXPECT_EQ(static_cast<uint32_t>(kDefaultPriority), 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_BLACKLIST, 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_WHITELIST,
std::make_unique<bool>(true),
flat_rule::OptionFlag_IS_WHITELIST |
flat_rule::OptionFlag_APPLIES_TO_FIRST_PARTY |
flat_rule::OptionFlag_IS_MATCH_CASE},
};
for (size_t i = 0; i < arraysize(cases); ++i) {
SCOPED_TRACE(base::StringPrintf("Testing case[%" PRIuS "]", i));
std::unique_ptr<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), &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},
{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_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_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>(ResourceTypeVec()),
std::make_unique<ResourceTypeVec>(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 < arraysize(cases); ++i) {
SCOPED_TRACE(base::StringPrintf("Testing case[%" PRIuS "]", i));
std::unique_ptr<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), &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},
};
for (size_t i = 0; i < arraysize(cases); ++i) {
SCOPED_TRACE(base::StringPrintf("Testing case[%" PRIuS "]", i));
std::unique_ptr<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), &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);
}
}
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>(DomainVec()),
nullptr,
ParseResult::ERROR_EMPTY_DOMAINS_LIST,
{},
{}},
{nullptr,
std::make_unique<DomainVec>(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"}}};
for (size_t i = 0; i < arraysize(cases); ++i) {
SCOPED_TRACE(base::StringPrintf("Testing case[%" PRIuS "]", i));
std::unique_ptr<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), &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 {
std::unique_ptr<std::string> redirect_url;
const ParseResult expected_result;
// Only valid if |expected_result| is SUCCESS.
const std::string expected_redirect_url;
} cases[] = {{std::make_unique<std::string>(""),
ParseResult::ERROR_EMPTY_REDIRECT_URL, ""},
{nullptr, ParseResult::ERROR_EMPTY_REDIRECT_URL, ""},
{std::make_unique<std::string>("http://google.com"),
ParseResult::SUCCESS, "http://google.com"},
{std::make_unique<std::string>("abc"),
ParseResult::ERROR_INVALID_REDIRECT_URL, ""}};
for (size_t i = 0; i < arraysize(cases); ++i) {
SCOPED_TRACE(base::StringPrintf("Testing case[%" PRIuS "]", i));
std::unique_ptr<dnr_api::Rule> rule = CreateGenericParsedRule();
rule->action.redirect_url = std::move(cases[i].redirect_url);
rule->action.type = dnr_api::RULE_ACTION_TYPE_REDIRECT;
rule->priority = std::make_unique<int>(kMinValidPriority);
IndexedRule indexed_rule;
ParseResult result =
IndexedRule::CreateIndexedRule(std::move(rule), &indexed_rule);
EXPECT_EQ(cases[i].expected_result, result);
if (result == ParseResult::SUCCESS)
EXPECT_EQ(cases[i].expected_redirect_url, indexed_rule.redirect_url);
}
}
} // namespace
} // namespace declarative_net_request
} // namespace extensions