blob: 6bc6f792e301b3f1f28e558492899f4f47eece0e [file] [log] [blame]
// Copyright 2016 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 "components/subresource_filter/core/common/unindexed_ruleset.h"
#include <memory>
#include <string>
#include <vector>
#include "base/macros.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/string_number_conversions.h"
#include "components/url_pattern_index/url_pattern.h"
#include "components/url_pattern_index/url_rule_test_support.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/protobuf/src/google/protobuf/io/zero_copy_stream.h"
#include "third_party/protobuf/src/google/protobuf/io/zero_copy_stream_impl_lite.h"
namespace subresource_filter {
namespace {
namespace proto = url_pattern_index::proto;
namespace testing = url_pattern_index::testing;
using url_pattern_index::UrlPattern;
bool IsEqual(const proto::UrlRule& lhs, const proto::UrlRule& rhs) {
return lhs.SerializeAsString() == rhs.SerializeAsString();
}
// The helper class used for building UnindexedRulesets.
class UnindexedRulesetTestBuilder {
public:
// Initializes the builder that writes the ruleset to StringOutputStream.
UnindexedRulesetTestBuilder()
: output_(
new google::protobuf::io::StringOutputStream(&ruleset_contents_)),
ruleset_writer_(output_.get()) {}
// Initializes the builder that writes the ruleset to an array of |array_size|
// through an ArrayOutputStream.
UnindexedRulesetTestBuilder(int array_size)
: ruleset_contents_(array_size, '\0'),
output_(
new google::protobuf::io::ArrayOutputStream(&ruleset_contents_[0],
array_size)),
ruleset_writer_(output_.get()) {}
int max_rules_per_chunk() const {
return ruleset_writer_.max_rules_per_chunk();
}
bool AddUrlRule(const UrlPattern& url_pattern,
proto::SourceType source_type,
bool is_whitelist = false) {
auto rule = testing::MakeUrlRule(url_pattern);
if (is_whitelist)
rule.set_semantics(proto::RULE_SEMANTICS_WHITELIST);
rule.set_source_type(source_type);
url_rules_.push_back(rule);
return !ruleset_writer_.had_error() &&
ruleset_writer_.AddUrlRule(url_rules_.back());
}
bool AddUrlRules(int number_of_rules) {
for (int i = 0; i < number_of_rules; ++i) {
std::string url_pattern = "example" + base::NumberToString(i) + ".com";
if (!AddUrlRule(UrlPattern(url_pattern), testing::kAnyParty, i & 1))
return false;
}
return true;
}
bool Finish() {
if (!ruleset_writer_.had_error() && ruleset_writer_.Finish()) {
// Note: This line has effect only when |output_| is an ArrayOutputStream.
ruleset_contents_.resize(output_->ByteCount());
return true;
}
return false;
}
const std::vector<proto::UrlRule>& url_rules() const { return url_rules_; }
const std::string& ruleset_contents() const { return ruleset_contents_; }
private:
std::vector<proto::UrlRule> url_rules_;
std::string ruleset_contents_;
std::unique_ptr<google::protobuf::io::ZeroCopyOutputStream> output_;
UnindexedRulesetWriter ruleset_writer_;
DISALLOW_COPY_AND_ASSIGN(UnindexedRulesetTestBuilder);
};
bool IsRulesetValid(const std::string& ruleset_contents,
const std::vector<proto::UrlRule>& expected_url_rules) {
google::protobuf::io::ArrayInputStream array_input(ruleset_contents.data(),
ruleset_contents.size());
UnindexedRulesetReader reader(&array_input);
proto::FilteringRules chunk;
std::vector<proto::UrlRule> read_rules;
while (reader.ReadNextChunk(&chunk)) {
read_rules.insert(read_rules.end(), chunk.url_rules().begin(),
chunk.url_rules().end());
}
if (base::checked_cast<size_t>(reader.num_bytes_read()) !=
ruleset_contents.size()) {
return false;
}
if (expected_url_rules.size() != read_rules.size())
return false;
for (size_t i = 0, size = read_rules.size(); i != size; ++i) {
if (!IsEqual(expected_url_rules[i], read_rules[i]))
return false;
}
return true;
}
} // namespace
TEST(UnindexedRulesetTest, EmptyRuleset) {
UnindexedRulesetTestBuilder builder;
EXPECT_TRUE(builder.Finish());
EXPECT_TRUE(IsRulesetValid(builder.ruleset_contents(), builder.url_rules()));
}
TEST(UnindexedRulesetTest, OneUrlRule) {
UnindexedRulesetTestBuilder builder;
EXPECT_TRUE(
builder.AddUrlRule(UrlPattern("example.com"), testing::kThirdParty));
EXPECT_TRUE(builder.Finish());
EXPECT_TRUE(IsRulesetValid(builder.ruleset_contents(), builder.url_rules()));
}
TEST(UnindexedRulesetTest, ManyUrlRules) {
UnindexedRulesetTestBuilder builder;
EXPECT_TRUE(builder.AddUrlRules(1234));
EXPECT_TRUE(builder.Finish());
EXPECT_TRUE(IsRulesetValid(builder.ruleset_contents(), builder.url_rules()));
}
TEST(UnindexedRulesetTest, ExactlyMaxRulesPerChunk) {
UnindexedRulesetTestBuilder builder;
EXPECT_TRUE(builder.AddUrlRules(builder.max_rules_per_chunk()));
EXPECT_TRUE(builder.Finish());
EXPECT_TRUE(IsRulesetValid(builder.ruleset_contents(), builder.url_rules()));
}
TEST(UnindexedRulesetTest, MaxRulesPerChunkPlusOne) {
UnindexedRulesetTestBuilder builder;
EXPECT_TRUE(builder.AddUrlRules(builder.max_rules_per_chunk() + 1));
EXPECT_TRUE(builder.Finish());
EXPECT_TRUE(IsRulesetValid(builder.ruleset_contents(), builder.url_rules()));
}
TEST(UnindexedRulesetTest, ErrorOnWrite) {
UnindexedRulesetTestBuilder builder(1000);
EXPECT_FALSE(builder.AddUrlRules(1234));
}
TEST(UnindexedRulesetTest, ReadCorruptedInput) {
UnindexedRulesetTestBuilder builder;
EXPECT_TRUE(builder.AddUrlRules(1000));
EXPECT_TRUE(builder.Finish());
{
std::string ruleset_contents = builder.ruleset_contents();
ASSERT_GE(ruleset_contents.size(), static_cast<size_t>(2000));
ruleset_contents[100] ^= 239;
ruleset_contents[1000] ^= 3;
EXPECT_FALSE(IsRulesetValid(ruleset_contents, builder.url_rules()));
}
{
std::string ruleset_contents = builder.ruleset_contents();
ASSERT_GT(ruleset_contents.size(), static_cast<size_t>(100));
ruleset_contents.resize(100);
EXPECT_FALSE(IsRulesetValid(ruleset_contents, builder.url_rules()));
}
}
} // namespace subresource_filter