910e62b5创建于 1月15日历史提交
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <string_view>

#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/json/json_file_value_serializer.h"
#include "base/strings/pattern.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.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/dnr_manifest_data.h"
#include "extensions/common/api/declarative_net_request/test_utils.h"
#include "extensions/common/constants.h"
#include "extensions/common/error_utils.h"
#include "extensions/common/extension.h"
#include "extensions/common/file_util.h"
#include "extensions/common/manifest_constants.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace extensions {

namespace errors = manifest_errors;
namespace dnr_api = api::declarative_net_request;

namespace declarative_net_request {
namespace {

std::string GetRuleResourcesPath() {
  return base::JoinString({dnr_api::ManifestKeys::kDeclarativeNetRequest,
                           dnr_api::DNRInfo::kRuleResources},
                          ".");
}

// Fixture testing the kDeclarativeNetRequestKey manifest key.
class DNRManifestTest : public testing::Test {
 public:
  DNRManifestTest() = default;

  DNRManifestTest(const DNRManifestTest&) = delete;
  DNRManifestTest& operator=(const DNRManifestTest&) = delete;

 protected:
  // Loads the extension and verifies the |expected_error|.
  void LoadAndExpectError(const std::u16string& expected_error) {
    std::u16string error;
    scoped_refptr<Extension> extension = file_util::LoadExtension(
        temp_dir_.GetPath(), mojom::ManifestLocation::kUnpacked,
        Extension::NO_FLAGS, &error);
    EXPECT_FALSE(extension);
    EXPECT_EQ(expected_error, error);
  }

  // Loads the extension and verifies that the manifest info is correctly set
  // up without any warnings or errors.
  void LoadAndExpectSuccess(const std::vector<TestRulesetInfo>& info) {
    LoadAndExpectWarning(info, std::nullopt);
  }

  // Loads the extension and verifies that the manifest info is correctly set
  // up, has no errors, but has provided warning.
  void LoadAndExpectWarning(
      const std::vector<TestRulesetInfo>& info,
      const std::optional<InstallWarning>& expected_warning) {
    std::u16string error;
    scoped_refptr<Extension> extension = file_util::LoadExtension(
        temp_dir_.GetPath(), mojom::ManifestLocation::kUnpacked,
        Extension::NO_FLAGS, &error);
    ASSERT_TRUE(extension) << error;
    EXPECT_TRUE(error.empty());
    if (expected_warning) {
      ASSERT_EQ(1u, extension.get()->install_warnings().size());
      EXPECT_EQ(expected_warning.value(),
                extension.get()->install_warnings()[0]);
    } else {
      EXPECT_TRUE(extension.get()->install_warnings().empty());
    }

    const std::vector<DNRManifestData::RulesetInfo>& rulesets =
        DNRManifestData::GetRulesets(*extension);
    ASSERT_EQ(info.size(), rulesets.size());
    for (size_t i = 0; i < rulesets.size(); ++i) {
      EXPECT_EQ(RulesetID(i + kMinValidStaticRulesetID.value()),
                rulesets[i].id);

      EXPECT_EQ(info[i].manifest_id, rulesets[i].manifest_id);

      // Compare normalized FilePaths instead of their string representations
      // since different platforms represent path separators differently.
      EXPECT_EQ(base::FilePath()
                    .AppendASCII(info[i].relative_file_path)
                    .NormalizePathSeparators(),
                rulesets[i].relative_path);

      EXPECT_EQ(info[i].enabled, rulesets[i].enabled);

      EXPECT_EQ(&rulesets[i],
                &DNRManifestData::GetRuleset(*extension, rulesets[i].id));
    }
  }

  void WriteManifestAndRuleset(const base::Value::Dict& manifest,
                               const std::vector<TestRulesetInfo>& info) {
    EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());

    for (const auto& ruleset : info) {
      base::FilePath rules_path =
          temp_dir_.GetPath().AppendASCII(ruleset.relative_file_path);

      // Create parent directory of |rules_path| if it doesn't exist.
      EXPECT_TRUE(base::CreateDirectory(rules_path.DirName()));

      // Persist an empty ruleset file.
      EXPECT_TRUE(base::WriteFile(rules_path, std::string_view()));
    }

    // Persist manifest file.
    JSONFileValueSerializer(temp_dir_.GetPath().Append(kManifestFilename))
        .Serialize(manifest);
  }

