910e62b5创建于 1月15日历史提交
// Copyright 2019 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/composite_matcher.h"

#include <algorithm>
#include <cstdint>
#include <functional>
#include <iterator>
#include <optional>
#include <set>
#include <utility>
#include <vector>

#include "base/containers/contains.h"
#include "base/metrics/histogram_macros.h"
#include "base/time/time.h"
#include "base/timer/elapsed_timer.h"
#include "components/url_pattern_index/flat/url_pattern_index_generated.h"
#include "extensions/browser/api/declarative_net_request/constants.h"
#include "extensions/browser/api/declarative_net_request/flat/extension_ruleset_generated.h"
#include "extensions/browser/api/declarative_net_request/request_action.h"
#include "extensions/browser/api/declarative_net_request/request_params.h"
#include "extensions/browser/api/declarative_net_request/utils.h"
#include "extensions/common/api/declarative_net_request/constants.h"

namespace extensions::declarative_net_request {
namespace flat_rule = url_pattern_index::flat;
using PageAccess = PermissionsData::PageAccess;
using ActionInfo = CompositeMatcher::ActionInfo;

namespace {

bool AreIDsUnique(const CompositeMatcher::MatcherList& matchers) {
  std::set<RulesetID> ids;
  for (const auto& matcher : matchers) {
    bool did_insert = ids.insert(matcher->id()).second;
    if (!did_insert) {
      return false;
    }
  }

  return true;
}

// Helper to log the time taken in CompositeMatcher::GetAction.
class ScopedGetBeforeRequestActionTimer {
 public:
  explicit ScopedGetBeforeRequestActionTimer(RulesetMatchingStage stage)
      : stage_(stage) {}
  ~ScopedGetBeforeRequestActionTimer() {
    switch (stage_) {
      case RulesetMatchingStage::kOnBeforeRequest:
        UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
            "Extensions.DeclarativeNetRequest.EvaluateBeforeRequestTime."
            "SingleExtension2",
            timer_.Elapsed(), base::Microseconds(1), base::Milliseconds(50),
            50);
        break;
      case RulesetMatchingStage::kOnHeadersReceived:
        UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
            "Extensions.DeclarativeNetRequest.EvaluateHeadersReceivedTime."
            "SingleExtension2",
            timer_.Elapsed(), base::Microseconds(1), base::Milliseconds(50),
            50);
        break;
    }
  }

 private:
  RulesetMatchingStage stage_;
  base::ElapsedTimer timer_;
};

}  // namespace

ActionInfo::ActionInfo() = default;
ActionInfo::ActionInfo(std::optional<RequestAction> action,
                       bool notify_request_withheld)
    : action(std::move(action)),
      notify_request_withheld(notify_request_withheld) {}

ActionInfo::~ActionInfo() = default;

ActionInfo::ActionInfo(ActionInfo&&) = default;
ActionInfo& ActionInfo::operator=(ActionInfo&& other) = default;

CompositeMatcher::CompositeMatcher(MatcherList matchers,
                                   const ExtensionId& extension_id,
                                   HostPermissionsAlwaysRequired mode)
    : matchers_(std::move(matchers)),
      extension_id_(extension_id),
      host_permissions_always_required_(mode) {
  DCHECK(AreIDsUnique(matchers_));
}

CompositeMatcher::~CompositeMatcher() = default;

const RulesetMatcher* CompositeMatcher::GetMatcherWithID(RulesetID id) const {
  auto it = std::ranges::find(matchers_, id, &RulesetMatcher::id);
  return it == matchers_.end() ? nullptr : it->get();
}

void CompositeMatcher::AddOrUpdateRuleset(
    std::unique_ptr<RulesetMatcher> matcher) {
  MatcherList matchers;
  matchers.push_back(std::move(matcher));
  AddOrUpdateRulesets(std::move(matchers));
}

void CompositeMatcher::AddOrUpdateRulesets(MatcherList matchers) {
  std::set<RulesetID> ids_to_remove;
  for (const auto& matcher : matchers) {
    ids_to_remove.insert(matcher->id());
  }

  RemoveRulesetsWithIDs(ids_to_remove);
  matchers_.insert(matchers_.end(), std::make_move_iterator(matchers.begin()),
                   std::make_move_iterator(matchers.end()));
  OnMatchersModified();
}

void CompositeMatcher::RemoveRulesetsWithIDs(const std::set<RulesetID>& ids) {
  size_t erased_count = std::erase_if(
      matchers_, [&ids](const std::unique_ptr<RulesetMatcher>& matcher) {
        return base::Contains(ids, matcher->id());
      });

  if (erased_count > 0) {
    OnMatchersModified();
  }
}

std::set<RulesetID> CompositeMatcher::ComputeStaticRulesetIDs() const {
  std::set<RulesetID> result;
  for (const std::unique_ptr<RulesetMatcher>& matcher : matchers_) {
    if (matcher->id() == kDynamicRulesetID) {
      continue;
    }

    result.insert(matcher->id());
  }

  return result;
}

