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