This is an automated email from the ASF dual-hosted git repository.

zwoop pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new aea1e2c6d8 HRW: Add support for elif, in if-elif-else (#12304)
aea1e2c6d8 is described below

commit aea1e2c6d863eea72bf855951d00bb45fd91ded0
Author: Leif Hedstrom <[email protected]>
AuthorDate: Tue Jun 24 13:50:29 2025 -0500

    HRW: Add support for elif, in if-elif-else (#12304)
    
    * HRW: Add support for elif, in if-elif-else
    
    * Change the rules to store std::unique_ptr
---
 doc/admin-guide/plugins/header_rewrite.en.rst      |  22 ++++
 plugins/header_rewrite/header_rewrite.cc           | 135 +++++++++++----------
 plugins/header_rewrite/parser.cc                   |  15 +--
 plugins/header_rewrite/parser.h                    |  21 +++-
 plugins/header_rewrite/ruleset.cc                  |  29 +++--
 plugins/header_rewrite/ruleset.h                   | 117 ++++++++++++------
 .../pluginTest/header_rewrite/gold/cond-elif.gold  |  11 ++
 .../header_rewrite/header_rewrite_url.test.py      |  11 ++
 .../header_rewrite/rules/rule_client.conf          |   5 +-
 9 files changed, 240 insertions(+), 126 deletions(-)

diff --git a/doc/admin-guide/plugins/header_rewrite.en.rst 
b/doc/admin-guide/plugins/header_rewrite.en.rst
index 5b2d232500..be45b57c82 100644
--- a/doc/admin-guide/plugins/header_rewrite.en.rst
+++ b/doc/admin-guide/plugins/header_rewrite.en.rst
@@ -142,6 +142,26 @@ with the word ``else``. The following example illustrates 
this::
 The ``else`` clause is not a condition, and does not take any flags, it is
 of course optional, but when specified must be followed by at least one 
operator.
 
+You can also do an ``elif`` (else if) clause, which is specified by starting a 
new line
+with the word ``elif``. The following example illustrates this::
+
+    cond %{STATUS} >399 [AND]
+    cond %{STATUS} <500
+      set-status 404
+    elif
+      cond %{STATUS} =503
+        set-status 502
+    else
+      set-status 503
+
+Keep in mind that nesting the ``else`` and ``elif`` clauses is not allowed, 
but any
+number of ``elif`` clauses can be specified. We can consider these clauses are 
more
+powerful and flexible ``switch`` statement. In an ``if-elif-else`` rule, only 
one
+will evaluate its operators.
+
+Similarly, each ``else`` and ``elif`` have the same implied
+:ref:`Hook Condition <hook_conditions>` as the initial condition.
+
 State variables
 ---------------
 
@@ -1239,6 +1259,8 @@ specifically and exclusively for a request header or just 
for a response
 header? And how do you ensure that a header rewrite occurs against a request
 before it is proxied?
 
+.. _hook_conditions:
+
 Hook Conditions
 ---------------
 
diff --git a/plugins/header_rewrite/header_rewrite.cc 
b/plugins/header_rewrite/header_rewrite.cc
index 449e6ea1f0..45c447e149 100644
--- a/plugins/header_rewrite/header_rewrite.cc
+++ b/plugins/header_rewrite/header_rewrite.cc
@@ -21,6 +21,7 @@
 #include <string>
 #include <stack>
 #include <stdexcept>
+#include <array>
 #include <getopt.h>
 
 #include "ts/ts.h"
@@ -78,9 +79,6 @@ public:
   RulesConfig()
   {
     Dbg(dbg_ctl, "RulesConfig CTOR");
-    memset(_rules, 0, sizeof(_rules));
-    memset(_resids, 0, sizeof(_resids));
-
     _cont = TSContCreate(cont_rewrite_headers, nullptr);
     TSContDataSet(_cont, static_cast<void *>(this));
   }
@@ -88,55 +86,46 @@ public:
   ~RulesConfig()
   {
     Dbg(dbg_ctl, "RulesConfig DTOR");
-    for (int i = TS_HTTP_READ_REQUEST_HDR_HOOK; i <= TS_HTTP_LAST_HOOK; ++i) { 
// lgtm[cpp/constant-comparison]
-      delete _rules[i];
-    }
     TSContDestroy(_cont);
   }
 
-  TSCont
+  [[nodiscard]] TSCont
   continuation() const
   {
     return _cont;
   }
 
-  ResourceIDs
+  [[nodiscard]] ResourceIDs
   resid(int hook) const
   {
     return _resids[hook];
   }
 
-  RuleSet *
+  [[nodiscard]] RuleSet *
   rule(int hook) const
   {
-    return _rules[hook];
+    return _rules[hook].get();
   }
 
   bool parse_config(const std::string &fname, TSHttpHookID default_hook, char 
*from_url = nullptr, char *to_url = nullptr);
 
 private:
-  bool add_rule(RuleSet *rule);
+  void add_rule(std::unique_ptr<RuleSet> rule);
 
-  TSCont      _cont;
-  RuleSet    *_rules[TS_HTTP_LAST_HOOK + 1];
-  ResourceIDs _resids[TS_HTTP_LAST_HOOK + 1];
+  TSCont                                                      _cont;
+  std::array<std::unique_ptr<RuleSet>, TS_HTTP_LAST_HOOK + 1> _rules{};
+  std::array<ResourceIDs, TS_HTTP_LAST_HOOK + 1>              _resids{};
 };
 
-// Helper function to add a rule to the rulesets
-bool
-RulesConfig::add_rule(RuleSet *rule)
+void
+RulesConfig::add_rule(std::unique_ptr<RuleSet> rule)
 {
-  if (rule && rule->has_operator()) {
-    Dbg(dbg_ctl, "   Adding rule to hook=%s", 
TSHttpHookNameLookup(rule->get_hook()));
-    if (nullptr == _rules[rule->get_hook()]) {
-      _rules[rule->get_hook()] = rule;
-    } else {
-      _rules[rule->get_hook()]->append(rule);
-    }
-    return true;
+  int hook = rule->get_hook();
+  if (!_rules[hook]) {
+    _rules[hook] = std::move(rule);
+  } else {
+    _rules[hook]->append(std::move(rule));
   }
-
-  return false;
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -206,15 +195,43 @@ RulesConfig::parse_config(const std::string &fname, 
TSHttpHookID default_hook, c
       continue;
     }
 
-    // If we are at the beginning of a new condition, save away the previous 
rule (but only if it has operators).
-    if (p.is_cond() && add_rule(rule.get())) {
-      rule.release();
+    TSHttpHookID hook    = default_hook;
+    bool         is_hook = p.cond_is_hook(hook);
+
+    // Deal with the elif / else special keywords, these are neither 
conditions nor operators.
+    if (p.is_else() || p.is_elif()) {
+      Dbg(pi_dbg_ctl, "Entering elif/else, CondClause=%d", 
static_cast<int>(p.get_clause()));
+      if (rule) {
+        group = rule->new_section(p.get_clause());
+        continue;
+      } else {
+        TSError("[%s] ELSE/ELIF clause without preceding conditions in file: 
%s, lineno: %d", PLUGIN_NAME, fname.c_str(), lineno);
+        return false;
+      }
     }
 
-    TSHttpHookID hook    = default_hook;
-    bool         is_hook = p.cond_is_hook(hook); // This updates the hook if 
explicitly set, if not leaves at default
+    // If we are at the beginning of a new condition, save away the previous 
rule (but only if it has operators).
+    if (p.is_cond() && rule) {
+      bool transfer    = rule->cur_section()->has_operator();
+      auto rule_clause = rule->get_clause();
 
-    if (nullptr == rule) {
+      if (rule_clause == Parser::CondClause::ELIF) {
+        if (is_hook) {
+          TSError("[%s] ELIF without operators are not allowed in file: %s, 
lineno: %d", PLUGIN_NAME, fname.c_str(), lineno);
+          return false;
+        }
+      } else if (rule_clause == Parser::CondClause::ELSE) {
+        if (!transfer) {
+          TSError("[%s] conditions not allowed in ELSE clause in file: %s, 
lineno: %d", PLUGIN_NAME, fname.c_str(), lineno);
+          return false;
+        }
+      }
+      if (transfer) {
+        add_rule(std::move(rule));
+      }
+    }
+
+    if (!rule) {
       if (!group_stack.empty()) {
         TSError("[%s] mismatched %%{GROUP} conditions in file: %s, lineno: 
%d", PLUGIN_NAME, fname.c_str(), lineno);
         return false;
@@ -251,7 +268,7 @@ RulesConfig::parse_config(const std::string &fname, 
TSHttpHookID default_hook, c
         if (!cond) {
           throw std::runtime_error("add_condition() failed");
         } else {
-          ConditionGroup *ngrp = dynamic_cast<ConditionGroup *>(cond);
+          auto *ngrp = dynamic_cast<ConditionGroup *>(cond);
 
           if (ngrp) {
             if (ngrp->closes()) {
@@ -274,9 +291,6 @@ RulesConfig::parse_config(const std::string &fname, 
TSHttpHookID default_hook, c
             group->add_condition(cond);
           }
         }
-      } else if (p.is_else()) {
-        // Switch to the else portion of operators
-        rule->switch_branch();
       } else { // Operator
         if (!rule->add_operator(p, filename.c_str(), lineno)) {
           throw std::runtime_error("add_operator() failed");
@@ -304,12 +318,12 @@ RulesConfig::parse_config(const std::string &fname, 
TSHttpHookID default_hook, c
   }
 
   // Add the last rule (possibly the only rule)
-  if (add_rule(rule.get())) {
-    rule.release();
+  if (rule && rule->has_operator()) {
+    add_rule(std::move(rule));
   }
 
   // Collect all resource IDs that we need
-  for (int i = TS_HTTP_READ_REQUEST_HDR_HOOK; i < TS_HTTP_LAST_HOOK; ++i) {
+  for (size_t i = TS_HTTP_READ_REQUEST_HDR_HOOK; i < TS_HTTP_LAST_HOOK; ++i) {
     if (_rules[i]) {
       _resids[i] = _rules[i]->get_all_resource_ids();
     }
@@ -324,9 +338,9 @@ RulesConfig::parse_config(const std::string &fname, 
TSHttpHookID default_hook, c
 static int
 cont_rewrite_headers(TSCont contp, TSEvent event, void *edata)
 {
-  TSHttpTxn    txnp = static_cast<TSHttpTxn>(edata);
+  auto         txnp = static_cast<TSHttpTxn>(edata);
   TSHttpHookID hook = TS_HTTP_LAST_HOOK;
-  RulesConfig *conf = static_cast<RulesConfig *>(TSContDataGet(contp));
+  auto        *conf = static_cast<RulesConfig *>(TSContDataGet(contp));
 
   switch (event) {
   case TS_EVENT_HTTP_READ_RESPONSE_HDR:
@@ -367,8 +381,8 @@ cont_rewrite_headers(TSCont contp, TSEvent event, void 
*edata)
 
     // Evaluation of all rules. This code is sort of duplicate in DoRemap as 
well.
     while (rule) {
-      const RuleSet::OperatorPair &ops = rule->eval(res);
-      const OperModifiers          rt  = rule->exec(ops, res);
+      const RuleSet::OperatorAndMods &ops = rule->eval(res);
+      const OperModifiers             rt  = rule->exec(ops, res);
 
       if (rt & OPER_NO_REENABLE) {
         reenable = false;
@@ -377,7 +391,7 @@ cont_rewrite_headers(TSCont contp, TSEvent event, void 
*edata)
       if (rule->last() || (rt & OPER_LAST)) {
         break; // Conditional break, force a break with [L]
       }
-      rule = rule->next;
+      rule = rule->next.get();
     }
   }
 
@@ -389,8 +403,8 @@ cont_rewrite_headers(TSCont contp, TSEvent event, void 
*edata)
 }
 
 static const struct option longopt[] = {
-  {"geo-db-path", required_argument, nullptr, 'm' },
-  {nullptr,       no_argument,       nullptr, '\0'}
+  {.name = "geo-db-path", .has_arg = required_argument, .flag = nullptr, .val 
= 'm' },
+  {.name = nullptr,       .has_arg = no_argument,       .flag = nullptr, .val 
= '\0'}
 };
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -424,7 +438,7 @@ TSPluginInit(int argc, const char *argv[])
     }
   }
 
-  if (!geoDBpath.empty() && geoDBpath.find('/') != 0) {
+  if (!geoDBpath.empty() && !geoDBpath.starts_with('/')) {
     geoDBpath = std::string(TSConfigDirGet()) + '/' + geoDBpath;
   }
 
@@ -434,8 +448,8 @@ TSPluginInit(int argc, const char *argv[])
 
   // Parse the global config file(s). All rules are just appended
   // to the "global" Rules configuration.
-  RulesConfig *conf       = new RulesConfig;
-  bool         got_config = false;
+  auto *conf       = new RulesConfig;
+  bool  got_config = false;
 
   for (int i = optind; i < argc; ++i) {
     // Parse the config file(s). Note that multiple config files are
@@ -511,7 +525,7 @@ TSRemapNewInstance(int argc, char *argv[], void **ih, char 
* /* errbuf ATS_UNUSE
   }
 
   if (!geoDBpath.empty()) {
-    if (geoDBpath.find('/') != 0) {
+    if (!geoDBpath.starts_with('/')) {
       geoDBpath = std::string(TSConfigDirGet()) + '/' + geoDBpath;
     }
 
@@ -519,7 +533,7 @@ TSRemapNewInstance(int argc, char *argv[], void **ih, char 
* /* errbuf ATS_UNUSE
     std::call_once(initHRWLibs, [&geoDBpath]() { initHRWLibraries(geoDBpath); 
});
   }
 
-  RulesConfig *conf = new RulesConfig;
+  auto *conf = new RulesConfig;
 
   for (int i = optind; i < argc; ++i) {
     Dbg(pi_dbg_ctl, "Loading remap configuration file %s", argv[i]);
@@ -532,15 +546,6 @@ TSRemapNewInstance(int argc, char *argv[], void **ih, char 
* /* errbuf ATS_UNUSE
     }
   }
 
-  // For debugging only
-  if (pi_dbg_ctl.on()) {
-    for (int i = TS_HTTP_READ_REQUEST_HDR_HOOK; i < TS_HTTP_LAST_HOOK; ++i) {
-      if (conf->rule(i)) {
-        Dbg(pi_dbg_ctl, "Adding remap ruleset to hook=%s", 
TSHttpHookNameLookup(static_cast<TSHttpHookID>(i)));
-      }
-    }
-  }
-
   *ih = static_cast<void *>(conf);
 
   return TS_SUCCESS;
@@ -566,7 +571,7 @@ TSRemapDoRemap(void *ih, TSHttpTxn rh, TSRemapRequestInfo 
*rri)
   }
 
   TSRemapStatus rval = TSREMAP_NO_REMAP;
-  RulesConfig  *conf = static_cast<RulesConfig *>(ih);
+  auto         *conf = static_cast<RulesConfig *>(ih);
 
   // Go through all hooks we support, and setup the txn hook(s) as necessary
   for (int i = TS_HTTP_READ_REQUEST_HDR_HOOK; i < TS_HTTP_LAST_HOOK; ++i) {
@@ -584,8 +589,8 @@ TSRemapDoRemap(void *ih, TSHttpTxn rh, TSRemapRequestInfo 
*rri)
 
   res.gather(RSRC_CLIENT_REQUEST_HEADERS, TS_REMAP_PSEUDO_HOOK);
   while (rule) {
-    const RuleSet::OperatorPair &ops = rule->eval(res);
-    const OperModifiers          rt  = rule->exec(ops, res);
+    const RuleSet::OperatorAndMods &ops = rule->eval(res);
+    const OperModifiers             rt  = rule->exec(ops, res);
 
     ink_assert((rt & OPER_NO_REENABLE) == 0);
 
@@ -597,7 +602,7 @@ TSRemapDoRemap(void *ih, TSHttpTxn rh, TSRemapRequestInfo 
*rri)
       break; // Conditional break, force a break with [L]
     }
 
-    rule = rule->next;
+    rule = rule->next.get();
   }
 
   Dbg(dbg_ctl, "Returning from TSRemapDoRemap with status: %d", rval);
diff --git a/plugins/header_rewrite/parser.cc b/plugins/header_rewrite/parser.cc
index 0cd16aa4ae..006d700680 100644
--- a/plugins/header_rewrite/parser.cc
+++ b/plugins/header_rewrite/parser.cc
@@ -180,19 +180,20 @@ Parser::preprocess(std::vector<std::string> tokens)
 
   // Special case for "conditional" values
   if (tokens[0].substr(0, 2) == "%{") {
-    _cond = true;
+    _clause = CondClause::COND;
   } else if (tokens[0] == "cond") {
-    _cond = true;
+    _clause = CondClause::COND;
     tokens.erase(tokens.begin());
   } else if (tokens[0] == "else") {
-    _cond = false;
-    _else = true;
-
+    _clause = CondClause::ELSE;
+    return true;
+  } else if (tokens[0] == "elif") {
+    _clause = CondClause::ELIF;
     return true;
   }
 
   // Is it a condition or operator?
-  if (_cond) {
+  if (_clause == CondClause::COND) {
     if ((tokens[0].substr(0, 2) == "%{") && (tokens[0][tokens[0].size() - 1] 
== '}')) {
       _op = tokens[0].substr(2, tokens[0].size() - 3);
       if (tokens.size() > 2 && (tokens[1][0] == '=' || tokens[1][0] == '>' || 
tokens[1][0] == '<')) {
@@ -240,7 +241,7 @@ Parser::preprocess(std::vector<std::string> tokens)
 bool
 Parser::cond_is_hook(TSHttpHookID &hook) const
 {
-  if (!_cond) {
+  if (_clause != CondClause::COND) {
     return false;
   }
 
diff --git a/plugins/header_rewrite/parser.h b/plugins/header_rewrite/parser.h
index 2e46648490..9844ad0899 100644
--- a/plugins/header_rewrite/parser.h
+++ b/plugins/header_rewrite/parser.h
@@ -113,6 +113,8 @@ std::optional<ConfReader> openConfig(const std::string 
&filename);
 class Parser
 {
 public:
+  enum class CondClause { OPER, COND, ELIF, ELSE };
+
   Parser() = default; // No from/to URLs for this parser
   Parser(char *from_url, char *to_url) : _from_url(from_url), _to_url(to_url) 
{}
 
@@ -139,16 +141,28 @@ public:
     return _empty;
   }
 
+  CondClause
+  get_clause() const
+  {
+    return _clause;
+  }
+
   bool
   is_cond() const
   {
-    return _cond;
+    return _clause == CondClause::COND;
   }
 
   bool
   is_else() const
   {
-    return _else;
+    return _clause == CondClause::ELSE;
+  }
+
+  bool
+  is_elif() const
+  {
+    return _clause == CondClause::ELIF;
   }
 
   const std::string &
@@ -236,8 +250,7 @@ public:
 private:
   bool preprocess(std::vector<std::string> tokens);
 
-  bool                     _cond     = false;
-  bool                     _else     = false;
+  CondClause               _clause   = CondClause::OPER;
   bool                     _empty    = false;
   char                    *_from_url = nullptr;
   char                    *_to_url   = nullptr;
diff --git a/plugins/header_rewrite/ruleset.cc 
b/plugins/header_rewrite/ruleset.cc
index a5d5709932..50d16ac4c4 100644
--- a/plugins/header_rewrite/ruleset.cc
+++ b/plugins/header_rewrite/ruleset.cc
@@ -28,16 +28,16 @@
 // Class implementation (no reason to have these inline)
 //
 void
-RuleSet::append(RuleSet *rule)
+RuleSet::append(std::unique_ptr<RuleSet> rule)
 {
-  RuleSet *tmp = this;
-
   TSReleaseAssert(rule->next == nullptr);
+  std::unique_ptr<RuleSet> *cur = &next;
 
-  while (tmp->next) {
-    tmp = tmp->next;
+  while (*cur) {
+    cur = &(*cur)->next;
   }
-  tmp->next = rule;
+
+  *cur = std::move(rule);
 }
 
 // This stays here, since the condition, albeit owned by a group, is tightly 
couple to the ruleset.
@@ -50,7 +50,7 @@ RuleSet::make_condition(Parser &p, const char *filename, int 
lineno)
     return nullptr; // Complete failure in the factory
   }
 
-  Dbg(pi_dbg_ctl, "    Adding condition: %%{%s} with arg: %s", 
p.get_op().c_str(), p.get_arg().c_str());
+  Dbg(pi_dbg_ctl, "    Creating condition: %%{%s} with arg: %s", 
p.get_op().c_str(), p.get_arg().c_str());
   c->initialize(p);
   if (!c->set_hook(_hook)) {
     delete c;
@@ -89,10 +89,9 @@ RuleSet::add_operator(Parser &p, const char *filename, int 
lineno)
       return false;
     }
 
-    // Work on the appropriate operator list
-    OperatorPair &ops = _is_else ? _operators[1] : _operators[0];
+    OperatorAndMods &ops = _cur_section->ops;
 
-    if (nullptr == ops.oper) {
+    if (!ops.oper) {
       ops.oper = op;
     } else {
       ops.oper->append(op);
@@ -111,12 +110,12 @@ RuleSet::add_operator(Parser &p, const char *filename, 
int lineno)
 ResourceIDs
 RuleSet::get_all_resource_ids() const
 {
-  ResourceIDs ids = _ids;
-  RuleSet    *tmp = this->next;
+  ResourceIDs                     ids = _ids;
+  const std::unique_ptr<RuleSet> *cur = &next;
 
-  while (tmp) {
-    ids = static_cast<ResourceIDs>(ids | tmp->get_resource_ids());
-    tmp = tmp->next;
+  while (*cur) {
+    ids = static_cast<ResourceIDs>(ids | (*cur)->get_resource_ids());
+    cur = &(*cur)->next;
   }
 
   return ids;
diff --git a/plugins/header_rewrite/ruleset.h b/plugins/header_rewrite/ruleset.h
index db19b406cf..4a5887aab9 100644
--- a/plugins/header_rewrite/ruleset.h
+++ b/plugins/header_rewrite/ruleset.h
@@ -39,32 +39,49 @@ class RuleSet
 {
 public:
   // Holding the IF and ELSE operators and mods, in two separate linked lists.
-  struct OperatorPair {
-    OperatorPair() = default;
+  struct OperatorAndMods {
+    OperatorAndMods() = default;
 
-    OperatorPair(const OperatorPair &)            = delete;
-    OperatorPair &operator=(const OperatorPair &) = delete;
+    OperatorAndMods(const OperatorAndMods &)            = delete;
+    OperatorAndMods &operator=(const OperatorAndMods &) = delete;
 
     Operator     *oper      = nullptr;
     OperModifiers oper_mods = OPER_NONE;
   };
 
+  struct CondOpSection {
+    CondOpSection() = default;
+
+    ~CondOpSection()
+    {
+      delete ops.oper;
+      delete next;
+    }
+
+    CondOpSection(const CondOpSection &)            = delete;
+    CondOpSection &operator=(const CondOpSection &) = delete;
+
+    bool
+    has_operator() const
+    {
+      return ops.oper != nullptr;
+    }
+
+    ConditionGroup  group;
+    OperatorAndMods ops;
+    CondOpSection  *next = nullptr; // For elif / else sections.
+  };
+
   RuleSet() { Dbg(dbg_ctl, "RuleSet CTOR"); }
 
-  ~RuleSet()
-  {
-    Dbg(dbg_ctl, "RulesSet DTOR");
-    delete _operators[0].oper; // These are pointers
-    delete _operators[1].oper;
-    delete next;
-  }
+  ~RuleSet() { Dbg(dbg_ctl, "RulesSet DTOR"); }
 
   // noncopyable
   RuleSet(const RuleSet &)        = delete;
   void operator=(const RuleSet &) = delete;
 
   // No reason to inline these
-  void        append(RuleSet *rule);
+  void        append(std::unique_ptr<RuleSet> rule);
   Condition  *make_condition(Parser &p, const char *filename, int lineno);
   bool        add_operator(Parser &p, const char *filename, int lineno);
   ResourceIDs get_all_resource_ids() const;
@@ -72,7 +89,15 @@ public:
   bool
   has_operator() const
   {
-    return (nullptr != _operators[0].oper) || (nullptr != _operators[1].oper);
+    const CondOpSection *section = &_sections;
+
+    while (section != nullptr) {
+      if (section->has_operator()) {
+        return true;
+      }
+      section = section->next;
+    }
+    return false;
   }
 
   void
@@ -84,7 +109,7 @@ public:
   ConditionGroup *
   get_group()
   {
-    return &_group;
+    return &_cur_section->group;
   }
 
   TSHttpHookID
@@ -93,6 +118,29 @@ public:
     return _hook;
   }
 
+  Parser::CondClause
+  get_clause() const
+  {
+    return _clause;
+  }
+
+  CondOpSection *
+  cur_section()
+  {
+    return _cur_section;
+  }
+
+  ConditionGroup *
+  new_section(Parser::CondClause clause)
+  {
+    TSAssert(_cur_section && !_cur_section->next);
+    _clause            = clause;
+    _cur_section->next = new CondOpSection();
+    _cur_section       = _cur_section->next;
+
+    return &_cur_section->group;
+  }
+
   ResourceIDs
   get_resource_ids() const
   {
@@ -105,20 +153,14 @@ public:
     return _last;
   }
 
-  void
-  switch_branch()
-  {
-    _is_else = !_is_else;
-  }
-
   OperModifiers
-  exec(const OperatorPair &ops, const Resources &res) const
+  exec(const OperatorAndMods &ops, const Resources &res) const
   {
     if (nullptr == ops.oper) {
       return ops.oper_mods;
     }
 
-    auto no_reenable_count{ops.oper->do_exec(res)};
+    auto no_reenable_count = ops.oper->do_exec(res);
 
     ink_assert(no_reenable_count < 2);
     if (no_reenable_count) {
@@ -128,25 +170,32 @@ public:
     return ops.oper_mods;
   }
 
-  const OperatorPair &
+  const RuleSet::OperatorAndMods &
   eval(const Resources &res)
   {
-    if (_group.eval(res)) {
-      return _operators[0]; // IF conditions
-    } else {
-      return _operators[1]; // ELSE conditions
+    for (CondOpSection *sec = &_sections; sec != nullptr; sec = sec->next) {
+      if (sec->group.eval(res)) {
+        return sec->ops;
+      }
     }
+
+    // No matching condition found, return empty operator set.
+    static OperatorAndMods empty_ops;
+    return empty_ops;
   }
 
-  RuleSet *next = nullptr; // Linked list
+  // Linked list of RuleSets
+  std::unique_ptr<RuleSet> next;
 
 private:
-  ConditionGroup _group;        // All conditions are now wrapped in a group
-  OperatorPair   _operators[2]; // Holds both the IF and the ELSE set of 
operators
+  // This holds one condition group, and the ops and optional else_ops, there's
+  // aways at least one of these in the vector (no "elif" sections).
+  CondOpSection  _sections;
+  CondOpSection *_cur_section = &_sections;
 
   // State values (updated when conds / operators are added)
-  TSHttpHookID _hook    = TS_HTTP_READ_RESPONSE_HDR_HOOK; // Which hook is 
this rule for
-  ResourceIDs  _ids     = RSRC_NONE;
-  bool         _last    = false;
-  bool         _is_else = false; // Are we in the else clause of the new rule? 
For parsing.
+  TSHttpHookID       _hook   = TS_HTTP_READ_RESPONSE_HDR_HOOK; // Which hook 
is this rule for
+  ResourceIDs        _ids    = RSRC_NONE;
+  bool               _last   = false;
+  Parser::CondClause _clause = Parser::CondClause::OPER;
 };
diff --git a/tests/gold_tests/pluginTest/header_rewrite/gold/cond-elif.gold 
b/tests/gold_tests/pluginTest/header_rewrite/gold/cond-elif.gold
new file mode 100644
index 0000000000..9ec88fc6e7
--- /dev/null
+++ b/tests/gold_tests/pluginTest/header_rewrite/gold/cond-elif.gold
@@ -0,0 +1,11 @@
+``
+> GET http://www.example.com/from_path/hrw-sets.png``
+> Host: www.example.com``
+> User-Agent: curl/``
+``
+< HTTP/1.1 200 OK
+< Date: ``
+``
+< X-Extension: Yes
+< X-Testing: elif
+``
diff --git 
a/tests/gold_tests/pluginTest/header_rewrite/header_rewrite_url.test.py 
b/tests/gold_tests/pluginTest/header_rewrite/header_rewrite_url.test.py
index e77b5ae2a7..bf594b00d8 100644
--- a/tests/gold_tests/pluginTest/header_rewrite/header_rewrite_url.test.py
+++ b/tests/gold_tests/pluginTest/header_rewrite/header_rewrite_url.test.py
@@ -96,3 +96,14 @@ tr.Processes.Default.ReturnCode = 0
 tr.Processes.Default.Streams.stderr = "gold/ext-sets.gold"
 tr.StillRunningAfter = server
 ts.Disk.traffic_out.Content = "gold/header_rewrite-tag.gold"
+
+# Test HRW elif
+tr = Test.AddTestRun()
+tr.MakeCurlCommand(
+    '--proxy 127.0.0.1:{0} "http://www.example.com/from_path/hrw-sets.png"; '
+    '-H "Proxy-Connection: keep-alive" -H "X-Testing: elif" '
+    '--verbose'.format(ts.Variables.port))
+tr.Processes.Default.ReturnCode = 0
+tr.Processes.Default.Streams.stderr = "gold/cond-elif.gold"
+tr.StillRunningAfter = server
+ts.Disk.traffic_out.Content = "gold/header_rewrite-tag.gold"
diff --git a/tests/gold_tests/pluginTest/header_rewrite/rules/rule_client.conf 
b/tests/gold_tests/pluginTest/header_rewrite/rules/rule_client.conf
index 1796078a53..6d38d81059 100644
--- a/tests/gold_tests/pluginTest/header_rewrite/rules/rule_client.conf
+++ b/tests/gold_tests/pluginTest/header_rewrite/rules/rule_client.conf
@@ -19,7 +19,7 @@ cond %{CLIENT-URL:PATH} /^from_path/
 cond %{CLIENT-URL:SCHEME} =http
 cond %{CLIENT-URL:HOST} =www.example.com
 cond %{CLIENT-URL:QUERY} /foo=bar/
-set-status 304
+  set-status 304
 
 cond %{SEND_RESPONSE_HDR_HOOK}
 cond %{CLIENT-URL:PATH} (png,gif,jpeg) [EXT,NOCASE]
@@ -34,5 +34,8 @@ else
 cond %{SEND_RESPONSE_HDR_HOOK}
 cond %{CLIENT-HEADER:X-Testing} (foo,bar,"foo,bar")
   set-header X-Testing "Yes"
+elif
+  cond %{CLIENT-HEADER:X-Testing} ="elif"
+    set-header X-Testing "elif"
 else
   set-header X-Testing "No"

Reply via email to