| // 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 |