910e62b5创建于 1月15日历史提交
// Copyright 2022 The Chromium Authors
// 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/prefs_helper.h"

#include <string>
#include <string_view>

#include "base/containers/contains.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "extensions/browser/api/declarative_net_request/utils.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/common/constants.h"
#include "services/preferences/public/cpp/scoped_pref_update.h"

namespace extensions::declarative_net_request {

namespace {

// Additional preferences keys, which are not needed by external clients.

// Key corresponding to which we store a ruleset's checksum for the Declarative
// Net Request API.
constexpr std::string_view kChecksumKey = "checksum";

// Key corresponding to which we store a ruleset's disabled rule ids for the
// Declarative Net Request API.
constexpr std::string_view kDisabledStaticRuleIds =
    "dnr_disabled_static_rule_ids";

// Key corresponding to the list of enabled static ruleset IDs for an extension.
// Used for the Declarative Net Request API.
constexpr std::string_view kEnabledStaticRulesetIDs = "dnr_enabled_ruleset_ids";

// A preference that indicates the amount of rules allocated to an extension
// from the global pool.
constexpr std::string_view kExtensionRulesAllocated =
    "dnr_extension_rules_allocated";

// A boolean that indicates if a ruleset should be ignored.
constexpr std::string_view kIgnoreRulesetKey = "ignore_ruleset";

// A boolean that indicates if an extension should have its unused rule
// allocation kept during its next load.
constexpr std::string_view kKeepExcessAllocation = "dnr_keep_excess_allocation";

// A boolean preference that indicates whether the extension's icon should be
// automatically badged to the matched action count for a tab. False by default.
constexpr std::string_view kUseActionCountAsBadgeText =
    "dnr_use_action_count_as_badge_text";

// Stores preferences corresponding to dynamic indexed ruleset for the
// Declarative Net Request API. Note: we use a separate preference key for
// dynamic rulesets instead of using the `kDNRStaticRulesetPref` dictionary.
// This is because the `kDNRStaticRulesetPref` dictionary is re-populated on
// each packed extension update and also on reloads of unpacked extensions.
// However for both of these cases, we want the dynamic ruleset preferences to
// stay unchanged. Also, this helps provide flexibility to have the dynamic
// ruleset preference schema diverge from the static one.
constexpr std::string_view kDynamicRulesetPref = "dnr_dynamic_ruleset";

base::flat_set<int> GetDisabledStaticRuleIdsFromDict(
    const base::Value::Dict* disabled_rule_ids_dict,
    RulesetID ruleset_id) {
  if (!disabled_rule_ids_dict) {
    return {};
  }

  const base::Value::List* disabled_rule_id_list =
      disabled_rule_ids_dict->FindList(
          base::NumberToString(ruleset_id.value()));
  if (!disabled_rule_id_list) {
    // Just ignore the prefs value if it is corrupted.
    // TODO(blee@igalia.com) The corrupted prefs value should be recovered by
    // wiping the value so that the extension returns to the sane state.
    return {};
  }

  base::flat_set<int> disabled_rule_ids;
  for (const base::Value& disabled_rule_id : *disabled_rule_id_list) {
    if (!disabled_rule_id.is_int()) {
      // Just ignore the prefs value if it is corrupted.
      // TODO(blee@igalia.com) The corrupted prefs value should be recovered by
      // wiping the value so that the extension returns to the sane state.
      return {};
    }

    disabled_rule_ids.insert(disabled_rule_id.GetInt());
  }

  return disabled_rule_ids;
}

size_t CountDisabledRules(const base::Value::Dict* disabled_rule_ids_dict) {
  if (!disabled_rule_ids_dict) {
    return 0;
  }

  size_t count = 0;
  for (const auto [key, value] : *disabled_rule_ids_dict) {
    if (!value.is_list()) {
      continue;
    }
    count += value.GetList().size();
  }
  return count;
}

bool ReadPrefAsBooleanAndReturn(const ExtensionPrefs& prefs,
                                const ExtensionId& extension_id,
                                std::string_view key) {
  bool value = false;
  if (prefs.ReadPrefAsBoolean(extension_id, key, &value)) {
    return value;
  }

  return false;
}

}  // namespace

PrefsHelper::PrefsHelper(ExtensionPrefs& prefs)
    : prefs_(prefs) {}
PrefsHelper::~PrefsHelper() = default;

PrefsHelper::RuleIdsToUpdate::RuleIdsToUpdate(
    const std::optional<std::vector<int>>& ids_to_disable,
    const std::optional<std::vector<int>>& ids_to_enable) {
  if (ids_to_disable) {
    this->ids_to_disable.insert(ids_to_disable->begin(), ids_to_disable->end());
  }

  if (ids_to_enable) {
    for (int id : *ids_to_enable) {
      // |ids_to_disable| takes priority over |ids_to_enable|.
      if (base::Contains(this->ids_to_disable, id)) {
        continue;
      }
      this->ids_to_enable.insert(id);
    }
  }
}

PrefsHelper::RuleIdsToUpdate::RuleIdsToUpdate(
    RuleIdsToUpdate&& other) = default;
PrefsHelper::RuleIdsToUpdate::~RuleIdsToUpdate() = default;

PrefsHelper::UpdateDisabledStaticRulesResult::
    UpdateDisabledStaticRulesResult() = default;
PrefsHelper::UpdateDisabledStaticRulesResult::
    UpdateDisabledStaticRulesResult(UpdateDisabledStaticRulesResult&& other) =
        default;
PrefsHelper::UpdateDisabledStaticRulesResult::
    ~UpdateDisabledStaticRulesResult() = default;

const base::Value::Dict*
PrefsHelper::GetDisabledRuleIdsDict(
    const ExtensionId& extension_id) const {
  return prefs_->ReadPrefAsDict(
      extension_id,
      ExtensionPrefs::JoinPrefs(
          {ExtensionPrefs::kDNRStaticRulesetPref, kDisabledStaticRuleIds}));
}

base::flat_set<int> PrefsHelper::GetDisabledStaticRuleIds(
    const ExtensionId& extension_id,
    RulesetID ruleset_id) const {
  return GetDisabledStaticRuleIdsFromDict(GetDisabledRuleIdsDict(extension_id),
                                          ruleset_id);
}

size_t PrefsHelper::GetDisabledStaticRuleCount(
    const ExtensionId& extension_id) const {
  return CountDisabledRules(GetDisabledRuleIdsDict(extension_id));
}

void PrefsHelper::SetDisabledStaticRuleIds(
    const ExtensionId& extension_id,
    RulesetID ruleset_id,
    const base::flat_set<int>& disabled_rule_ids) {
  std::string key = ExtensionPrefs::JoinPrefs(
      {ExtensionPrefs::kDNRStaticRulesetPref, kDisabledStaticRuleIds});

  ExtensionPrefs::ScopedDictionaryUpdate update(&*prefs_, extension_id, key);

  if (disabled_rule_ids.empty()) {
    std::unique_ptr<prefs::DictionaryValueUpdate> disabled_rule_ids_dict =
        update.Get();
    if (disabled_rule_ids_dict) {
      disabled_rule_ids_dict->Remove(base::NumberToString(ruleset_id.value()));
    }
    return;
  }

  std::unique_ptr<prefs::DictionaryValueUpdate> disabled_rule_ids_dict =
      update.Create();

  base::Value::List ids_list;
  ids_list.reserve(disabled_rule_ids.size());
  for (int id : disabled_rule_ids) {
    ids_list.Append(id);
  }

  disabled_rule_ids_dict->Set(base::NumberToString(ruleset_id.value()),
                              base::Value(std::move(ids_list)));
}

PrefsHelper::UpdateDisabledStaticRulesResult
PrefsHelper::UpdateDisabledStaticRules(
    const ExtensionId& extension_id,
    RulesetID ruleset_id,
    const RuleIdsToUpdate& rule_ids_to_update) {
  UpdateDisabledStaticRulesResult result;

  const base::Value::Dict* disabled_rule_ids_dict =
      GetDisabledRuleIdsDict(extension_id);

  base::flat_set<int> old_disabled_rule_ids(
      GetDisabledStaticRuleIdsFromDict(disabled_rule_ids_dict, ruleset_id));

  for (int id : old_disabled_rule_ids) {
    if (base::Contains(rule_ids_to_update.ids_to_enable, id)) {
      result.changed = true;
      continue;
    }
    result.disabled_rule_ids_after_update.insert(id);
  }
  for (int id : rule_ids_to_update.ids_to_disable) {
    auto pair = result.disabled_rule_ids_after_update.insert(id);
    if (pair.second) {
      result.changed = true;
    }
  }

  if (!result.changed) {
    result.disabled_rule_ids_after_update.clear();
    return result;
  }

  int count_before = old_disabled_rule_ids.size();
  int count_after = result.disabled_rule_ids_after_update.size();
  int new_count =
      CountDisabledRules(disabled_rule_ids_dict) + count_after - count_before;
  DCHECK_GE(new_count, 0);

  if (new_count > GetDisabledStaticRuleLimit()) {
    result.error = kDisabledStaticRuleCountExceeded;
    result.changed = false;
    result.disabled_rule_ids_after_update.clear();
    return result;
  }

  SetDisabledStaticRuleIds(extension_id, ruleset_id,
                           result.disabled_rule_ids_after_update);
  return result;
}

bool PrefsHelper::GetStaticRulesetChecksum(
    const ExtensionId& extension_id,
    declarative_net_request::RulesetID ruleset_id,
    int& checksum) const {
  std::string pref = ExtensionPrefs::JoinPrefs(
      {ExtensionPrefs::kDNRStaticRulesetPref,
       base::NumberToString(ruleset_id.value()), kChecksumKey});
  return prefs_->ReadPrefAsInteger(extension_id, pref, &checksum);
}

void PrefsHelper::SetStaticRulesetChecksum(
    const ExtensionId& extension_id,
    declarative_net_request::RulesetID ruleset_id,
    int checksum) {
  std::string pref = ExtensionPrefs::JoinPrefs(
      {ExtensionPrefs::kDNRStaticRulesetPref,
       base::NumberToString(ruleset_id.value()), kChecksumKey});
  prefs_->UpdateExtensionPref(extension_id, pref, base::Value(checksum));
}

bool PrefsHelper::GetDynamicRulesetChecksum(const ExtensionId& extension_id,
                                            int& checksum) const {
  std::string pref =
      ExtensionPrefs::JoinPrefs({kDynamicRulesetPref, kChecksumKey});
  return prefs_->ReadPrefAsInteger(extension_id, pref, &checksum);
}

void PrefsHelper::SetDynamicRulesetChecksum(const ExtensionId& extension_id,
                                            int checksum) {
  std::string pref =
      ExtensionPrefs::JoinPrefs({kDynamicRulesetPref, kChecksumKey});
  prefs_->UpdateExtensionPref(extension_id, pref, base::Value(checksum));
}

std::optional<std::set<RulesetID>> PrefsHelper::GetEnabledStaticRulesets(
    const ExtensionId& extension_id) const {
  std::set<RulesetID> ids;
  const base::Value::List* ids_value =
      prefs_->ReadPrefAsList(extension_id, kEnabledStaticRulesetIDs);
  if (!ids_value) {
    return std::nullopt;
  }

  for (const base::Value& id_value : *ids_value) {
    if (!id_value.is_int()) {
      return std::nullopt;
    }

    ids.insert(RulesetID(id_value.GetInt()));
  }

  return ids;
}

void PrefsHelper::SetEnabledStaticRulesets(const ExtensionId& extension_id,
                                           const std::set<RulesetID>& ids) {
  base::Value::List ids_list;
  for (const auto& id : ids) {
    ids_list.Append(id.value());
  }

  prefs_->UpdateExtensionPref(extension_id, kEnabledStaticRulesetIDs,
                              base::Value(std::move(ids_list)));
}

bool PrefsHelper::GetUseActionCountAsBadgeText(
    const ExtensionId& extension_id) const {
  return ReadPrefAsBooleanAndReturn(*prefs_, extension_id,
                                    kUseActionCountAsBadgeText);
}

void PrefsHelper::SetUseActionCountAsBadgeText(
    const ExtensionId& extension_id,
    bool use_action_count_as_badge_text) {
  prefs_->UpdateExtensionPref(extension_id, kUseActionCountAsBadgeText,
                              base::Value(use_action_count_as_badge_text));
}

// Whether the ruleset for the given `extension_id` and `ruleset_id` should be
// ignored while loading the extension.
bool PrefsHelper::ShouldIgnoreRuleset(const ExtensionId& extension_id,
                                      RulesetID ruleset_id) const {
  std::string pref = ExtensionPrefs::JoinPrefs(
      {ExtensionPrefs::kDNRStaticRulesetPref,
       base::NumberToString(ruleset_id.value()), kIgnoreRulesetKey});
  return ReadPrefAsBooleanAndReturn(*prefs_, extension_id, pref);
}

// Returns the global rule allocation for the given |extension_id|. If no
// rules are allocated to the extension, false is returned.
bool PrefsHelper::GetAllocatedGlobalRuleCount(const ExtensionId& extension_id,
                                              int& rule_count) const {
  if (!prefs_->ReadPrefAsInteger(extension_id, kExtensionRulesAllocated,
                                 &rule_count)) {
    return false;
  }

  DCHECK_GT(rule_count, 0);

  return true;
}

void PrefsHelper::SetAllocatedGlobalRuleCount(const ExtensionId& extension_id,
                                              int rule_count) {
  DCHECK_LE(rule_count, GetGlobalStaticRuleLimit());

  // Clear the pref entry if the extension has a global allocation of 0.
  std::optional<base::Value> pref_value;
  if (rule_count > 0) {
    pref_value = base::Value(rule_count);
  }
  prefs_->UpdateExtensionPref(extension_id, kExtensionRulesAllocated,
                              std::move(pref_value));
}

bool PrefsHelper::GetKeepExcessAllocation(
    const ExtensionId& extension_id) const {
  return ReadPrefAsBooleanAndReturn(*prefs_, extension_id,
                                    kKeepExcessAllocation);
}

void PrefsHelper::SetKeepExcessAllocation(const ExtensionId& extension_id,
                                          bool keep_excess_allocation) {
  // Clear the pref entry if the extension will not keep its excess global rules
  // allocation.
  std::optional<base::Value> pref_value;
  if (keep_excess_allocation) {
    pref_value = base::Value(true);
  }
  prefs_->UpdateExtensionPref(extension_id, kKeepExcessAllocation,
                              std::move(pref_value));
}

}  // namespace extensions::declarative_net_request