text: // Copyright (c) 2013 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  //
  // DeclarativeRule<>, DeclarativeConditionSet<>, and DeclarativeActionSet<>
  // templates usable with multiple different declarativeFoo systems.  These are
  // templated on the Condition and Action types that define the behavior of a
  // particular declarative event.
  
  #ifndef CHROME_BROWSER_EXTENSIONS_API_DECLARATIVE_DECLARATIVE_RULE_H__
  #define CHROME_BROWSER_EXTENSIONS_API_DECLARATIVE_DECLARATIVE_RULE_H__
  
  #include <limits>
  #include <set>
  #include <string>
  #include <vector>
  
  #include "base/callback.h"
  #include "base/memory/linked_ptr.h"
  #include "base/memory/scoped_vector.h"
  #include "base/stl_util.h"
  #include "base/time/time.h"
  #include "chrome/common/extensions/api/events.h"
  #include "extensions/common/matcher/url_matcher.h"
  
  namespace base {
  class Time;
  class Value;
  }
  
  namespace extensions {
  
  // This class stores a set of conditions that may be part of a DeclarativeRule.
  // If any condition is fulfilled, the Actions of the DeclarativeRule can be
  // triggered.
  //
  // ConditionT should be immutable after creation.  It must define the following
  // members:
  //
  //   // Arguments passed through from DeclarativeConditionSet::Create.
  //   static scoped_ptr<ConditionT> Create(
  //       URLMatcherConditionFactory* url_matcher_condition_factory,
  //       // Except this argument gets elements of the AnyVector.
  //       const base::Value& definition,
  //       std::string* error);
  //   // If the Condition needs to be filtered by some URLMatcherConditionSets,
  //   // append them to |condition_sets|.
  //   // DeclarativeConditionSet::GetURLMatcherConditionSets forwards here.
  //   void GetURLMatcherConditionSets(
  //       URLMatcherConditionSet::Vector* condition_sets);
  //   // |match_data| passed through from DeclarativeConditionSet::IsFulfilled.
  //   bool IsFulfilled(const ConditionT::MatchData& match_data);
  template<typename ConditionT>
  class DeclarativeConditionSet {
   public:
    typedef std::vector<linked_ptr<base::Value> > AnyVector;
    typedef std::vector<linked_ptr<const ConditionT> > Conditions;
    typedef typename Conditions::const_iterator const_iterator;
  
    // Factory method that creates a DeclarativeConditionSet according to the JSON
    // array |conditions| passed by the extension API. Sets |error| and returns
    // NULL in case of an error.
    static scoped_ptr<DeclarativeConditionSet> Create(
        URLMatcherConditionFactory* url_matcher_condition_factory,
        const AnyVector& conditions,
        std::string* error);
  
    const Conditions& conditions() const {
      return conditions_;
    }
  
    const_iterator begin() const { return conditions_.begin(); }
    const_iterator end() const { return conditions_.end(); }
  
    // If |url_match_trigger| is not -1, this function looks for a condition
    // with this URLMatcherConditionSet, and forwards to that condition's
    // IsFulfilled(|match_data|). If there is no such condition, then false is
    // returned. If |url_match_trigger| is -1, this function returns whether any
    // of the conditions without URL attributes is satisfied.
    bool IsFulfilled(URLMatcherConditionSet::ID url_match_trigger,
                     const typename ConditionT::MatchData& match_data) const;
  
    // Appends the URLMatcherConditionSet from all conditions to |condition_sets|.
    void GetURLMatcherConditionSets(
        URLMatcherConditionSet::Vector* condition_sets) const;
  
    // Returns whether there are some conditions without UrlFilter attributes.
    bool HasConditionsWithoutUrls() const {
      return !conditions_without_urls_.empty();
    }
  
   private:
    typedef std::map<URLMatcherConditionSet::ID, const ConditionT*>
        URLMatcherIdToCondition;
  
    DeclarativeConditionSet(
        const Conditions& conditions,
        const URLMatcherIdToCondition& match_id_to_condition,
        const std::vector<const ConditionT*>& conditions_without_urls);
  
    const URLMatcherIdToCondition match_id_to_condition_;
    const Conditions conditions_;
    const std::vector<const ConditionT*> conditions_without_urls_;
  
    DISALLOW_COPY_AND_ASSIGN(DeclarativeConditionSet);
  };
  
  // Immutable container for multiple actions.
  //
  // ActionT should be immutable after creation.  It must define the following
  // members:
  //
  //   // Arguments passed through from ActionSet::Create.
  //   static scoped_ptr<ActionT> Create(
  //       // Except this argument gets elements of the AnyVector.
  //       const base::Value& definition,
  //       std::string* error, bool* bad_message);
  //   void Apply(const std::string& extension_id,
  //              const base::Time& extension_install_time,
  //              // Contains action-type-specific in/out parameters.
  //              typename ActionT::ApplyInfo* apply_info) const;
  //   // Only needed if the RulesRegistry calls DeclarativeActionSet::Revert().
  //   void Revert(const std::string& extension_id,
  //               const base::Time& extension_install_time,
  //               // Contains action-type-specific in/out parameters.
  //               typename ActionT::ApplyInfo* apply_info) const;
  //   // Return the minimum priority of rules that can be evaluated after this
  //   // action runs.  A suitable default value is MIN_INT.
  //   int minimum_priority() const;
  //
  // TODO(battre): As DeclarativeActionSet can become the single owner of all
  // actions, we can optimize here by making some of them singletons (e.g. Cancel
  // actions).
  template<typename ActionT>
  class DeclarativeActionSet {
   public:
    typedef std::vector<linked_ptr<base::Value> > AnyVector;
    typedef std::vector<scoped_refptr<const ActionT> > Actions;
  
    explicit DeclarativeActionSet(const Actions& actions);
  
    // Factory method that instantiates a DeclarativeActionSet according to
    // |actions| which represents the array of actions received from the
    // extension API.
    static scoped_ptr<DeclarativeActionSet> Create(const AnyVector& actions,
                                                   std::string* error,
                                                   bool* bad_message);
  
    // Rules call this method when their conditions are fulfilled.
    void Apply(const std::string& extension_id,
               const base::Time& extension_install_time,
               typename ActionT::ApplyInfo* apply_info) const;
  
    // Rules call this method when they have stateful conditions, and those
    // conditions stop being fulfilled.  Rules with event-based conditions (e.g. a
    // network request happened) will never Revert() an action.
    void Revert(const std::string& extension_id,
                const base::Time& extension_install_time,
                typename ActionT::ApplyInfo* apply_info) const;
  
    // Returns the minimum priority of rules that may be evaluated after
    // this rule. Defaults to MIN_INT.
    int GetMinimumPriority() const;
  
    const Actions& actions() const { return actions_; }
  
   private:
    const Actions actions_;
  
    DISALLOW_COPY_AND_ASSIGN(DeclarativeActionSet);
  };
  
  // Representation of a rule of a declarative API:
  // https://developer.chrome.com/beta/extensions/events.html#declarative.
  // Generally a RulesRegistry will hold a collection of Rules for a given
  // declarative API and contain the logic for matching and applying them.
  //
  // See DeclarativeConditionSet and DeclarativeActionSet for the requirements on
  // ConditionT and ActionT.
  template<typename ConditionT, typename ActionT>
  class DeclarativeRule {
   public:
    typedef std::string ExtensionId;
    typedef std::string RuleId;
    typedef std::pair<ExtensionId, RuleId> GlobalRuleId;
    typedef int Priority;
    typedef DeclarativeConditionSet<ConditionT> ConditionSet;
    typedef DeclarativeActionSet<ActionT> ActionSet;
    typedef extensions::api::events::Rule JsonRule;
    typedef std::vector<std::string> Tags;
  
    // Checks whether the set of |conditions| and |actions| are consistent.
    // Returns true in case of consistency and MUST set |error| otherwise.
    typedef base::Callback<bool(const ConditionSet* conditions,
                                const ActionSet* actions,
                                std::string* error)> ConsistencyChecker;
  
    DeclarativeRule(const GlobalRuleId& id,
                    const Tags& tags,
                    base::Time extension_installation_time,
                    scoped_ptr<ConditionSet> conditions,
                    scoped_ptr<ActionSet> actions,
                    Priority priority);
  
    // Creates a DeclarativeRule for an extension given a json definition.  The
    // format of each condition and action's json is up to the specific ConditionT
    // and ActionT.
    //
    // Before constructing the final rule, calls check_consistency(conditions,
    // actions, error) and returns NULL if it fails.  Pass NULL if no consistency
    // check is needed.  If |error| is empty, the translation was successful and
    // the returned rule is internally consistent.
    static scoped_ptr<DeclarativeRule> Create(
        URLMatcherConditionFactory* url_matcher_condition_factory,
        const std::string& extension_id,
        base::Time extension_installation_time,
        linked_ptr<JsonRule> rule,
        ConsistencyChecker check_consistency,
        std::string* error);
  
    const GlobalRuleId& id() const { return id_; }
    const Tags& tags() const { return tags_; }
    const std::string& extension_id() const { return id_.first; }
    const ConditionSet& conditions() const { return *conditions_; }
    const ActionSet& actions() const { return *actions_; }
    Priority priority() const { return priority_; }
  
    // Calls actions().Apply(extension_id(), extension_installation_time_,
    // apply_info). This function should only be called when the conditions_ are
    // fulfilled (from a semantic point of view; no harm is done if this function
    // is called at other times for testing purposes).
    void Apply(typename ActionT::ApplyInfo* apply_info) const;
  
    // Returns the minimum priority of rules that may be evaluated after
    // this rule. Defaults to MIN_INT. Only valid if the conditions of this rule
    // are fulfilled.
    Priority GetMinimumPriority() const;
  
   private:
    GlobalRuleId id_;
    Tags tags_;
    base::Time extension_installation_time_;  // For precedences of rules.
    scoped_ptr<ConditionSet> conditions_;
    scoped_ptr<ActionSet> actions_;
    Priority priority_;
  
    DISALLOW_COPY_AND_ASSIGN(DeclarativeRule);
  };
  
  // Implementation details below here.
  
  //
  // DeclarativeConditionSet
  //
  
  template<typename ConditionT>
  bool DeclarativeConditionSet<ConditionT>::IsFulfilled(
      URLMatcherConditionSet::ID url_match_trigger,
      const typename ConditionT::MatchData& match_data) const {
    if (url_match_trigger == -1) {
      // Invalid trigger -- indication that we should only check conditions
      // without URL attributes.
      for (typename std::vector<const ConditionT*>::const_iterator it =
               conditions_without_urls_.begin();
           it != conditions_without_urls_.end(); ++it) {
        if ((*it)->IsFulfilled(match_data))
          return true;
      }
      return false;
    }
  
    typename URLMatcherIdToCondition::const_iterator triggered =
        match_id_to_condition_.find(url_match_trigger);
    return (triggered != match_id_to_condition_.end() &&
            triggered->second->IsFulfilled(match_data));
  }
  
  template<typename ConditionT>
  void DeclarativeConditionSet<ConditionT>::GetURLMatcherConditionSets(
      URLMatcherConditionSet::Vector* condition_sets) const {
    for (typename Conditions::const_iterator i = conditions_.begin();
         i != conditions_.end(); ++i) {
      (*i)->GetURLMatcherConditionSets(condition_sets);
    }
  }
  
  // static
  template<typename ConditionT>
  scoped_ptr<DeclarativeConditionSet<ConditionT> >
  DeclarativeConditionSet<ConditionT>::Create(
      URLMatcherConditionFactory* url_matcher_condition_factory,
      const AnyVector& conditions,
      std::string* error) {
    Conditions result;
  
    for (AnyVector::const_iterator i = conditions.begin();
         i != conditions.end(); ++i) {
      CHECK(i->get());
      scoped_ptr<ConditionT> condition =
          ConditionT::Create(url_matcher_condition_factory, **i, error);
      if (!error->empty())
        return scoped_ptr<DeclarativeConditionSet>();
      result.push_back(make_linked_ptr(condition.release()));
    }
  
    URLMatcherIdToCondition match_id_to_condition;
    std::vector<const ConditionT*> conditions_without_urls;
    URLMatcherConditionSet::Vector condition_sets;
  
    for (typename Conditions::const_iterator i = result.begin();
         i != result.end(); ++i) {
      condition_sets.clear();
      (*i)->GetURLMatcherConditionSets(&condition_sets);
      if (condition_sets.empty()) {
        conditions_without_urls.push_back(i->get());
      } else {
        for (URLMatcherConditionSet::Vector::const_iterator
                 match_set = condition_sets.begin();
             match_set != condition_sets.end(); ++match_set)
          match_id_to_condition[(*match_set)->id()] = i->get();
      }
    }
  
    return make_scoped_ptr(new DeclarativeConditionSet(
        result, match_id_to_condition, conditions_without_urls));
  }
  
  template<typename ConditionT>
  DeclarativeConditionSet<ConditionT>::DeclarativeConditionSet(
      const Conditions& conditions,
      const URLMatcherIdToCondition& match_id_to_condition,
      const std::vector<const ConditionT*>& conditions_without_urls)
      : match_id_to_condition_(match_id_to_condition),
        conditions_(conditions),
        conditions_without_urls_(conditions_without_urls) {}
  
  //
  // DeclarativeActionSet
  //
  
  template<typename ActionT>
  DeclarativeActionSet<ActionT>::DeclarativeActionSet(const Actions& actions)
      : actions_(actions) {}
  
  // static
  template<typename ActionT>
  scoped_ptr<DeclarativeActionSet<ActionT> >
  DeclarativeActionSet<ActionT>::Create(
      const AnyVector& actions,
      std::string* error,
      bool* bad_message) {
    *error = "";
    *bad_message = false;
    Actions result;
  
    for (AnyVector::const_iterator i = actions.begin();
         i != actions.end(); ++i) {
      CHECK(i->get());
      scoped_refptr<const ActionT> action =
          ActionT::Create(**i, error, bad_message);
      if (!error->empty() || *bad_message)
        return scoped_ptr<DeclarativeActionSet>();
      result.push_back(action);
    }
  
    return scoped_ptr<DeclarativeActionSet>(new DeclarativeActionSet(result));
  }
  
  template<typename ActionT>
  void DeclarativeActionSet<ActionT>::Apply(
      const std::string& extension_id,
      const base::Time& extension_install_time,
      typename ActionT::ApplyInfo* apply_info) const {
    for (typename Actions::const_iterator i = actions_.begin();
         i != actions_.end(); ++i)
      (*i)->Apply(extension_id, extension_install_time, apply_info);
  }
  
  template<typename ActionT>
  void DeclarativeActionSet<ActionT>::Revert(
      const std::string& extension_id,
      const base::Time& extension_install_time,
      typename ActionT::ApplyInfo* apply_info) const {
    for (typename Actions::const_iterator i = actions_.begin();
         i != actions_.end(); ++i)
      (*i)->Revert(extension_id, extension_install_time, apply_info);
  }
  
  template<typename ActionT>
  int DeclarativeActionSet<ActionT>::GetMinimumPriority() const {
    int minimum_priority = std::numeric_limits<int>::min();
    for (typename Actions::const_iterator i = actions_.begin();
         i != actions_.end(); ++i) {
      minimum_priority = std::max(minimum_priority, (*i)->minimum_priority());
    }
    return minimum_priority;
  }
  
  //
  // DeclarativeRule
  //
  
  template<typename ConditionT, typename ActionT>
  DeclarativeRule<ConditionT, ActionT>::DeclarativeRule(
      const GlobalRuleId& id,
      const Tags& tags,
      base::Time extension_installation_time,
      scoped_ptr<ConditionSet> conditions,
      scoped_ptr<ActionSet> actions,
      Priority priority)
      : id_(id),
        tags_(tags),
        extension_installation_time_(extension_installation_time),
        conditions_(conditions.release()),
        actions_(actions.release()),
        priority_(priority) {
    CHECK(conditions_.get());
    CHECK(actions_.get());
  }
  
  // static
  template<typename ConditionT, typename ActionT>
  scoped_ptr<DeclarativeRule<ConditionT, ActionT> >
  DeclarativeRule<ConditionT, ActionT>::Create(
      URLMatcherConditionFactory* url_matcher_condition_factory,
      const std::string& extension_id,
      base::Time extension_installation_time,
      linked_ptr<JsonRule> rule,
      ConsistencyChecker check_consistency,
      std::string* error) {
    scoped_ptr<DeclarativeRule> error_result;
  
    scoped_ptr<ConditionSet> conditions = ConditionSet::Create(
        url_matcher_condition_factory, rule->conditions, error);
    if (!error->empty())
      return error_result.Pass();
    CHECK(conditions.get());
  
    bool bad_message = false;
    scoped_ptr<ActionSet> actions =
        ActionSet::Create(rule->actions, error, &bad_message);
    if (bad_message) {
      // TODO(battre) Export concept of bad_message to caller, the extension
      // should be killed in case it is true.
      *error = "An action of a rule set had an invalid "
          "structure that should have been caught by the JSON validator.";
      return error_result.Pass();
    }
    if (!error->empty() || bad_message)
      return error_result.Pass();
    CHECK(actions.get());
  
    if (!check_consistency.is_null() &&
        !check_consistency.Run(conditions.get(), actions.get(), error)) {
      DCHECK(!error->empty());
      return error_result.Pass();
    }
  
    CHECK(rule->priority.get());
    int priority = *(rule->priority);
  
    GlobalRuleId rule_id(extension_id, *(rule->id));
    Tags tags = rule->tags ? *rule->tags : Tags();
    return scoped_ptr<DeclarativeRule>(
        new DeclarativeRule(rule_id, tags, extension_installation_time,
                            conditions.Pass(), actions.Pass(), priority));
  }
  
  template<typename ConditionT, typename ActionT>
  void DeclarativeRule<ConditionT, ActionT>::Apply(
      typename ActionT::ApplyInfo* apply_info) const {
    return actions_->Apply(extension_id(),
                           extension_installation_time_,
                           apply_info);
  }
  
  template<typename ConditionT, typename ActionT>
  int DeclarativeRule<ConditionT, ActionT>::GetMinimumPriority() const {
    return actions_->GetMinimumPriority();
  }
  
  }  // namespace extensions
  
  #endif  // CHROME_BROWSER_EXTENSIONS_API_DECLARATIVE_DECLARATIVE_RULE_H__
  