  TestRulesetInfo CreateDefaultRuleset() {
    return TestRulesetInfo("id", "rules_file.json", base::Value::List());
  }

 private:
  base::ScopedTempDir temp_dir_;
};

TEST_F(DNRManifestTest, EmptyRuleset) {
  std::vector<TestRulesetInfo> rulesets({CreateDefaultRuleset()});
  WriteManifestAndRuleset(CreateManifest(rulesets), rulesets);
  LoadAndExpectSuccess(rulesets);
}

TEST_F(DNRManifestTest, InvalidManifestKey) {
  std::vector<TestRulesetInfo> rulesets({CreateDefaultRuleset()});
  base::Value::Dict manifest = CreateManifest(rulesets);
  manifest.Set(dnr_api::ManifestKeys::kDeclarativeNetRequest, 3);

  WriteManifestAndRuleset(manifest, rulesets);
  LoadAndExpectError(
      u"Error at key 'declarative_net_request'. Type is invalid. Expected "
      u"dictionary, found integer.");
}

TEST_F(DNRManifestTest, InvalidRulesFileKey) {
  std::vector<TestRulesetInfo> rulesets({CreateDefaultRuleset()});
  base::Value::Dict manifest = CreateManifest(rulesets);
  manifest.SetByDottedPath(GetRuleResourcesPath(), 3);

  WriteManifestAndRuleset(manifest, rulesets);
  LoadAndExpectError(
      u"Error at key 'declarative_net_request.rule_resources'. Type is "
      u"invalid. "
      u"Expected list, found integer.");
}

TEST_F(DNRManifestTest, InvalidRulesFileFormat) {
  const char kRulesetFile[] = "file1.json";
  base::Value::Dict manifest = CreateManifest({}).Set(
      dnr_api::ManifestKeys::kDeclarativeNetRequest,
      base::Value::Dict().Set(dnr_api::DNRInfo::kRuleResources,
                              base::Value::List().Append(kRulesetFile)));

  WriteManifestAndRuleset(manifest, {});

  LoadAndExpectError(
      u"Error at key 'declarative_net_request.rule_resources'. Parsing array "
      u"failed at index 0: expected dictionary, got string");
}

TEST_F(DNRManifestTest, InvalidRulesetPath) {
  TestRulesetInfo ruleset("rules", "sub/../rules.json", base::Value::List());
  WriteManifestAndRuleset(CreateManifest({ruleset}), {});
  // TODO(crbug.com/41317803): Continue removing std::string errors and
  // replacing with std::u16string.
  LoadAndExpectError(base::UTF8ToUTF16(ErrorUtils::FormatErrorMessage(
      errors::kRulesFileIsInvalid,
      dnr_api::ManifestKeys::kDeclarativeNetRequest,
      dnr_api::DNRInfo::kRuleResources, ruleset.relative_file_path)));
}

TEST_F(DNRManifestTest, InvalidRulesetPath2) {
  TestRulesetInfo ruleset("rules", "rules.json?param=1", base::Value::List());
  WriteManifestAndRuleset(CreateManifest({ruleset}), {});
  LoadAndExpectError(base::UTF8ToUTF16(ErrorUtils::FormatErrorMessage(
      errors::kRulesFileIsInvalid,
      dnr_api::ManifestKeys::kDeclarativeNetRequest,
      dnr_api::DNRInfo::kRuleResources, ruleset.relative_file_path)));
}

TEST_F(DNRManifestTest, ZeroRulesets) {
  std::vector<TestRulesetInfo> no_rulesets;
  WriteManifestAndRuleset(CreateManifest(no_rulesets), no_rulesets);
  LoadAndExpectSuccess(no_rulesets);
}

TEST_F(DNRManifestTest, MultipleRulesFileSuccess) {
  TestRulesetInfo ruleset_1("1", "file1.json", base::Value::List(),
                            true /* enabled */);
  TestRulesetInfo ruleset_2("2", "file2.json", base::Value::List(),
                            false /* enabled */);
  TestRulesetInfo ruleset_3("3", "file3.json", base::Value::List(),
                            true /* enabled */);

  std::vector<TestRulesetInfo> rulesets = {ruleset_1, ruleset_2, ruleset_3};
  WriteManifestAndRuleset(CreateManifest(rulesets), rulesets);
  LoadAndExpectSuccess(rulesets);
}

TEST_F(DNRManifestTest, MultipleRulesFileInvalidPath) {
  TestRulesetInfo ruleset_1("1", "file1.json", base::Value::List());
  TestRulesetInfo ruleset_2("2", "file2.json", base::Value::List());

  // Only persist |ruleset_1| on disk but include both in the manifest.
  WriteManifestAndRuleset(CreateManifest({ruleset_1, ruleset_2}), {ruleset_1});

  LoadAndExpectError(base::UTF8ToUTF16(ErrorUtils::FormatErrorMessage(
      errors::kRulesFileIsInvalid,
      dnr_api::ManifestKeys::kDeclarativeNetRequest,
      dnr_api::DNRInfo::kRuleResources, ruleset_2.relative_file_path)));
}

TEST_F(DNRManifestTest, RulesetCountExceeded) {
  std::vector<TestRulesetInfo> rulesets;
  for (int i = 0; i <= dnr_api::MAX_NUMBER_OF_STATIC_RULESETS; ++i) {
    rulesets.emplace_back(base::NumberToString(i), base::Value::List(),
                          false /* enabled */);
  }

  WriteManifestAndRuleset(CreateManifest(rulesets), rulesets);

  LoadAndExpectError(base::UTF8ToUTF16(ErrorUtils::FormatErrorMessage(
      errors::kRulesetCountExceeded,
      dnr_api::ManifestKeys::kDeclarativeNetRequest,
      dnr_api::DNRInfo::kRuleResources,
      base::NumberToString(dnr_api::MAX_NUMBER_OF_STATIC_RULESETS))));
}

TEST_F(DNRManifestTest, EnabledRulesetCountExceeded) {
  std::vector<TestRulesetInfo> rulesets;
  for (int i = 0; i <= dnr_api::MAX_NUMBER_OF_ENABLED_STATIC_RULESETS; ++i) {
    rulesets.emplace_back(base::NumberToString(i), base::Value::List(),
                          true /* enabled */);
  }

  WriteManifestAndRuleset(CreateManifest(rulesets), rulesets);

  LoadAndExpectError(base::UTF8ToUTF16(ErrorUtils::FormatErrorMessage(
      errors::kEnabledRulesetCountExceeded,
      dnr_api::ManifestKeys::kDeclarativeNetRequest,
      dnr_api::DNRInfo::kRuleResources,
      base::NumberToString(dnr_api::MAX_NUMBER_OF_ENABLED_STATIC_RULESETS))));
}

TEST_F(DNRManifestTest, NonExistentRulesFile) {
  TestRulesetInfo ruleset("id", "invalid_file.json", base::Value::List());

  base::Value::Dict manifest = CreateManifest({ruleset});

  WriteManifestAndRuleset(manifest, {});

  LoadAndExpectError(base::UTF8ToUTF16(ErrorUtils::FormatErrorMessage(
      errors::kRulesFileIsInvalid,
      dnr_api::ManifestKeys::kDeclarativeNetRequest,
      dnr_api::DNRInfo::kRuleResources, ruleset.relative_file_path)));
}

TEST_F(DNRManifestTest, NeedsDeclarativeNetRequestPermission) {
  std::vector<TestRulesetInfo> rulesets({CreateDefaultRuleset()});
  base::Value::Dict manifest = CreateManifest(rulesets);
  // Remove "declarativeNetRequest" permission.
  manifest.Remove(manifest_keys::kPermissions);

  WriteManifestAndRuleset(manifest, rulesets);

  LoadAndExpectError(base::UTF8ToUTF16(ErrorUtils::FormatErrorMessage(
      errors::kDeclarativeNetRequestPermissionNeeded,
      dnr_api::ManifestKeys::kDeclarativeNetRequest)));
}

TEST_F(DNRManifestTest, RulesFileInNestedDirectory) {
  TestRulesetInfo ruleset("id", "dir/rules_file.json", base::Value::List());

  std::vector<TestRulesetInfo> rulesets({ruleset});
  base::Value::Dict manifest = CreateManifest(rulesets);

  WriteManifestAndRuleset(manifest, rulesets);
  LoadAndExpectSuccess(rulesets);
}

TEST_F(DNRManifestTest, EmptyRulesetID) {
  TestRulesetInfo ruleset_1("1", "1.json", base::Value::List());
  TestRulesetInfo ruleset_2("", "2.json", base::Value::List());
  TestRulesetInfo ruleset_3("3", "3.json", base::Value::List());
  std::vector<TestRulesetInfo> rulesets({ruleset_1, ruleset_2, ruleset_3});
  WriteManifestAndRuleset(CreateManifest(rulesets), rulesets);

  LoadAndExpectError(base::UTF8ToUTF16(ErrorUtils::FormatErrorMessage(
      errors::kInvalidRulesetID, dnr_api::ManifestKeys::kDeclarativeNetRequest,
      dnr_api::DNRInfo::kRuleResources, "1")));
}

TEST_F(DNRManifestTest, DuplicateRulesetID) {
  TestRulesetInfo ruleset_1("1", "1.json", base::Value::List());
  TestRulesetInfo ruleset_2("2", "2.json", base::Value::List());
  TestRulesetInfo ruleset_3("3", "3.json", base::Value::List());
  TestRulesetInfo ruleset_4("1", "3.json", base::Value::List());
  std::vector<TestRulesetInfo> rulesets(
      {ruleset_1, ruleset_2, ruleset_3, ruleset_4});
  WriteManifestAndRuleset(CreateManifest(rulesets), rulesets);

  LoadAndExpectError(base::UTF8ToUTF16(ErrorUtils::FormatErrorMessage(
      errors::kInvalidRulesetID, dnr_api::ManifestKeys::kDeclarativeNetRequest,
      dnr_api::DNRInfo::kRuleResources, "3")));
}

TEST_F(DNRManifestTest, ReservedRulesetID) {
  TestRulesetInfo ruleset_1("foo", "1.json", base::Value::List());
  TestRulesetInfo ruleset_2("_bar", "2.json", base::Value::List());
  TestRulesetInfo ruleset_3("baz", "3.json", base::Value::List());
  std::vector<TestRulesetInfo> rulesets({ruleset_1, ruleset_2, ruleset_3});
  WriteManifestAndRuleset(CreateManifest(rulesets), rulesets);

  LoadAndExpectError(base::UTF8ToUTF16(ErrorUtils::FormatErrorMessage(
      errors::kInvalidRulesetID, dnr_api::ManifestKeys::kDeclarativeNetRequest,
      dnr_api::DNRInfo::kRuleResources, "1")));
}

// The webstore installation flow involves creation of a dummy extension with an
// empty extension root path. Ensure the manifest handler for declarative net
// request handles it correctly. Regression test for crbug.com/1087348.
TEST_F(DNRManifestTest, EmptyExtensionRootPath) {
  TestRulesetInfo ruleset("foo", "1.json", base::Value::List());

  std::u16string error;
  scoped_refptr<Extension> extension = Extension::Create(
      base::FilePath(), mojom::ManifestLocation::kInternal,
      CreateManifest({ruleset}), Extension::FROM_WEBSTORE, &error);

  EXPECT_TRUE(extension);
  EXPECT_TRUE(error.empty()) << error;
}

TEST_F(DNRManifestTest, EmptyRulesetPath1) {
  TestRulesetInfo ruleset("foo", "", base::Value::List());
  WriteManifestAndRuleset(CreateManifest({ruleset}), {});
  LoadAndExpectError(base::UTF8ToUTF16(ErrorUtils::FormatErrorMessage(
      errors::kRulesFileIsInvalid,
      dnr_api::ManifestKeys::kDeclarativeNetRequest,
      dnr_api::DNRInfo::kRuleResources, ruleset.relative_file_path)));
}

TEST_F(DNRManifestTest, EmptyRulesetPath2) {
  TestRulesetInfo ruleset("foo", ".", base::Value::List());
  WriteManifestAndRuleset(CreateManifest({ruleset}), {});
  LoadAndExpectError(base::UTF8ToUTF16(ErrorUtils::FormatErrorMessage(
      errors::kRulesFileIsInvalid,
      dnr_api::ManifestKeys::kDeclarativeNetRequest,
      dnr_api::DNRInfo::kRuleResources, ruleset.relative_file_path)));
}

TEST_F(DNRManifestTest, DuplicateRulesetPath) {
  TestRulesetInfo ruleset_1("foo", "rules.json", base::Value::List());
  TestRulesetInfo ruleset_2("bar", "rules.json", base::Value::List());
  std::vector<TestRulesetInfo> rulesets({ruleset_1, ruleset_2});
  WriteManifestAndRuleset(CreateManifest(rulesets), rulesets);
  LoadAndExpectWarning(
      rulesets, InstallWarning(errors::kDeclarativeNetRequestPathDuplicates));
}

}  // namespace
}  // namespace declarative_net_request
}  // namespace extensions