blob: 54cfa1f01a6d227a73df708e37a18c349a58d324 [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/ruleset_matcher.h"
#include <utility>
#include <vector>
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "components/url_pattern_index/flat/url_pattern_index_generated.h"
#include "components/version_info/version_info.h"
#include "extensions/browser/api/declarative_net_request/constants.h"
#include "extensions/browser/api/declarative_net_request/ruleset_source.h"
#include "extensions/browser/api/declarative_net_request/test_utils.h"
#include "extensions/browser/api/declarative_net_request/utils.h"
#include "extensions/common/api/declarative_net_request/constants.h"
#include "extensions/common/api/declarative_net_request/test_utils.h"
#include "extensions/common/features/feature_channel.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace extensions {
namespace declarative_net_request {
namespace {
class RulesetMatcherTest : public ::testing::Test {
public:
RulesetMatcherTest() : channel_(::version_info::Channel::UNKNOWN) {}
private:
// Run this on the trunk channel to ensure the API is available.
ScopedCurrentChannel channel_;
DISALLOW_COPY_AND_ASSIGN(RulesetMatcherTest);
};
// Tests a simple blocking rule.
TEST_F(RulesetMatcherTest, BlockingRule) {
TestRule rule = CreateGenericRule();
rule.condition->url_filter = std::string("google.com");
std::unique_ptr<RulesetMatcher> matcher;
ASSERT_TRUE(CreateVerifiedMatcher({rule}, CreateTemporarySource(), &matcher));
auto should_block_request = [&matcher](const RequestParams& params) {
return !matcher->HasMatchingAllowRule(params) &&
matcher->HasMatchingBlockRule(params);
};
GURL google_url("http://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_block_request(params));
GURL yahoo_url("http://yahoo.com");
params.url = &yahoo_url;
EXPECT_FALSE(should_block_request(params));
}
// Tests a simple redirect rule.
TEST_F(RulesetMatcherTest, RedirectRule) {
TestRule rule = CreateGenericRule();
rule.condition->url_filter = std::string("google.com");
rule.priority = kMinValidPriority;
rule.action->type = std::string("redirect");
rule.action->redirect.emplace();
rule.action->redirect->url = std::string("http://yahoo.com");
std::unique_ptr<RulesetMatcher> matcher;
ASSERT_TRUE(CreateVerifiedMatcher({rule}, CreateTemporarySource(), &matcher));
auto should_redirect_request = [&matcher](const RequestParams& params,
GURL* redirect_url) {
return matcher->GetRedirectRule(params, redirect_url) != nullptr;
};
GURL google_url("http://google.com");
GURL yahoo_url("http://yahoo.com");
RequestParams params;
params.url = &google_url;
params.element_type = url_pattern_index::flat::ElementType_SUBDOCUMENT;
params.is_third_party = true;
GURL redirect_url;
EXPECT_TRUE(should_redirect_request(params, &redirect_url));
EXPECT_EQ(yahoo_url, redirect_url);
params.url = &yahoo_url;
EXPECT_FALSE(should_redirect_request(params, &redirect_url));
}
// Test that a URL cannot redirect to itself, as filed in crbug.com/954646.
TEST_F(RulesetMatcherTest, PreventSelfRedirect) {
TestRule rule = CreateGenericRule();
rule.condition->url_filter = std::string("go*");
rule.priority = kMinValidPriority;
rule.action->type = std::string("redirect");
rule.action->redirect.emplace();
rule.action->redirect->url = std::string("http://google.com");
std::unique_ptr<RulesetMatcher> matcher;
ASSERT_TRUE(CreateVerifiedMatcher({rule}, CreateTemporarySource(), &matcher));
GURL url("http://google.com");
RequestParams params;
params.url = &url;
params.element_type = url_pattern_index::flat::ElementType_SUBDOCUMENT;
params.is_third_party = true;
GURL 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.
TEST_F(RulesetMatcherTest, FailedVerification) {
RulesetSource source = CreateTemporarySource();
std::unique_ptr<RulesetMatcher> matcher;
int expected_checksum;
ASSERT_TRUE(CreateVerifiedMatcher({}, source, &matcher, &expected_checksum));
// Persist invalid data to the ruleset file and ensure that a version mismatch
// occurs.
std::string data = "invalid data";
ASSERT_EQ(static_cast<int>(data.size()),
base::WriteFile(source.indexed_path(), data.c_str(), data.size()));
EXPECT_EQ(RulesetMatcher::kLoadErrorVersionMismatch,
RulesetMatcher::CreateVerifiedMatcher(source, expected_checksum,
&matcher));
// Now, persist invalid data to the ruleset file, while maintaining the
// correct version header. Ensure that it fails verification due to checksum
// mismatch.
data = GetVersionHeaderForTesting() + "invalid data";
ASSERT_EQ(static_cast<int>(data.size()),
base::WriteFile(source.indexed_path(), data.c_str(), data.size()));
EXPECT_EQ(RulesetMatcher::kLoadErrorChecksumMismatch,
RulesetMatcher::CreateVerifiedMatcher(source, expected_checksum,
&matcher));
}
// Tests IsExtraHeadersMatcher and GetRemoveHeadersMask.
TEST_F(RulesetMatcherTest, RemoveHeaders) {
TestRule rule = CreateGenericRule();
rule.condition->url_filter = std::string("example.com");
std::unique_ptr<RulesetMatcher> matcher;
ASSERT_TRUE(CreateVerifiedMatcher({rule}, CreateTemporarySource(), &matcher));
EXPECT_FALSE(matcher->IsExtraHeadersMatcher());
GURL example_url("http://example.com");
RequestParams params;
params.url = &example_url;
params.element_type = url_pattern_index::flat::ElementType_SUBDOCUMENT;
params.is_third_party = true;
EXPECT_EQ(0u, matcher->GetRemoveHeadersMask(params, 0u /* ignored_mask */));
rule.action->type = std::string("removeHeaders");
rule.action->remove_headers_list =
std::vector<std::string>({"referer", "cookie", "setCookie"});
ASSERT_TRUE(CreateVerifiedMatcher({rule}, CreateTemporarySource(), &matcher));
EXPECT_TRUE(matcher->IsExtraHeadersMatcher());
EXPECT_EQ(kRemoveHeadersMask_Referer | kRemoveHeadersMask_Cookie |
kRemoveHeadersMask_SetCookie,
matcher->GetRemoveHeadersMask(params, 0u /* ignored_mask */));
GURL google_url("http://google.com");
params.url = &google_url;
EXPECT_EQ(0u, matcher->GetRemoveHeadersMask(params, 0u /* ignored_mask */));
uint8_t ignored_mask =
kRemoveHeadersMask_Referer | kRemoveHeadersMask_SetCookie;
EXPECT_EQ(0u, matcher->GetRemoveHeadersMask(params, ignored_mask));
// The current mask is ignored while matching and is not returned as part of
// the result.
params.url = &example_url;
EXPECT_EQ(kRemoveHeadersMask_Cookie,
matcher->GetRemoveHeadersMask(params, ignored_mask));
}
// Tests a rule to redirect to an extension path.
TEST_F(RulesetMatcherTest, RedirectToExtensionPath) {
TestRule rule = CreateGenericRule();
rule.condition->url_filter = std::string("example.com");
rule.action->type = std::string("redirect");
rule.priority = kMinValidPriority;
rule.action->redirect.emplace();
rule.action->redirect->extension_path = "/path/newfile.js?query#fragment";
std::unique_ptr<RulesetMatcher> matcher;
const size_t kId = 1;
const size_t kPriority = 1;
const size_t kRuleCountLimit = 10;
const ExtensionId extension_id = "extension_id";
ASSERT_TRUE(CreateVerifiedMatcher(
{rule},
CreateTemporarySource(kId, kPriority, kRuleCountLimit, extension_id),
&matcher));
GURL example_url("http://example.com");
RequestParams params;
params.url = &example_url;
GURL redirect_url;
EXPECT_TRUE(matcher->GetRedirectRule(params, &redirect_url));
GURL expected_redirect_url(
"chrome-extension://extension_id/path/newfile.js?query#fragment");
EXPECT_EQ(expected_redirect_url, redirect_url);
}
// Tests a rule to redirect to a static url.
TEST_F(RulesetMatcherTest, RedirectToStaticUrl) {
TestRule rule = CreateGenericRule();
rule.condition->url_filter = std::string("example.com");
rule.action->type = std::string("redirect");
rule.priority = kMinValidPriority;
rule.action->redirect.emplace();
rule.action->redirect->url = "https://google.com";
std::unique_ptr<RulesetMatcher> matcher;
ASSERT_TRUE(CreateVerifiedMatcher({rule}, CreateTemporarySource(), &matcher));
GURL example_url("http://example.com");
RequestParams params;
params.url = &example_url;
GURL redirect_url;
GURL expected_redirect_url("https://google.com");
EXPECT_TRUE(matcher->GetRedirectRule(params, &redirect_url));
EXPECT_EQ(expected_redirect_url, redirect_url);
}
// Tests url transformation rules.
TEST_F(RulesetMatcherTest, UrlTransform) {
struct TestCase {
std::string url;
// Valid if a redirect is expected.
base::Optional<std::string> expected_redirect_url;
};
std::vector<TestCase> cases;
std::vector<TestRule> rules;
auto create_transform_rule = [](size_t id, const std::string& filter) {
TestRule rule = CreateGenericRule();
rule.id = id;
rule.condition->url_filter = filter;
rule.priority = kMinValidPriority;
rule.action->type = std::string("redirect");
rule.action->redirect.emplace();
rule.action->redirect->transform.emplace();
return rule;
};
TestRule rule = create_transform_rule(1, "||1.com");
rule.action->redirect->transform->scheme = "https";
rules.push_back(rule);
cases.push_back({"http://1.com/path?query", "https://1.com/path?query"});
rule = create_transform_rule(2, "||2.com");
rule.action->redirect->transform->scheme = "ftp";
rule.action->redirect->transform->host = "ftp.host.com";
rule.action->redirect->transform->port = "70";
rules.push_back(rule);
cases.push_back(
{"http://2.com:100/path?query", "ftp://ftp.host.com:70/path?query"});
rule = create_transform_rule(3, "||3.com");
rule.action->redirect->transform->port = "";
rule.action->redirect->transform->path = "";
rule.action->redirect->transform->query = "?new_query";
rule.action->redirect->transform->fragment = "#fragment";
rules.push_back(rule);
// The path separator '/' is output even when cleared, except when there is no
// authority, query and fragment.
cases.push_back(
{"http://3.com:100/path?query", "http://3.com/?new_query#fragment"});
rule = create_transform_rule(4, "||4.com");
rule.action->redirect->transform->scheme = "http";
rule.action->redirect->transform->path = " ";
rules.push_back(rule);
cases.push_back({"http://4.com/xyz", "http://4.com/%20"});
rule = create_transform_rule(5, "||5.com");
rule.action->redirect->transform->path = "/";
rule.action->redirect->transform->query = "";
rule.action->redirect->transform->fragment = "#";
rules.push_back(rule);
cases.push_back(
{"http://5.com:100/path?query#fragment", "http://5.com:100/#"});
rule = create_transform_rule(6, "||6.com");
rule.action->redirect->transform->path = "/path?query";
rules.push_back(rule);
// The "?" in path is url encoded since it's not part of the query.
cases.push_back({"http://6.com/xyz?q1", "http://6.com/path%3Fquery?q1"});
rule = create_transform_rule(7, "||7.com");
rule.action->redirect->transform->username = "new_user";
rule.action->redirect->transform->password = "new_pass";
rules.push_back(rule);
cases.push_back(
{"http://user@7.com/xyz", "http://new_user:new_pass@7.com/xyz"});
auto make_query = [](const std::string& key, const std::string& value) {
TestRuleQueryKeyValue query;
query.key = key;
query.value = value;
return query;
};
rule = create_transform_rule(8, "||8.com");
rule.action->redirect->transform->query_transform.emplace();
rule.action->redirect->transform->query_transform->remove_params =
std::vector<std::string>({"r1", "r2"});
rule.action->redirect->transform->query_transform->add_or_replace_params =
std::vector<TestRuleQueryKeyValue>(
{make_query("a1", "#"), make_query("a2", ""),
make_query("a1", "new2"), make_query("a1", "new3")});
rules.push_back(rule);
cases.push_back(
{"http://8.com/"
"path?r1&r1=val1&a1=val1&r2=val&x3=val&a1=val2&a2=val&r1=val2",
"http://8.com/path?a1=%23&x3=val&a1=new2&a2=&a1=new3"});
cases.push_back({"http://8.com/path?query",
"http://8.com/path?query=&a1=%23&a2=&a1=new2&a1=new3"});
rule = create_transform_rule(9, "||9.com");
rule.action->redirect->transform->query_transform.emplace();
rule.action->redirect->transform->query_transform->remove_params =
std::vector<std::string>({"r1", "r2"});
rules.push_back(rule);
// No redirect is performed since the url won't change.
cases.push_back({"http://9.com/path?query#fragment", base::nullopt});
rule = create_transform_rule(10, "||10.com");
rule.action->redirect->transform->query_transform.emplace();
rule.action->redirect->transform->query_transform->remove_params =
std::vector<std::string>({"q1"});
rule.action->redirect->transform->query_transform->add_or_replace_params =
std::vector<TestRuleQueryKeyValue>({make_query("q1", "new")});
rules.push_back(rule);
cases.push_back(
{"https://10.com/path?q1=1&q1=2&q1=3", "https://10.com/path?q1=new"});
std::unique_ptr<RulesetMatcher> matcher;
ASSERT_TRUE(CreateVerifiedMatcher(rules, CreateTemporarySource(), &matcher));
for (const auto& test_case : cases) {
SCOPED_TRACE(base::StringPrintf("Testing url %s", test_case.url.c_str()));
GURL url(test_case.url);
ASSERT_TRUE(url.is_valid()) << test_case.url;
RequestParams params;
params.url = &url;
GURL redirect_url;
if (!test_case.expected_redirect_url) {
EXPECT_FALSE(matcher->GetRedirectRule(params, &redirect_url))
<< redirect_url.spec();
continue;
}
ASSERT_TRUE(GURL(*test_case.expected_redirect_url).is_valid())
<< *test_case.expected_redirect_url;
EXPECT_TRUE(matcher->GetRedirectRule(params, &redirect_url));
EXPECT_EQ(*test_case.expected_redirect_url, redirect_url.spec());
}
}
} // namespace
} // namespace declarative_net_request
} // namespace extensions