[go: nahoru, domu]

blob: 031ca86eff5fe4e5449875f61b56c35ebeb7f33b [file] [log] [blame]
// Copyright 2019 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/file_sequence_helper.h"
#include <functional>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/files/file_util.h"
#include "base/run_loop.h"
#include "base/task/post_task.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/threading/thread_restrictions.h"
#include "components/crx_file/id_util.h"
#include "extensions/browser/api/declarative_net_request/constants.h"
#include "extensions/browser/api/declarative_net_request/file_backed_ruleset_source.h"
#include "extensions/browser/api/declarative_net_request/parse_info.h"
#include "extensions/browser/api/declarative_net_request/rules_count_pair.h"
#include "extensions/browser/api/declarative_net_request/test_utils.h"
#include "extensions/browser/api/declarative_net_request/utils.h"
#include "extensions/browser/extension_file_task_runner.h"
#include "extensions/browser/extensions_test.h"
#include "extensions/common/api/declarative_net_request.h"
#include "extensions/common/api/declarative_net_request/constants.h"
#include "extensions/common/api/declarative_net_request/test_utils.h"
#include "services/data_decoder/public/cpp/test_support/in_process_data_decoder.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace extensions {
namespace declarative_net_request {
namespace {
api::declarative_net_request::Rule GetAPIRule(const TestRule& rule) {
std::unique_ptr<base::DictionaryValue> value = rule.ToValue();
EXPECT_TRUE(value);
api::declarative_net_request::Rule result;
std::u16string error;
EXPECT_TRUE(
api::declarative_net_request::Rule::Populate(*value, &result, &error))
<< error;
EXPECT_TRUE(error.empty()) << error;
return result;
}
struct TestLoadRulesetInfo {
bool has_new_checksum = false;
base::Optional<bool> reindexing_successful;
base::Optional<LoadRulesetResult> load_result;
};
struct TestCase {
explicit TestCase(FileBackedRulesetSource source)
: source(std::move(source)) {}
int checksum;
FileBackedRulesetSource source;
TestLoadRulesetInfo expected_result;
};
ExtensionId GenerateDummyExtensionID() {
return crx_file::id_util::GenerateId("dummy_extension");
}
class FileSequenceHelperTest : public ExtensionsTest {
public:
FileSequenceHelperTest() = default;
// ExtensionsTest overrides:
void SetUp() override {
ExtensionsTest::SetUp();
helper_ = std::make_unique<FileSequenceHelper>();
}
void TearDown() override {
GetExtensionFileTaskRunner()->DeleteSoon(FROM_HERE, std::move(helper_));
base::RunLoop().RunUntilIdle();
ExtensionsTest::TearDown();
}
void TestAddDynamicRules(
FileBackedRulesetSource source,
std::vector<api::declarative_net_request::Rule> rules_to_add,
ReadJSONRulesResult::Status expected_read_status,
UpdateDynamicRulesStatus expected_update_status,
base::Optional<std::string> expected_error,
bool expected_did_load_successfully) {
base::RunLoop run_loop;
auto add_rules_callback = base::BindOnce(
[](base::RunLoop* run_loop, bool expected_did_load_successfully,
base::Optional<std::string> expected_error, LoadRequestData data,
base::Optional<std::string> error) {
EXPECT_EQ(1u, data.rulesets.size());
EXPECT_EQ(expected_error, error) << error.value_or("no actual error");
EXPECT_EQ(expected_did_load_successfully,
data.rulesets[0].did_load_successfully());
run_loop->Quit();
},
&run_loop, expected_did_load_successfully, expected_error);
ExtensionId extension_id = crx_file::id_util::GenerateId("dummy_extension");
LoadRequestData data(extension_id);
data.rulesets.emplace_back(std::move(source));
// Unretained is safe because |helper_| outlives the |add_rules_task|.
auto add_rules_task = base::BindOnce(
&FileSequenceHelper::UpdateDynamicRules,
base::Unretained(helper_.get()), std::move(data),
/* rule_ids_to_remove */ std::vector<int>(), std::move(rules_to_add),
RulesCountPair(GetDynamicAndSessionRuleLimit(), GetRegexRuleLimit()),
std::move(add_rules_callback));
base::HistogramTester tester;
GetExtensionFileTaskRunner()->PostTask(FROM_HERE,
std::move(add_rules_task));
run_loop.Run();
tester.ExpectUniqueSample(kUpdateDynamicRulesStatusHistogram,
expected_update_status, 1 /* expected_count */);
tester.ExpectUniqueSample(kReadDynamicRulesJSONStatusHistogram,
expected_read_status, 1 /* expected_count */);
}
void TestLoadRulesets(const std::vector<TestCase>& test_cases) {
LoadRequestData data(GenerateDummyExtensionID());
for (const auto& test_case : test_cases) {
data.rulesets.emplace_back(test_case.source.Clone());
data.rulesets.back().set_expected_checksum(test_case.checksum);
}
base::RunLoop run_loop;
auto load_ruleset_callback = base::BindOnce(
[](base::RunLoop* run_loop, const std::vector<TestCase>& test_cases,
LoadRequestData data) {
// Verify |data| is as expected.
ASSERT_EQ(data.rulesets.size(), test_cases.size());
for (size_t i = 0; i < data.rulesets.size(); i++) {
SCOPED_TRACE(base::StringPrintf("Testing ruleset %" PRIuS, i));
const RulesetInfo& ruleset = data.rulesets[i];
const TestLoadRulesetInfo& expected_result =
test_cases[i].expected_result;
EXPECT_EQ(expected_result.has_new_checksum,
ruleset.new_checksum().has_value());
EXPECT_EQ(expected_result.reindexing_successful,
ruleset.reindexing_successful());
ASSERT_TRUE(ruleset.load_ruleset_result());
EXPECT_EQ(expected_result.load_result,
ruleset.load_ruleset_result())
<< *ruleset.load_ruleset_result();
}
run_loop->Quit();
},
&run_loop, std::cref(test_cases));
// Unretained is safe because |helper_| outlives the |load_ruleset_task|.
auto load_ruleset_task = base::BindOnce(
&FileSequenceHelper::LoadRulesets, base::Unretained(helper_.get()),
std::move(data), std::move(load_ruleset_callback));
GetExtensionFileTaskRunner()->PostTask(FROM_HERE,
std::move(load_ruleset_task));
run_loop.Run();
}
void TestNoRulesetsToLoad() {
LoadRequestData data(GenerateDummyExtensionID());
base::RunLoop run_loop;
auto load_ruleset_callback = base::BindOnce(
[](base::RunLoop* run_loop, LoadRequestData data) { run_loop->Quit(); },
&run_loop);
auto load_ruleset_task = base::BindOnce(
&FileSequenceHelper::LoadRulesets, base::Unretained(helper_.get()),
std::move(data), std::move(load_ruleset_callback));
GetExtensionFileTaskRunner()->PostTask(FROM_HERE,
std::move(load_ruleset_task));
run_loop.Run();
}
// Initialize |num_rulesets| rulesets and returns the corresponding test
// cases.
std::vector<TestCase> InitializeRulesets(size_t num_rulesets) const {
std::vector<TestCase> test_cases;
test_cases.reserve(num_rulesets);
for (size_t i = 0; i < num_rulesets; i++) {
test_cases.emplace_back(CreateTemporarySource());
auto& test_case = test_cases.back();
std::unique_ptr<RulesetMatcher> matcher;
EXPECT_TRUE(CreateVerifiedMatcher({CreateGenericRule()}, test_case.source,
&matcher, &test_case.checksum));
// Initially loading all the rulesets should succeed.
test_case.expected_result.load_result = LoadRulesetResult::kSuccess;
}
return test_cases;
}
private:
std::unique_ptr<FileSequenceHelper> helper_;
// Required to use DataDecoder's JSON parsing for re-indexing.
data_decoder::test::InProcessDataDecoder in_process_data_decoder_;
DISALLOW_COPY_AND_ASSIGN(FileSequenceHelperTest);
};
TEST_F(FileSequenceHelperTest, NoRulesetsToLoad) {
TestNoRulesetsToLoad();
}
TEST_F(FileSequenceHelperTest, IndexedRulesetDeleted) {
const size_t kNumRulesets = 3;
std::vector<TestCase> test_cases = InitializeRulesets(kNumRulesets);
TestLoadRulesets(test_cases);
// Now delete the first and third indexed rulesets. This would cause a
// re-index.
base::DeleteFile(test_cases[0].source.indexed_path());
base::DeleteFile(test_cases[2].source.indexed_path());
test_cases[0].expected_result.reindexing_successful = true;
test_cases[2].expected_result.reindexing_successful = true;
TestLoadRulesets(test_cases);
// The files should have been re-indexed.
EXPECT_TRUE(base::PathExists(test_cases[0].source.indexed_path()));
EXPECT_TRUE(base::PathExists(test_cases[2].source.indexed_path()));
}
TEST_F(FileSequenceHelperTest, ChecksumMismatch) {
const size_t kNumRulesets = 4;
std::vector<TestCase> test_cases = InitializeRulesets(kNumRulesets);
TestLoadRulesets(test_cases);
// Change the expected checksum for rulesets 2 and 3. Loading both of the
// rulesets should now fail due to a checksum mismatch.
test_cases[1].checksum--;
test_cases[2].checksum--;
test_cases[1].expected_result.load_result =
LoadRulesetResult::kErrorChecksumMismatch;
test_cases[2].expected_result.load_result =
LoadRulesetResult::kErrorChecksumMismatch;
test_cases[1].expected_result.reindexing_successful = false;
test_cases[2].expected_result.reindexing_successful = false;
TestLoadRulesets(test_cases);
}
TEST_F(FileSequenceHelperTest, RulesetFormatVersionMismatch) {
const size_t kNumRulesets = 4;
std::vector<TestCase> test_cases = InitializeRulesets(kNumRulesets);
TestLoadRulesets(test_cases);
// Now simulate a flatbuffer version mismatch.
ScopedIncrementRulesetVersion scoped_version_change =
CreateScopedIncrementRulesetVersionForTesting();
// Version mismatch will cause reindexing and updated checksums.
for (auto& test_case : test_cases) {
test_case.expected_result.reindexing_successful = true;
test_case.expected_result.has_new_checksum = true;
test_case.expected_result.load_result = LoadRulesetResult::kSuccess;
}
TestLoadRulesets(test_cases);
}
TEST_F(FileSequenceHelperTest, JSONAndIndexedRulesetDeleted) {
const size_t kNumRulesets = 3;
std::vector<TestCase> test_cases = InitializeRulesets(kNumRulesets);
TestLoadRulesets(test_cases);
base::DeleteFile(test_cases[0].source.json_path());
base::DeleteFile(test_cases[1].source.json_path());
base::DeleteFile(test_cases[0].source.indexed_path());
base::DeleteFile(test_cases[1].source.indexed_path());
// Reindexing will fail since the JSON ruleset is now deleted.
test_cases[0].expected_result.reindexing_successful = false;
test_cases[1].expected_result.reindexing_successful = false;
test_cases[0].expected_result.load_result =
LoadRulesetResult::kErrorInvalidPath;
test_cases[1].expected_result.load_result =
LoadRulesetResult::kErrorInvalidPath;
test_cases[2].expected_result.load_result = LoadRulesetResult::kSuccess;
TestLoadRulesets(test_cases);
}
// Tests updating dynamic rules.
TEST_F(FileSequenceHelperTest, UpdateDynamicRules) {
// Simulate adding rules for the first time i.e. with no JSON and indexed
// ruleset files.
FileBackedRulesetSource source = CreateTemporarySource();
base::DeleteFile(source.json_path());
base::DeleteFile(source.indexed_path());
// Test success.
std::vector<api::declarative_net_request::Rule> api_rules;
{
SCOPED_TRACE("Test adding a valid rule");
api_rules.push_back(GetAPIRule(CreateGenericRule()));
TestAddDynamicRules(source.Clone(), std::move(api_rules),
ReadJSONRulesResult::Status::kFileDoesNotExist,
UpdateDynamicRulesStatus::kSuccess,
base::nullopt /* expected_error */,
true /* expected_did_load_successfully*/);
}
// Test adding an invalid rule, e.g. a rule with invalid priority.
{
SCOPED_TRACE("Test adding an invalid rule");
TestRule rule = CreateGenericRule();
rule.id = kMinValidID + 1;
rule.action->type = std::string("redirect");
rule.action->redirect.emplace();
rule.action->redirect->url = std::string("http://google.com");
rule.priority = kMinValidPriority - 1;
api_rules.clear();
api_rules.push_back(GetAPIRule(rule));
int rule_id = kMinValidID + 1;
ParseInfo info(ParseResult::ERROR_INVALID_RULE_PRIORITY, &rule_id);
TestAddDynamicRules(source.Clone(), std::move(api_rules),
ReadJSONRulesResult::Status::kSuccess,
UpdateDynamicRulesStatus::kErrorInvalidRules,
info.error(),
false /* expected_did_load_successfully */);
}
// Write invalid JSON to the JSON rules file. The update should still succeed.
{
base::ScopedAllowBlockingForTesting allow_blocking_for_testing;
std::string data = "Invalid JSON";
ASSERT_EQ(data.size(), static_cast<size_t>(base::WriteFile(
source.json_path(), data.c_str(), data.size())));
}
{
SCOPED_TRACE("Test corrupted JSON rules file");
TestRule rule = CreateGenericRule();
rule.id = kMinValidID + 2;
api_rules.clear();
api_rules.push_back(GetAPIRule(rule));
TestAddDynamicRules(source.Clone(), std::move(api_rules),
ReadJSONRulesResult::Status::kJSONParseError,
UpdateDynamicRulesStatus::kSuccess,
base::nullopt /* expected_error */,
true /* expected_did_load_successfully*/);
}
}
} // namespace
} // namespace declarative_net_request
} // namespace extensions