1// Copyright (c) 2013 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// DeclarativeRule<>, DeclarativeConditionSet<>, and DeclarativeActionSet<>
6// templates usable with multiple different declarativeFoo systems.  These are
7// templated on the Condition and Action types that define the behavior of a
8// particular declarative event.
9
10#ifndef CHROME_BROWSER_EXTENSIONS_API_DECLARATIVE_DECLARATIVE_RULE_H__
11#define CHROME_BROWSER_EXTENSIONS_API_DECLARATIVE_DECLARATIVE_RULE_H__
12
13#include <limits>
14#include <set>
15#include <string>
16#include <vector>
17
18#include "base/callback.h"
19#include "base/memory/linked_ptr.h"
20#include "base/memory/scoped_vector.h"
21#include "base/stl_util.h"
22#include "base/time/time.h"
23#include "chrome/common/extensions/api/events.h"
24#include "components/url_matcher/url_matcher.h"
25#include "extensions/common/extension.h"
26
27namespace base {
28class Time;
29class Value;
30}
31
32namespace extensions {
33
34// This class stores a set of conditions that may be part of a DeclarativeRule.
35// If any condition is fulfilled, the Actions of the DeclarativeRule can be
36// triggered.
37//
38// ConditionT should be immutable after creation.  It must define the following
39// members:
40//
41//   // Arguments passed through from DeclarativeConditionSet::Create.
42//   static scoped_ptr<ConditionT> Create(
43//       const Extension* extension,
44//       URLMatcherConditionFactory* url_matcher_condition_factory,
45//       // Except this argument gets elements of the AnyVector.
46//       const base::Value& definition,
47//       std::string* error);
48//   // If the Condition needs to be filtered by some URLMatcherConditionSets,
49//   // append them to |condition_sets|.
50//   // DeclarativeConditionSet::GetURLMatcherConditionSets forwards here.
51//   void GetURLMatcherConditionSets(
52//       URLMatcherConditionSet::Vector* condition_sets);
53//   // |match_data| passed through from DeclarativeConditionSet::IsFulfilled.
54//   bool IsFulfilled(const ConditionT::MatchData& match_data);
55template<typename ConditionT>
56class DeclarativeConditionSet {
57 public:
58  typedef std::vector<linked_ptr<base::Value> > AnyVector;
59  typedef std::vector<linked_ptr<const ConditionT> > Conditions;
60  typedef typename Conditions::const_iterator const_iterator;
61
62  // Factory method that creates a DeclarativeConditionSet for |extension|
63  // according to the JSON array |conditions| passed by the extension API. Sets
64  // |error| and returns NULL in case of an error.
65  static scoped_ptr<DeclarativeConditionSet> Create(
66      const Extension* extension,
67      url_matcher::URLMatcherConditionFactory* url_matcher_condition_factory,
68      const AnyVector& conditions,
69      std::string* error);
70
71  const Conditions& conditions() const {
72    return conditions_;
73  }
74
75  const_iterator begin() const { return conditions_.begin(); }
76  const_iterator end() const { return conditions_.end(); }
77
78  // If |url_match_trigger| is not -1, this function looks for a condition
79  // with this URLMatcherConditionSet, and forwards to that condition's
80  // IsFulfilled(|match_data|). If there is no such condition, then false is
81  // returned. If |url_match_trigger| is -1, this function returns whether any
82  // of the conditions without URL attributes is satisfied.
83  bool IsFulfilled(url_matcher::URLMatcherConditionSet::ID url_match_trigger,
84                   const typename ConditionT::MatchData& match_data) const;
85
86  // Appends the URLMatcherConditionSet from all conditions to |condition_sets|.
87  void GetURLMatcherConditionSets(
88      url_matcher::URLMatcherConditionSet::Vector* condition_sets) const;
89
90  // Returns whether there are some conditions without UrlFilter attributes.
91  bool HasConditionsWithoutUrls() const {
92    return !conditions_without_urls_.empty();
93  }
94
95 private:
96  typedef std::map<url_matcher::URLMatcherConditionSet::ID, const ConditionT*>
97      URLMatcherIdToCondition;
98
99  DeclarativeConditionSet(
100      const Conditions& conditions,
101      const URLMatcherIdToCondition& match_id_to_condition,
102      const std::vector<const ConditionT*>& conditions_without_urls);
103
104  const URLMatcherIdToCondition match_id_to_condition_;
105  const Conditions conditions_;
106  const std::vector<const ConditionT*> conditions_without_urls_;
107
108  DISALLOW_COPY_AND_ASSIGN(DeclarativeConditionSet);
109};
110
111// Immutable container for multiple actions.
112//
113// ActionT should be immutable after creation.  It must define the following
114// members:
115//
116//   // Arguments passed through from ActionSet::Create.
117//   static scoped_ptr<ActionT> Create(
118//       const Extension* extension,
119//       // Except this argument gets elements of the AnyVector.
120//       const base::Value& definition,
121//       std::string* error, bool* bad_message);
122//   void Apply(const std::string& extension_id,
123//              const base::Time& extension_install_time,
124//              // Contains action-type-specific in/out parameters.
125//              typename ActionT::ApplyInfo* apply_info) const;
126//   // Only needed if the RulesRegistry calls DeclarativeActionSet::Revert().
127//   void Revert(const std::string& extension_id,
128//               const base::Time& extension_install_time,
129//               // Contains action-type-specific in/out parameters.
130//               typename ActionT::ApplyInfo* apply_info) const;
131//   // Return the minimum priority of rules that can be evaluated after this
132//   // action runs.  A suitable default value is MIN_INT.
133//   int minimum_priority() const;
134//
135// TODO(battre): As DeclarativeActionSet can become the single owner of all
136// actions, we can optimize here by making some of them singletons (e.g. Cancel
137// actions).
138template<typename ActionT>
139class DeclarativeActionSet {
140 public:
141  typedef std::vector<linked_ptr<base::Value> > AnyVector;
142  typedef std::vector<scoped_refptr<const ActionT> > Actions;
143
144  explicit DeclarativeActionSet(const Actions& actions);
145
146  // Factory method that instantiates a DeclarativeActionSet for |extension|
147  // according to |actions| which represents the array of actions received from
148  // the extension API.
149  static scoped_ptr<DeclarativeActionSet> Create(const Extension* extension,
150                                                 const AnyVector& actions,
151                                                 std::string* error,
152                                                 bool* bad_message);
153
154  // Rules call this method when their conditions are fulfilled.
155  void Apply(const std::string& extension_id,
156             const base::Time& extension_install_time,
157             typename ActionT::ApplyInfo* apply_info) const;
158
159  // Rules call this method when they have stateful conditions, and those
160  // conditions stop being fulfilled.  Rules with event-based conditions (e.g. a
161  // network request happened) will never Revert() an action.
162  void Revert(const std::string& extension_id,
163              const base::Time& extension_install_time,
164              typename ActionT::ApplyInfo* apply_info) const;
165
166  // Returns the minimum priority of rules that may be evaluated after
167  // this rule. Defaults to MIN_INT.
168  int GetMinimumPriority() const;
169
170  const Actions& actions() const { return actions_; }
171
172 private:
173  const Actions actions_;
174
175  DISALLOW_COPY_AND_ASSIGN(DeclarativeActionSet);
176};
177
178// Representation of a rule of a declarative API:
179// https://developer.chrome.com/beta/extensions/events.html#declarative.
180// Generally a RulesRegistry will hold a collection of Rules for a given
181// declarative API and contain the logic for matching and applying them.
182//
183// See DeclarativeConditionSet and DeclarativeActionSet for the requirements on
184// ConditionT and ActionT.
185template<typename ConditionT, typename ActionT>
186class DeclarativeRule {
187 public:
188  typedef std::string ExtensionId;
189  typedef std::string RuleId;
190  typedef std::pair<ExtensionId, RuleId> GlobalRuleId;
191  typedef int Priority;
192  typedef DeclarativeConditionSet<ConditionT> ConditionSet;
193  typedef DeclarativeActionSet<ActionT> ActionSet;
194  typedef extensions::api::events::Rule JsonRule;
195  typedef std::vector<std::string> Tags;
196
197  // Checks whether the set of |conditions| and |actions| are consistent.
198  // Returns true in case of consistency and MUST set |error| otherwise.
199  typedef base::Callback<bool(const ConditionSet* conditions,
200                              const ActionSet* actions,
201                              std::string* error)> ConsistencyChecker;
202
203  DeclarativeRule(const GlobalRuleId& id,
204                  const Tags& tags,
205                  base::Time extension_installation_time,
206                  scoped_ptr<ConditionSet> conditions,
207                  scoped_ptr<ActionSet> actions,
208                  Priority priority);
209
210  // Creates a DeclarativeRule for |extension| given a json definition.  The
211  // format of each condition and action's json is up to the specific ConditionT
212  // and ActionT.  |extension| may be NULL in tests.
213  //
214  // Before constructing the final rule, calls check_consistency(conditions,
215  // actions, error) and returns NULL if it fails.  Pass NULL if no consistency
216  // check is needed.  If |error| is empty, the translation was successful and
217  // the returned rule is internally consistent.
218  static scoped_ptr<DeclarativeRule> Create(
219      url_matcher::URLMatcherConditionFactory* url_matcher_condition_factory,
220      const Extension* extension,
221      base::Time extension_installation_time,
222      linked_ptr<JsonRule> rule,
223      ConsistencyChecker check_consistency,
224      std::string* error);
225
226  const GlobalRuleId& id() const { return id_; }
227  const Tags& tags() const { return tags_; }
228  const std::string& extension_id() const { return id_.first; }
229  const ConditionSet& conditions() const { return *conditions_; }
230  const ActionSet& actions() const { return *actions_; }
231  Priority priority() const { return priority_; }
232
233  // Calls actions().Apply(extension_id(), extension_installation_time_,
234  // apply_info). This function should only be called when the conditions_ are
235  // fulfilled (from a semantic point of view; no harm is done if this function
236  // is called at other times for testing purposes).
237  void Apply(typename ActionT::ApplyInfo* apply_info) const;
238
239  // Returns the minimum priority of rules that may be evaluated after
240  // this rule. Defaults to MIN_INT. Only valid if the conditions of this rule
241  // are fulfilled.
242  Priority GetMinimumPriority() const;
243
244 private:
245  GlobalRuleId id_;
246  Tags tags_;
247  base::Time extension_installation_time_;  // For precedences of rules.
248  scoped_ptr<ConditionSet> conditions_;
249  scoped_ptr<ActionSet> actions_;
250  Priority priority_;
251
252  DISALLOW_COPY_AND_ASSIGN(DeclarativeRule);
253};
254
255// Implementation details below here.
256
257//
258// DeclarativeConditionSet
259//
260
261template<typename ConditionT>
262bool DeclarativeConditionSet<ConditionT>::IsFulfilled(
263    url_matcher::URLMatcherConditionSet::ID url_match_trigger,
264    const typename ConditionT::MatchData& match_data) const {
265  if (url_match_trigger == -1) {
266    // Invalid trigger -- indication that we should only check conditions
267    // without URL attributes.
268    for (typename std::vector<const ConditionT*>::const_iterator it =
269             conditions_without_urls_.begin();
270         it != conditions_without_urls_.end(); ++it) {
271      if ((*it)->IsFulfilled(match_data))
272        return true;
273    }
274    return false;
275  }
276
277  typename URLMatcherIdToCondition::const_iterator triggered =
278      match_id_to_condition_.find(url_match_trigger);
279  return (triggered != match_id_to_condition_.end() &&
280          triggered->second->IsFulfilled(match_data));
281}
282
283template<typename ConditionT>
284void DeclarativeConditionSet<ConditionT>::GetURLMatcherConditionSets(
285    url_matcher::URLMatcherConditionSet::Vector* condition_sets) const {
286  for (typename Conditions::const_iterator i = conditions_.begin();
287       i != conditions_.end(); ++i) {
288    (*i)->GetURLMatcherConditionSets(condition_sets);
289  }
290}
291
292// static
293template<typename ConditionT>
294scoped_ptr<DeclarativeConditionSet<ConditionT> >
295DeclarativeConditionSet<ConditionT>::Create(
296    const Extension* extension,
297    url_matcher::URLMatcherConditionFactory* url_matcher_condition_factory,
298    const AnyVector& conditions,
299    std::string* error) {
300  Conditions result;
301
302  for (AnyVector::const_iterator i = conditions.begin();
303       i != conditions.end(); ++i) {
304    CHECK(i->get());
305    scoped_ptr<ConditionT> condition = ConditionT::Create(
306        extension, url_matcher_condition_factory, **i, error);
307    if (!error->empty())
308      return scoped_ptr<DeclarativeConditionSet>();
309    result.push_back(make_linked_ptr(condition.release()));
310  }
311
312  URLMatcherIdToCondition match_id_to_condition;
313  std::vector<const ConditionT*> conditions_without_urls;
314  url_matcher::URLMatcherConditionSet::Vector condition_sets;
315
316  for (typename Conditions::const_iterator i = result.begin();
317       i != result.end(); ++i) {
318    condition_sets.clear();
319    (*i)->GetURLMatcherConditionSets(&condition_sets);
320    if (condition_sets.empty()) {
321      conditions_without_urls.push_back(i->get());
322    } else {
323      for (url_matcher::URLMatcherConditionSet::Vector::const_iterator
324               match_set = condition_sets.begin();
325           match_set != condition_sets.end(); ++match_set)
326        match_id_to_condition[(*match_set)->id()] = i->get();
327    }
328  }
329
330  return make_scoped_ptr(new DeclarativeConditionSet(
331      result, match_id_to_condition, conditions_without_urls));
332}
333
334template<typename ConditionT>
335DeclarativeConditionSet<ConditionT>::DeclarativeConditionSet(
336    const Conditions& conditions,
337    const URLMatcherIdToCondition& match_id_to_condition,
338    const std::vector<const ConditionT*>& conditions_without_urls)
339    : match_id_to_condition_(match_id_to_condition),
340      conditions_(conditions),
341      conditions_without_urls_(conditions_without_urls) {}
342
343//
344// DeclarativeActionSet
345//
346
347template<typename ActionT>
348DeclarativeActionSet<ActionT>::DeclarativeActionSet(const Actions& actions)
349    : actions_(actions) {}
350
351// static
352template<typename ActionT>
353scoped_ptr<DeclarativeActionSet<ActionT> >
354DeclarativeActionSet<ActionT>::Create(
355    const Extension* extension,
356    const AnyVector& actions,
357    std::string* error,
358    bool* bad_message) {
359  *error = "";
360  *bad_message = false;
361  Actions result;
362
363  for (AnyVector::const_iterator i = actions.begin();
364       i != actions.end(); ++i) {
365    CHECK(i->get());
366    scoped_refptr<const ActionT> action =
367        ActionT::Create(extension, **i, error, bad_message);
368    if (!error->empty() || *bad_message)
369      return scoped_ptr<DeclarativeActionSet>();
370    result.push_back(action);
371  }
372
373  return scoped_ptr<DeclarativeActionSet>(new DeclarativeActionSet(result));
374}
375
376template<typename ActionT>
377void DeclarativeActionSet<ActionT>::Apply(
378    const std::string& extension_id,
379    const base::Time& extension_install_time,
380    typename ActionT::ApplyInfo* apply_info) const {
381  for (typename Actions::const_iterator i = actions_.begin();
382       i != actions_.end(); ++i)
383    (*i)->Apply(extension_id, extension_install_time, apply_info);
384}
385
386template<typename ActionT>
387void DeclarativeActionSet<ActionT>::Revert(
388    const std::string& extension_id,
389    const base::Time& extension_install_time,
390    typename ActionT::ApplyInfo* apply_info) const {
391  for (typename Actions::const_iterator i = actions_.begin();
392       i != actions_.end(); ++i)
393    (*i)->Revert(extension_id, extension_install_time, apply_info);
394}
395
396template<typename ActionT>
397int DeclarativeActionSet<ActionT>::GetMinimumPriority() const {
398  int minimum_priority = std::numeric_limits<int>::min();
399  for (typename Actions::const_iterator i = actions_.begin();
400       i != actions_.end(); ++i) {
401    minimum_priority = std::max(minimum_priority, (*i)->minimum_priority());
402  }
403  return minimum_priority;
404}
405
406//
407// DeclarativeRule
408//
409
410template<typename ConditionT, typename ActionT>
411DeclarativeRule<ConditionT, ActionT>::DeclarativeRule(
412    const GlobalRuleId& id,
413    const Tags& tags,
414    base::Time extension_installation_time,
415    scoped_ptr<ConditionSet> conditions,
416    scoped_ptr<ActionSet> actions,
417    Priority priority)
418    : id_(id),
419      tags_(tags),
420      extension_installation_time_(extension_installation_time),
421      conditions_(conditions.release()),
422      actions_(actions.release()),
423      priority_(priority) {
424  CHECK(conditions_.get());
425  CHECK(actions_.get());
426}
427
428// static
429template<typename ConditionT, typename ActionT>
430scoped_ptr<DeclarativeRule<ConditionT, ActionT> >
431DeclarativeRule<ConditionT, ActionT>::Create(
432    url_matcher::URLMatcherConditionFactory* url_matcher_condition_factory,
433    const Extension* extension,
434    base::Time extension_installation_time,
435    linked_ptr<JsonRule> rule,
436    ConsistencyChecker check_consistency,
437    std::string* error) {
438  scoped_ptr<DeclarativeRule> error_result;
439
440  scoped_ptr<ConditionSet> conditions = ConditionSet::Create(
441      extension, url_matcher_condition_factory, rule->conditions, error);
442  if (!error->empty())
443    return error_result.Pass();
444  CHECK(conditions.get());
445
446  bool bad_message = false;
447  scoped_ptr<ActionSet> actions =
448      ActionSet::Create(extension, rule->actions, error, &bad_message);
449  if (bad_message) {
450    // TODO(battre) Export concept of bad_message to caller, the extension
451    // should be killed in case it is true.
452    *error = "An action of a rule set had an invalid "
453        "structure that should have been caught by the JSON validator.";
454    return error_result.Pass();
455  }
456  if (!error->empty() || bad_message)
457    return error_result.Pass();
458  CHECK(actions.get());
459
460  if (!check_consistency.is_null() &&
461      !check_consistency.Run(conditions.get(), actions.get(), error)) {
462    DCHECK(!error->empty());
463    return error_result.Pass();
464  }
465
466  CHECK(rule->priority.get());
467  int priority = *(rule->priority);
468
469  GlobalRuleId rule_id(extension->id(), *(rule->id));
470  Tags tags = rule->tags ? *rule->tags : Tags();
471  return scoped_ptr<DeclarativeRule>(
472      new DeclarativeRule(rule_id, tags, extension_installation_time,
473                          conditions.Pass(), actions.Pass(), priority));
474}
475
476template<typename ConditionT, typename ActionT>
477void DeclarativeRule<ConditionT, ActionT>::Apply(
478    typename ActionT::ApplyInfo* apply_info) const {
479  return actions_->Apply(extension_id(),
480                         extension_installation_time_,
481                         apply_info);
482}
483
484template<typename ConditionT, typename ActionT>
485int DeclarativeRule<ConditionT, ActionT>::GetMinimumPriority() const {
486  return actions_->GetMinimumPriority();
487}
488
489}  // namespace extensions
490
491#endif  // CHROME_BROWSER_EXTENSIONS_API_DECLARATIVE_DECLARATIVE_RULE_H__
492