1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "extensions/browser/api/declarative_net_request/composite_matcher.h"
6
7 #include <algorithm>
8 #include <functional>
9 #include <iterator>
10 #include <set>
11 #include <utility>
12
13 #include "base/metrics/histogram_macros.h"
14 #include "base/stl_util.h"
15 #include "base/time/time.h"
16 #include "base/timer/elapsed_timer.h"
17 #include "extensions/browser/api/declarative_net_request/flat/extension_ruleset_generated.h"
18 #include "extensions/browser/api/declarative_net_request/request_action.h"
19 #include "extensions/browser/api/declarative_net_request/request_params.h"
20 #include "extensions/browser/api/declarative_net_request/utils.h"
21 #include "extensions/common/api/declarative_net_request/constants.h"
22
23 namespace extensions {
24 namespace declarative_net_request {
25 namespace flat_rule = url_pattern_index::flat;
26 using PageAccess = PermissionsData::PageAccess;
27 using ActionInfo = CompositeMatcher::ActionInfo;
28
29 namespace {
30
AreIDsUnique(const CompositeMatcher::MatcherList & matchers)31 bool AreIDsUnique(const CompositeMatcher::MatcherList& matchers) {
32 std::set<RulesetID> ids;
33 for (const auto& matcher : matchers) {
34 bool did_insert = ids.insert(matcher->id()).second;
35 if (!did_insert)
36 return false;
37 }
38
39 return true;
40 }
41
42 // Helper to log the time taken in CompositeMatcher::GetBeforeRequestAction.
43 class ScopedGetBeforeRequestActionTimer {
44 public:
45 ScopedGetBeforeRequestActionTimer() = default;
~ScopedGetBeforeRequestActionTimer()46 ~ScopedGetBeforeRequestActionTimer() {
47 UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
48 "Extensions.DeclarativeNetRequest.EvaluateBeforeRequestTime."
49 "SingleExtension2",
50 timer_.Elapsed(), base::TimeDelta::FromMicroseconds(1),
51 base::TimeDelta::FromMilliseconds(50), 50);
52 }
53
54 private:
55 base::ElapsedTimer timer_;
56 };
57
58 } // namespace
59
ActionInfo(base::Optional<RequestAction> action,bool notify_request_withheld)60 ActionInfo::ActionInfo(base::Optional<RequestAction> action,
61 bool notify_request_withheld)
62 : action(std::move(action)),
63 notify_request_withheld(notify_request_withheld) {}
64
65 ActionInfo::~ActionInfo() = default;
66
67 ActionInfo::ActionInfo(ActionInfo&&) = default;
68 ActionInfo& ActionInfo::operator=(ActionInfo&& other) = default;
69
CompositeMatcher(MatcherList matchers)70 CompositeMatcher::CompositeMatcher(MatcherList matchers)
71 : matchers_(std::move(matchers)) {
72 DCHECK(AreIDsUnique(matchers_));
73 }
74
75 CompositeMatcher::~CompositeMatcher() = default;
76
AddOrUpdateRuleset(std::unique_ptr<RulesetMatcher> matcher)77 void CompositeMatcher::AddOrUpdateRuleset(
78 std::unique_ptr<RulesetMatcher> matcher) {
79 MatcherList matchers;
80 matchers.push_back(std::move(matcher));
81 AddOrUpdateRulesets(std::move(matchers));
82 }
83
AddOrUpdateRulesets(MatcherList matchers)84 void CompositeMatcher::AddOrUpdateRulesets(MatcherList matchers) {
85 std::set<RulesetID> ids_to_remove;
86 for (const auto& matcher : matchers)
87 ids_to_remove.insert(matcher->id());
88
89 RemoveRulesetsWithIDs(ids_to_remove);
90 matchers_.insert(matchers_.end(), std::make_move_iterator(matchers.begin()),
91 std::make_move_iterator(matchers.end()));
92 OnMatchersModified();
93 }
94
RemoveRulesetsWithIDs(const std::set<RulesetID> & ids)95 void CompositeMatcher::RemoveRulesetsWithIDs(const std::set<RulesetID>& ids) {
96 size_t erased_count = base::EraseIf(
97 matchers_, [&ids](const std::unique_ptr<RulesetMatcher>& matcher) {
98 return base::Contains(ids, matcher->id());
99 });
100
101 if (erased_count > 0)
102 OnMatchersModified();
103 }
104
ComputeStaticRulesetIDs() const105 std::set<RulesetID> CompositeMatcher::ComputeStaticRulesetIDs() const {
106 std::set<RulesetID> result;
107 for (const std::unique_ptr<RulesetMatcher>& matcher : matchers_) {
108 if (matcher->id() == kDynamicRulesetID)
109 continue;
110
111 result.insert(matcher->id());
112 }
113
114 return result;
115 }
116
GetBeforeRequestAction(const RequestParams & params,PageAccess page_access) const117 ActionInfo CompositeMatcher::GetBeforeRequestAction(
118 const RequestParams& params,
119 PageAccess page_access) const {
120 ScopedGetBeforeRequestActionTimer timer;
121
122 bool notify_request_withheld = false;
123 base::Optional<RequestAction> final_action;
124
125 // The priority of the highest priority matching allow or allowAllRequests
126 // rule within this matcher, or base::nullopt otherwise.
127 base::Optional<uint64_t> max_allow_rule_priority;
128
129 for (const auto& matcher : matchers_) {
130 base::Optional<RequestAction> action =
131 matcher->GetBeforeRequestAction(params);
132
133 if (action && action->IsAllowOrAllowAllRequests()) {
134 max_allow_rule_priority =
135 max_allow_rule_priority
136 ? std::max(*max_allow_rule_priority, action->index_priority)
137 : action->index_priority;
138 }
139
140 if (action && action->type == RequestAction::Type::REDIRECT) {
141 // Redirecting requires host permissions.
142 // TODO(crbug.com/1033780): returning base::nullopt here results in
143 // counterintuitive behavior.
144 if (page_access == PageAccess::kDenied) {
145 action = base::nullopt;
146 } else if (page_access == PageAccess::kWithheld) {
147 action = base::nullopt;
148 notify_request_withheld = true;
149 }
150 }
151
152 final_action =
153 GetMaxPriorityAction(std::move(final_action), std::move(action));
154 }
155
156 params.allow_rule_max_priority[this] = max_allow_rule_priority;
157
158 if (final_action)
159 return ActionInfo(std::move(final_action), false);
160 return ActionInfo(base::nullopt, notify_request_withheld);
161 }
162
GetModifyHeadersActions(const RequestParams & params) const163 std::vector<RequestAction> CompositeMatcher::GetModifyHeadersActions(
164 const RequestParams& params) const {
165 std::vector<RequestAction> modify_headers_actions;
166 DCHECK(params.allow_rule_max_priority.contains(this));
167
168 // The priority of the highest priority matching allow or allowAllRequests
169 // rule within this matcher, or base::nullopt if no such rule exists.
170 base::Optional<uint64_t> max_allow_rule_priority =
171 params.allow_rule_max_priority[this];
172
173 for (const auto& matcher : matchers_) {
174 // Plumb |max_allow_rule_priority| into GetModifyHeadersActions so that
175 // modifyHeaders rules with priorities less than or equal to the highest
176 // priority matching allow/allowAllRequests rule are ignored.
177 std::vector<RequestAction> actions_for_matcher =
178 matcher->GetModifyHeadersActions(params, max_allow_rule_priority);
179
180 modify_headers_actions.insert(
181 modify_headers_actions.end(),
182 std::make_move_iterator(actions_for_matcher.begin()),
183 std::make_move_iterator(actions_for_matcher.end()));
184 }
185
186 // Sort |modify_headers_actions| in descending order of priority.
187 std::sort(modify_headers_actions.begin(), modify_headers_actions.end(),
188 std::greater<>());
189 return modify_headers_actions;
190 }
191
HasAnyExtraHeadersMatcher() const192 bool CompositeMatcher::HasAnyExtraHeadersMatcher() const {
193 if (!has_any_extra_headers_matcher_.has_value())
194 has_any_extra_headers_matcher_ = ComputeHasAnyExtraHeadersMatcher();
195 return has_any_extra_headers_matcher_.value();
196 }
197
OnRenderFrameCreated(content::RenderFrameHost * host)198 void CompositeMatcher::OnRenderFrameCreated(content::RenderFrameHost* host) {
199 for (auto& matcher : matchers_)
200 matcher->OnRenderFrameCreated(host);
201 }
202
OnRenderFrameDeleted(content::RenderFrameHost * host)203 void CompositeMatcher::OnRenderFrameDeleted(content::RenderFrameHost* host) {
204 for (auto& matcher : matchers_)
205 matcher->OnRenderFrameDeleted(host);
206 }
207
OnDidFinishNavigation(content::RenderFrameHost * host)208 void CompositeMatcher::OnDidFinishNavigation(content::RenderFrameHost* host) {
209 for (auto& matcher : matchers_)
210 matcher->OnDidFinishNavigation(host);
211 }
212
OnMatchersModified()213 void CompositeMatcher::OnMatchersModified() {
214 DCHECK(AreIDsUnique(matchers_));
215
216 // Clear the renderers' cache so that they take the updated rules into
217 // account.
218 ClearRendererCacheOnNavigation();
219
220 has_any_extra_headers_matcher_.reset();
221 }
222
ComputeHasAnyExtraHeadersMatcher() const223 bool CompositeMatcher::ComputeHasAnyExtraHeadersMatcher() const {
224 for (const auto& matcher : matchers_) {
225 if (matcher->IsExtraHeadersMatcher())
226 return true;
227 }
228 return false;
229 }
230
231 } // namespace declarative_net_request
232 } // namespace extensions
233