ActionInfo CompositeMatcher::GetAction(
    const RequestParams& params,
    RulesetMatchingStage stage,
    PermissionsData::PageAccess page_access) const {
  ScopedGetBeforeRequestActionTimer timer(stage);

  bool always_require_host_permissions =
      host_permissions_always_required_ == HostPermissionsAlwaysRequired::kTrue;
  if (always_require_host_permissions) {
    // We shouldn't be evaluating this ruleset if host permissions are always
    // required but this extension doesn't have access to the request.
    DCHECK(page_access == PermissionsData::PageAccess::kAllowed ||
           page_access == PermissionsData::PageAccess::kWithheld);
  }

  // Get the max priority allow action for this extension, or implicitly assign
  // it as nullopt in `params.max_priority_allow_action` if there isn't one.
  auto& max_priority_allow_action_for_extension =
      params.max_priority_allow_action[extension_id_];

  // Assign `final_action` to the max priority allow action matched in previous
  // request stages (if any). This way, that action will be returned again if it
  // outprioritizes all rules that are matched in the current request `stage`.
  std::optional<RequestAction> final_action =
      max_priority_allow_action_for_extension.has_value()
          ? std::make_optional(max_priority_allow_action_for_extension->Clone())
          : std::nullopt;

  for (const auto& matcher : matchers_) {
    std::optional<RequestAction> action = matcher->GetAction(params, stage);
    uint64_t max_allow_rule_priority =
        max_priority_allow_action_for_extension.has_value()
            ? max_priority_allow_action_for_extension->index_priority
            : 0u;
    if (!action || action->index_priority <= max_allow_rule_priority) {
      continue;
    }

    if (action->IsAllowOrAllowAllRequests()) {
      // This will update `max_priority_allow_action_for_extension`.
      params.max_priority_allow_action.insert_or_assign(extension_id_,
                                                        action->Clone());
    }

    final_action =
        GetMaxPriorityAction(std::move(final_action), std::move(action));
  }

  if (!final_action) {
    return ActionInfo();
  }

  bool requires_host_permission =
      always_require_host_permissions ||
      final_action->type == RequestAction::Type::REDIRECT;
  if (!requires_host_permission || page_access == PageAccess::kAllowed) {
    return ActionInfo(std::move(final_action),
                      /*notify_request_withheld=*/false);
  }

  // `requires_host_permission` is true and `page_access` is withheld or denied.
  bool notify_request_withheld = page_access == PageAccess::kWithheld &&
                                 !final_action->IsAllowOrAllowAllRequests();
  return ActionInfo(std::nullopt, notify_request_withheld);
}

std::vector<RequestAction> CompositeMatcher::GetModifyHeadersActions(
    const RequestParams& params,
    RulesetMatchingStage stage) const {
  std::vector<RequestAction> modify_headers_actions;

  // The priority of the highest priority matching allow or allowAllRequests
  // rule within this matcher, or 0 if no such rule exists (the minimum priority
  // for a rule, specified in `kMinValidPriority`, is 1.)
  uint64_t max_allow_rule_priority = 0u;

  DCHECK(base::Contains(params.max_priority_allow_action, extension_id_));
  if (auto& allow_action = params.max_priority_allow_action.at(extension_id_)) {
    max_allow_rule_priority = allow_action->index_priority;
  }

  for (const auto& matcher : matchers_) {
    // Plumb `max_allow_rule_priority` into GetModifyHeadersActions so that
    // modifyHeaders rules with priorities less than or equal to the highest
    // priority matching allow/allowAllRequests rule are ignored.
    std::vector<RequestAction> actions_for_matcher =
        matcher->GetModifyHeadersActions(params, stage,
                                         max_allow_rule_priority);

    modify_headers_actions.insert(
        modify_headers_actions.end(),
        std::make_move_iterator(actions_for_matcher.begin()),
        std::make_move_iterator(actions_for_matcher.end()));
  }

  // Sort `modify_headers_actions` in descending order of priority.
  std::sort(modify_headers_actions.begin(), modify_headers_actions.end(),
            std::greater<>());
  return modify_headers_actions;
}

bool CompositeMatcher::HasAnyExtraHeadersMatcher() const {
  if (!has_any_extra_headers_matcher_.has_value()) {
    has_any_extra_headers_matcher_ = ComputeHasAnyExtraHeadersMatcher();
  }
  return has_any_extra_headers_matcher_.value();
}

void CompositeMatcher::OnRenderFrameCreated(content::RenderFrameHost* host) {
  for (auto& matcher : matchers_) {
    matcher->OnRenderFrameCreated(host);
  }
}

void CompositeMatcher::OnRenderFrameDeleted(content::RenderFrameHost* host) {
  for (auto& matcher : matchers_) {
    matcher->OnRenderFrameDeleted(host);
  }
}

void CompositeMatcher::OnDidFinishNavigation(
    content::NavigationHandle* navigation_handle) {
  for (auto& matcher : matchers_) {
    matcher->OnDidFinishNavigation(navigation_handle);
  }
}

bool CompositeMatcher::HasRulesets(RulesetMatchingStage stage) const {
  return std::ranges::any_of(
      matchers_, [stage](const std::unique_ptr<RulesetMatcher>& matcher) {
        return matcher->GetRulesCount(stage) > 0;
      });
}

void CompositeMatcher::OnMatchersModified() {
  DCHECK(AreIDsUnique(matchers_));

  // Clear the renderers' cache so that they take the updated rules into
  // account.
  ClearRendererCacheOnNavigation();

  has_any_extra_headers_matcher_.reset();
}

bool CompositeMatcher::ComputeHasAnyExtraHeadersMatcher() const {
  for (const auto& matcher : matchers_) {
    if (matcher->IsExtraHeadersMatcher()) {
      return true;
    }
  }
  return false;
}

}  // namespace extensions::declarative_net_request