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"