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 2fc1735c05 Refactor HRW matchers, add SETS (#12218)
2fc1735c05 is described below
commit 2fc1735c0592030fd5d2825bf57affd16603cd89
Author: Leif Hedstrom <[email protected]>
AuthorDate: Fri May 2 21:51:45 2025 -0600
Refactor HRW matchers, add SETS (#12218)
---
doc/admin-guide/plugins/header_rewrite.en.rst | 10 ++
plugins/header_rewrite/condition.cc | 9 +
plugins/header_rewrite/conditions.cc | 76 ++++----
plugins/header_rewrite/conditions.h | 197 ++++++++++++--------
plugins/header_rewrite/lulu.h | 2 +
plugins/header_rewrite/matcher.cc | 129 +++++++------
plugins/header_rewrite/matcher.h | 200 +++++++++++++--------
plugins/header_rewrite/parser.h | 51 ++++++
.../pluginTest/header_rewrite/gold/ext-sets.gold | 10 ++
.../header_rewrite/gold/header_rewrite-client.gold | 2 +-
.../header_rewrite/header_rewrite_url.test.py | 26 ++-
.../header_rewrite/rules/rule_client.conf | 12 +-
.../header_rewrite/rules/rule_cond_method.conf | 2 +-
13 files changed, 480 insertions(+), 246 deletions(-)
diff --git a/doc/admin-guide/plugins/header_rewrite.en.rst
b/doc/admin-guide/plugins/header_rewrite.en.rst
index c6da5747de..3a4469179c 100644
--- a/doc/admin-guide/plugins/header_rewrite.en.rst
+++ b/doc/admin-guide/plugins/header_rewrite.en.rst
@@ -722,6 +722,9 @@ Operand Description
/regex/ Matches the condition's provided value against the regular
expression. Start the regex with (?i) to flag it for a case
insensitive match, e.g. /(?i)regex/ will match ReGeX.
+(x,y,z) Matches the condition's provided value against the list of
+ comma-separated values. The list may be a list of strings, like
+ ``(mp3,m3u,m3u8)``, or a list of integers, like
``(301,302,307,308)``.
<string Matches if the value from the condition is lexically less than
*string*.
>string Matches if the value from the condition is lexically greater than
@@ -1504,6 +1507,13 @@ already set to some value, and the status code is a 2xx::
cond %{STATUS} <300
set-header Cache-Control "max-age=600, public"
+Add a response header for certain status codes
+----------------------------------------------
+
+ cond %{SEND_RESPONSE_HDR_HOOK} [AND]
+ cond %{STATUS} (301,302,307,308)
+ set-header X-Redirect-Status %{STATUS}
+
Add HSTS
--------
diff --git a/plugins/header_rewrite/condition.cc
b/plugins/header_rewrite/condition.cc
index 5a97d4aa7e..da657c59d9 100644
--- a/plugins/header_rewrite/condition.cc
+++ b/plugins/header_rewrite/condition.cc
@@ -60,6 +60,15 @@ parse_matcher_op(std::string &arg)
} else {
return MATCH_ERROR;
}
+ case '(':
+ arg.erase(0, 1);
+ // There should be a right paren at the end
+ if (arg.length() >= 1 && arg[arg.length() - 1] == ')') {
+ arg.erase(arg.length() - 1, arg.length());
+ return MATCH_SET;
+ } else {
+ return MATCH_ERROR;
+ }
default:
return MATCH_EQUAL;
break;
diff --git a/plugins/header_rewrite/conditions.cc
b/plugins/header_rewrite/conditions.cc
index cae6e911ab..de135608ff 100644
--- a/plugins/header_rewrite/conditions.cc
+++ b/plugins/header_rewrite/conditions.cc
@@ -39,9 +39,15 @@ void
ConditionStatus::initialize(Parser &p)
{
Condition::initialize(p);
- MatcherType *match = new MatcherType(_cond_op);
+ auto *match = new MatcherType(_cond_op);
- match->set(static_cast<TSHttpStatus>(strtol(p.get_arg().c_str(), nullptr,
10)), mods());
+ match->set(p.get_arg(), mods(), [](const std::string &s) -> DataType {
+ auto status = Parser::parseNumeric<DataType>(s);
+ if (status > 999) {
+ throw std::runtime_error("Invalid status code: " + s);
+ }
+ return status;
+ });
_matcher = match;
require_resources(RSRC_SERVER_RESPONSE_HEADERS);
@@ -60,6 +66,7 @@ bool
ConditionStatus::eval(const Resources &res)
{
Dbg(pi_dbg_ctl, "Evaluating STATUS()");
+
return static_cast<MatcherType *>(_matcher)->test(res.resp_status, res);
}
@@ -75,7 +82,7 @@ void
ConditionMethod::initialize(Parser &p)
{
Condition::initialize(p);
- MatcherType *match = new MatcherType(_cond_op);
+ auto *match = new MatcherType(_cond_op);
match->set(p.get_arg(), mods());
_matcher = match;
@@ -117,13 +124,13 @@ ConditionRandom::initialize(Parser &p)
{
struct timeval tv;
Condition::initialize(p);
- MatcherType *match = new MatcherType(_cond_op);
+ auto *match = new MatcherType(_cond_op);
gettimeofday(&tv, nullptr);
_seed = getpid() * tv.tv_usec;
_max = strtol(_qualifier.c_str(), nullptr, 10);
- match->set(static_cast<unsigned int>(strtol(p.get_arg().c_str(), nullptr,
10)), mods());
+ match->set(p.get_arg(), mods(), [](const std::string &s) -> DataType {
return Parser::parseNumeric<DataType>(s); });
_matcher = match;
}
@@ -190,7 +197,7 @@ void
ConditionHeader::initialize(Parser &p)
{
Condition::initialize(p);
- MatcherType *match = new MatcherType(_cond_op);
+ auto *match = new MatcherType(_cond_op);
match->set(p.get_arg(), mods());
_matcher = match;
@@ -255,7 +262,7 @@ ConditionUrl::initialize(Parser &p)
{
Condition::initialize(p);
- MatcherType *match = new MatcherType(_cond_op);
+ auto *match = new MatcherType(_cond_op);
match->set(p.get_arg(), mods());
_matcher = match;
}
@@ -369,7 +376,7 @@ ConditionDBM::initialize(Parser &p)
{
Condition::initialize(p);
- MatcherType *match = new MatcherType(_cond_op);
+ auto *match = new MatcherType(_cond_op);
match->set(p.get_arg(), mods());
_matcher = match;
@@ -433,7 +440,7 @@ ConditionCookie::initialize(Parser &p)
{
Condition::initialize(p);
- MatcherType *match = new MatcherType(_cond_op);
+ auto *match = new MatcherType(_cond_op);
match->set(p.get_arg(), mods());
_matcher = match;
@@ -512,13 +519,13 @@ ConditionIp::initialize(Parser &p)
{
Condition::initialize(p);
- if (_cond_op == MATCH_IP_RANGES) { // Special hack for IP ranges for now ...
+ if (_cond_op == MATCH_IP_RANGES) { // Special hack for IP ranges
MatcherTypeIp *match = new MatcherTypeIp(_cond_op);
- match->set(p.get_arg());
+ match->set(p.get_arg(), mods(), [](const std::string & /*s*/) { return
static_cast<const sockaddr *>(nullptr); });
_matcher = match;
} else {
- MatcherType *match = new MatcherType(_cond_op);
+ auto *match = new MatcherType(_cond_op);
match->set(p.get_arg(), mods());
_matcher = match;
@@ -615,10 +622,9 @@ void
ConditionTransactCount::initialize(Parser &p)
{
Condition::initialize(p);
- MatcherType *match = new MatcherType(_cond_op);
- std::string const &arg = p.get_arg();
+ auto *match = new MatcherType(_cond_op);
- match->set(strtol(arg.c_str(), nullptr, 10), mods());
+ match->set(p.get_arg(), mods(), [](const std::string &s) -> DataType {
return Parser::parseNumeric<DataType>(s); });
_matcher = match;
}
@@ -706,9 +712,9 @@ ConditionNow::initialize(Parser &p)
{
Condition::initialize(p);
- MatcherType *match = new MatcherType(_cond_op);
+ auto *match = new MatcherType(_cond_op);
- match->set(static_cast<int64_t>(strtol(p.get_arg().c_str(), nullptr, 10)),
mods());
+ match->set(p.get_arg(), mods(), [](const std::string &s) -> DataType {
return Parser::parseNumeric<DataType>(s); });
_matcher = match;
}
@@ -776,9 +782,9 @@ ConditionGeo::initialize(Parser &p)
Condition::initialize(p);
if (is_int_type()) {
- Matchers<int64_t> *match = new Matchers<int64_t>(_cond_op);
+ auto *match = new Matchers<int64_t>(_cond_op);
- match->set(static_cast<int64_t>(strtol(p.get_arg().c_str(), nullptr, 10)),
mods());
+ match->set(p.get_arg(), mods(), [](const std::string &s) -> int64_t {
return Parser::parseNumeric<int64_t>(s); });
_matcher = match;
} else {
// The default is to have a string matcher
@@ -854,9 +860,9 @@ ConditionId::initialize(Parser &p)
Condition::initialize(p);
if (_id_qual == ID_QUAL_REQUEST) {
- Matchers<uint64_t> *match = new Matchers<uint64_t>(_cond_op);
+ auto *match = new Matchers<uint64_t>(_cond_op);
- match->set(static_cast<uint64_t>(strtol(p.get_arg().c_str(), nullptr,
10)), mods());
+ match->set(p.get_arg(), mods(), [](const std::string &s) -> uint64_t {
return Parser::parseNumeric<uint64_t>(s); });
_matcher = match;
} else {
// The default is to have a string matcher
@@ -934,7 +940,7 @@ ConditionCidr::initialize(Parser &p)
{
Condition::initialize(p);
- MatcherType *match = new MatcherType(_cond_op);
+ auto *match = new MatcherType(_cond_op);
match->set(p.get_arg(), mods());
_matcher = match;
@@ -1040,10 +1046,10 @@ ConditionInbound::initialize(Parser &p)
if (_cond_op == MATCH_IP_RANGES) { // Special hack for IP ranges for now ...
MatcherTypeIp *match = new MatcherTypeIp(_cond_op);
- match->set(p.get_arg());
+ match->set(p.get_arg(), mods(), [](const std::string & /* s */) { return
static_cast<const sockaddr *>(nullptr); });
_matcher = match;
} else {
- MatcherType *match = new MatcherType(_cond_op);
+ auto *match = new MatcherType(_cond_op);
match->set(p.get_arg(), mods());
_matcher = match;
@@ -1212,10 +1218,9 @@ void
ConditionSessionTransactCount::initialize(Parser &p)
{
Condition::initialize(p);
- MatcherType *match = new MatcherType(_cond_op);
- std::string const &arg = p.get_arg();
+ auto *match = new MatcherType(_cond_op);
- match->set(strtol(arg.c_str(), nullptr, 10), mods());
+ match->set(p.get_arg(), mods(), [](const std::string &s) -> DataType {
return Parser::parseNumeric<DataType>(s); });
_matcher = match;
}
@@ -1246,10 +1251,9 @@ ConditionTcpInfo::initialize(Parser &p)
{
Condition::initialize(p);
Dbg(pi_dbg_ctl, "Initializing TCP Info");
- MatcherType *match = new MatcherType(_cond_op);
- std::string const &arg = p.get_arg();
+ auto *match = new MatcherType(_cond_op);
- match->set(strtol(arg.c_str(), nullptr, 10), mods());
+ match->set(p.get_arg(), mods(), [](const std::string &s) -> DataType {
return Parser::parseNumeric<DataType>(s); });
_matcher = match;
}
@@ -1318,7 +1322,7 @@ void
ConditionCache::initialize(Parser &p)
{
Condition::initialize(p);
- MatcherType *match = new MatcherType(_cond_op);
+ auto *match = new MatcherType(_cond_op);
match->set(p.get_arg(), mods());
_matcher = match;
@@ -1367,7 +1371,7 @@ ConditionNextHop::initialize(Parser &p)
{
Condition::initialize(p);
- MatcherType *match = new MatcherType(_cond_op);
+ auto *match = new MatcherType(_cond_op);
match->set(p.get_arg(), mods());
_matcher = match;
}
@@ -1475,9 +1479,9 @@ void
ConditionStateInt8::initialize(Parser &p)
{
Condition::initialize(p);
- MatcherType *match = new MatcherType(_cond_op);
+ auto *match = new MatcherType(_cond_op);
- match->set(static_cast<uint8_t>(strtol(p.get_arg().c_str(), nullptr, 10)),
mods());
+ match->set(p.get_arg(), mods(), [](const std::string &s) -> DataType {
return Parser::parseNumeric<DataType>(s); });
_matcher = match;
}
@@ -1519,9 +1523,9 @@ void
ConditionStateInt16::initialize(Parser &p)
{
Condition::initialize(p);
- MatcherType *match = new MatcherType(_cond_op);
+ auto *match = new MatcherType(_cond_op);
- match->set(static_cast<uint16_t>(strtol(p.get_arg().c_str(), nullptr, 10)),
mods());
+ match->set(p.get_arg(), mods(), [](const std::string &s) -> DataType {
return Parser::parseNumeric<DataType>(s); });
_matcher = match;
}
diff --git a/plugins/header_rewrite/conditions.h
b/plugins/header_rewrite/conditions.h
index 18912f4c1c..45aa153577 100644
--- a/plugins/header_rewrite/conditions.h
+++ b/plugins/header_rewrite/conditions.h
@@ -90,14 +90,16 @@ protected:
// Check the HTTP return status
class ConditionStatus : public Condition
{
- using MatcherType = Matchers<TSHttpStatus>;
+ using DataType = std::underlying_type_t<TSHttpStatus>;
+ using MatcherType = Matchers<DataType>;
+ using SelfType = ConditionStatus;
public:
ConditionStatus() { Dbg(dbg_ctl, "Calling CTOR for ConditionStatus"); }
// noncopyable
- ConditionStatus(const ConditionStatus &) = delete;
- void operator=(const ConditionStatus &) = delete;
+ ConditionStatus(const SelfType &) = delete;
+ void operator=(const SelfType &) = delete;
void initialize(Parser &p) override;
void append_value(std::string &s, const Resources &res) override;
@@ -110,14 +112,16 @@ protected:
// Check the HTTP method
class ConditionMethod : public Condition
{
- using MatcherType = Matchers<std::string>;
+ using DataType = std::string;
+ using MatcherType = Matchers<DataType>;
+ using SelfType = ConditionMethod;
public:
ConditionMethod() { Dbg(dbg_ctl, "Calling CTOR for ConditionMethod"); }
// noncopyable
- ConditionMethod(const ConditionMethod &) = delete;
- void operator=(const ConditionMethod &) = delete;
+ ConditionMethod(const SelfType &) = delete;
+ void operator=(const SelfType &) = delete;
void initialize(Parser &p) override;
void append_value(std::string &s, const Resources &res) override;
@@ -129,14 +133,16 @@ protected:
// Random 0 to (N-1)
class ConditionRandom : public Condition
{
- using MatcherType = Matchers<unsigned int>;
+ using DataType = unsigned int;
+ using MatcherType = Matchers<DataType>;
+ using SelfType = ConditionRandom;
public:
ConditionRandom() { Dbg(dbg_ctl, "Calling CTOR for ConditionRandom"); }
// noncopyable
- ConditionRandom(const ConditionRandom &) = delete;
- void operator=(const ConditionRandom &) = delete;
+ ConditionRandom(const SelfType &) = delete;
+ void operator=(const SelfType &) = delete;
void initialize(Parser &p) override;
void append_value(std::string &s, const Resources &res) override;
@@ -173,14 +179,16 @@ private:
// cookie(name)
class ConditionCookie : public Condition
{
- using MatcherType = Matchers<std::string>;
+ using DataType = std::string;
+ using MatcherType = Matchers<DataType>;
+ using SelfType = ConditionCookie;
public:
ConditionCookie() { Dbg(dbg_ctl, "Calling CTOR for ConditionCookie"); }
// noncopyable
- ConditionCookie(const ConditionCookie &) = delete;
- void operator=(const ConditionCookie &) = delete;
+ ConditionCookie(const SelfType &) = delete;
+ void operator=(const SelfType &) = delete;
void initialize(Parser &p) override;
void append_value(std::string &s, const Resources &res) override;
@@ -242,7 +250,9 @@ private:
// header
class ConditionHeader : public Condition
{
- using MatcherType = Matchers<std::string>;
+ using DataType = std::string;
+ using MatcherType = Matchers<DataType>;
+ using SelfType = ConditionHeader;
public:
explicit ConditionHeader(bool client = false) : _client(client)
@@ -251,8 +261,8 @@ public:
}
// noncopyable
- ConditionHeader(const ConditionHeader &) = delete;
- void operator=(const ConditionHeader &) = delete;
+ ConditionHeader(const SelfType &) = delete;
+ void operator=(const SelfType &) = delete;
void initialize(Parser &p) override;
void append_value(std::string &s, const Resources &res) override;
@@ -267,7 +277,9 @@ private:
// url
class ConditionUrl : public Condition
{
- using MatcherType = Matchers<std::string>;
+ using DataType = std::string;
+ using MatcherType = Matchers<DataType>;
+ using SelfType = ConditionUrl;
public:
enum UrlType { CLIENT, URL, FROM, TO };
@@ -275,8 +287,8 @@ public:
explicit ConditionUrl(const UrlType type) : _type(type) { Dbg(dbg_ctl,
"Calling CTOR for ConditionUrl"); }
// noncopyable
- ConditionUrl(const ConditionUrl &) = delete;
- void operator=(const ConditionUrl &) = delete;
+ ConditionUrl(const SelfType &) = delete;
+ void operator=(const SelfType &) = delete;
void initialize(Parser &p) override;
void set_qualifier(const std::string &q) override;
@@ -293,7 +305,9 @@ private:
// DBM lookups
class ConditionDBM : public Condition
{
- using MatcherType = Matchers<std::string>;
+ using DataType = std::string;
+ using MatcherType = Matchers<DataType>;
+ using SelfType = ConditionDBM;
public:
ConditionDBM()
@@ -313,8 +327,8 @@ public:
}
// noncopyable
- ConditionDBM(const ConditionDBM &) = delete;
- void operator=(const ConditionDBM &) = delete;
+ ConditionDBM(const SelfType &) = delete;
+ void operator=(const SelfType &) = delete;
void initialize(Parser &p) override;
void append_value(std::string &s, const Resources &res) override;
@@ -331,7 +345,9 @@ private:
class ConditionInternalTxn : public Condition
{
- using MatcherType = Matchers<std::string>;
+ using DataType = std::string;
+ using MatcherType = Matchers<DataType>;
+ using SelfType = ConditionInternalTxn;
public:
void
@@ -345,15 +361,17 @@ protected:
class ConditionIp : public Condition
{
- using MatcherType = Matchers<std::string>;
+ using DataType = std::string;
+ using MatcherType = Matchers<DataType>;
using MatcherTypeIp = Matchers<const sockaddr *>;
+ using SelfType = ConditionIp;
public:
explicit ConditionIp() { Dbg(dbg_ctl, "Calling CTOR for ConditionIp"); };
// noncopyable
- ConditionIp(const ConditionIp &) = delete;
- void operator=(const ConditionIp &) = delete;
+ ConditionIp(const SelfType &) = delete;
+ void operator=(const SelfType &) = delete;
void initialize(Parser &p) override;
void set_qualifier(const std::string &q) override;
@@ -369,14 +387,16 @@ private:
// Transact Count
class ConditionTransactCount : public Condition
{
- using MatcherType = Matchers<int>;
+ using DataType = int;
+ using MatcherType = Matchers<DataType>;
+ using SelfType = ConditionTransactCount;
public:
ConditionTransactCount() { Dbg(dbg_ctl, "Calling CTOR for
ConditionTransactCount"); }
// noncopyable
- ConditionTransactCount(const ConditionTransactCount &) = delete;
- void operator=(const ConditionTransactCount &) = delete;
+ ConditionTransactCount(const SelfType &) = delete;
+ void operator=(const SelfType &) = delete;
void initialize(Parser &p) override;
void append_value(std::string &s, const Resources &res) override;
@@ -388,14 +408,16 @@ protected:
// now: Keeping track of current time / day / hour etc.
class ConditionNow : public Condition
{
- using MatcherType = Matchers<int64_t>;
+ using DataType = int64_t;
+ using MatcherType = Matchers<DataType>;
+ using SelfType = ConditionNow;
public:
explicit ConditionNow() { Dbg(dbg_ctl, "Calling CTOR for ConditionNow"); }
// noncopyable
- ConditionNow(const ConditionNow &) = delete;
- void operator=(const ConditionNow &) = delete;
+ ConditionNow(const SelfType &) = delete;
+ void operator=(const SelfType &) = delete;
void initialize(Parser &p) override;
void set_qualifier(const std::string &q) override;
@@ -412,12 +434,15 @@ private:
// GeoIP class for the "integer" based Geo information pieces
class ConditionGeo : public Condition
{
+ using SelfType = ConditionGeo;
+ // This has multiple "data types" ...
+
public:
explicit ConditionGeo() { Dbg(dbg_ctl, "Calling CTOR for ConditionGeo"); }
// noncopyable
- ConditionGeo(const ConditionGeo &) = delete;
- void operator=(const ConditionGeo &) = delete;
+ ConditionGeo(const SelfType &) = delete;
+ void operator=(const SelfType &) = delete;
void initialize(Parser &p) override;
void set_qualifier(const std::string &q) override;
@@ -449,12 +474,15 @@ protected:
// id: Various identifiers for the requests, server process etc.
class ConditionId : public Condition
{
+ using SelfType = ConditionId;
+ // This has multiple "data types" for matching
+
public:
explicit ConditionId() { Dbg(dbg_ctl, "Calling CTOR for ConditionId"); };
// noncopyable
- ConditionId(const ConditionId &) = delete;
- void operator=(const ConditionId &) = delete;
+ ConditionId(const SelfType &) = delete;
+ void operator=(const SelfType &) = delete;
void initialize(Parser &p) override;
void set_qualifier(const std::string &q) override;
@@ -470,8 +498,9 @@ private:
// cidr: A CIDR masked string representation of the Client's IP.
class ConditionCidr : public Condition
{
- using MatcherType = Matchers<std::string>;
- using self = ConditionCidr;
+ using DataType = std::string;
+ using MatcherType = Matchers<DataType>;
+ using SelfType = ConditionCidr;
public:
explicit ConditionCidr()
@@ -480,8 +509,8 @@ public:
Dbg(dbg_ctl, "Calling CTOR for ConditionCidr");
};
- ConditionCidr(self &) = delete;
- self &operator=(self &) = delete;
+ ConditionCidr(SelfType &) = delete;
+ SelfType &operator=(SelfType &) = delete;
void initialize(Parser &p) override;
void set_qualifier(const std::string &q) override;
@@ -502,14 +531,15 @@ private:
/// Information about the inbound (client) session.
class ConditionInbound : public Condition
{
+ using DataType = const sockaddr *;
using MatcherType = Matchers<std::string>;
- using MatcherTypeIp = Matchers<const sockaddr *>;
- using self = ConditionInbound;
+ using MatcherTypeIp = Matchers<DataType>;
+ using SelfType = ConditionInbound;
public:
explicit ConditionInbound() { Dbg(dbg_ctl, "Calling CTOR for
ConditionInbound"); };
- ConditionInbound(self &) = delete;
- self &operator=(self &) = delete;
+ ConditionInbound(SelfType &) = delete;
+ SelfType &operator=(SelfType &) = delete;
void initialize(Parser &p) override;
void set_qualifier(const std::string &q) override;
@@ -527,14 +557,16 @@ private:
class ConditionStringLiteral : public Condition
{
- using MatcherType = Matchers<std::string>;
+ using DataType = std::string;
+ using MatcherType = Matchers<DataType>;
+ using SelfType = ConditionStringLiteral;
public:
explicit ConditionStringLiteral(const std::string &v);
// noncopyable
- ConditionStringLiteral(const ConditionStringLiteral &) = delete;
- void operator=(const ConditionStringLiteral &) = delete;
+ ConditionStringLiteral(const SelfType &) = delete;
+ void operator=(const SelfType &) = delete;
void append_value(std::string &s, const Resources & /* res ATS_UNUSED */)
override;
@@ -548,14 +580,16 @@ private:
// Single Session Transaction Count
class ConditionSessionTransactCount : public Condition
{
- using MatcherType = Matchers<int>;
+ using DataType = int;
+ using MatcherType = Matchers<DataType>;
+ using SelfType = ConditionSessionTransactCount;
public:
ConditionSessionTransactCount() { Dbg(dbg_ctl,
"ConditionSessionTransactCount()"); }
// noncopyable
- ConditionSessionTransactCount(const ConditionSessionTransactCount &) =
delete;
- void operator=(const ConditionSessionTransactCount &) =
delete;
+ ConditionSessionTransactCount(const SelfType &) = delete;
+ void operator=(const SelfType &) = delete;
void initialize(Parser &p) override;
void append_value(std::string &s, const Resources &res) override;
@@ -567,14 +601,16 @@ protected:
// Tcp Info
class ConditionTcpInfo : public Condition
{
- using MatcherType = Matchers<int>;
+ using DataType = int;
+ using MatcherType = Matchers<DataType>;
+ using SelfType = ConditionTcpInfo;
public:
ConditionTcpInfo() { Dbg(dbg_ctl, "Calling CTOR for ConditionTcpInfo"); }
// noncopyable
- ConditionTcpInfo(const ConditionTcpInfo &) = delete;
- void operator=(const ConditionTcpInfo &) = delete;
+ ConditionTcpInfo(const SelfType &) = delete;
+ void operator=(const SelfType &) = delete;
void initialize(Parser &p) override;
void append_value(std::string &s, const Resources &res) override;
@@ -587,14 +623,16 @@ protected:
// Cache Lookup Results
class ConditionCache : public Condition
{
+ using DataType = std::string;
using MatcherType = Matchers<std::string>;
+ using SelfType = ConditionCache;
public:
ConditionCache() { Dbg(dbg_ctl, "Calling CTOR for ConditionCache"); }
// noncopyable
- ConditionCache(const ConditionCache &) = delete;
- void operator=(const ConditionCache &) = delete;
+ ConditionCache(const SelfType &) = delete;
+ void operator=(const SelfType &) = delete;
void initialize(Parser &p) override;
void append_value(std::string &s, const Resources &res) override;
@@ -606,7 +644,9 @@ protected:
// Next Hop
class ConditionNextHop : public Condition
{
- using MatcherType = Matchers<std::string>;
+ using DataType = std::string;
+ using MatcherType = Matchers<DataType>;
+ using SelfType = ConditionNextHop;
public:
enum HostType { NAME, PORT };
@@ -614,8 +654,8 @@ public:
explicit ConditionNextHop() { Dbg(dbg_ctl, "Calling CTOR for
ConditionNextHop"); }
// noncopyable
- ConditionNextHop(const ConditionNextHop &) = delete;
- void operator=(const ConditionNextHop &) = delete;
+ ConditionNextHop(const SelfType &) = delete;
+ void operator=(const SelfType &) = delete;
void initialize(Parser &p) override;
void set_qualifier(const std::string &q) override;
@@ -631,12 +671,14 @@ private:
// HTTP CNTL
class ConditionHttpCntl : public Condition
{
+ using SelfType = ConditionHttpCntl;
+
public:
explicit ConditionHttpCntl() { Dbg(dbg_ctl, "Calling CTOR for
ConditionHttpCntl"); }
// noncopyable
- ConditionHttpCntl(const ConditionHttpCntl &) = delete;
- void operator=(const ConditionHttpCntl &) = delete;
+ ConditionHttpCntl(const SelfType &) = delete;
+ void operator=(const SelfType &) = delete;
void set_qualifier(const std::string &q) override;
void append_value(std::string &s, const Resources &res) override;
@@ -650,6 +692,8 @@ private:
class ConditionGroup : public Condition
{
+ using SelfType = ConditionGroup;
+
public:
ConditionGroup() { Dbg(dbg_ctl, "Calling CTOR for ConditionGroup"); }
@@ -670,8 +714,8 @@ public:
}
// noncopyable
- ConditionGroup(const ConditionGroup &) = delete;
- void operator=(const ConditionGroup &) = delete;
+ ConditionGroup(const SelfType &) = delete;
+ void operator=(const SelfType &) = delete;
bool
closes() const
@@ -717,6 +761,9 @@ private:
// State Flags
class ConditionStateFlag : public Condition
{
+ using SelfType = ConditionStateFlag;
+ // No matcher for this, it's all easy peasy
+
public:
explicit ConditionStateFlag()
{
@@ -725,8 +772,8 @@ public:
}
// noncopyable
- ConditionStateFlag(const ConditionStateFlag &) = delete;
- void operator=(const ConditionStateFlag &) = delete;
+ ConditionStateFlag(const SelfType &) = delete;
+ void operator=(const SelfType &) = delete;
void set_qualifier(const std::string &q) override;
void append_value(std::string &s, const Resources &res) override;
@@ -748,7 +795,9 @@ private:
// INT8 state variables
class ConditionStateInt8 : public Condition
{
- using MatcherType = Matchers<uint8_t>;
+ using DataType = uint8_t;
+ using MatcherType = Matchers<DataType>;
+ using SelfType = ConditionStateInt8;
public:
explicit ConditionStateInt8()
@@ -758,8 +807,8 @@ public:
}
// noncopyable
- ConditionStateInt8(const ConditionStateInt8 &) = delete;
- void operator=(const ConditionStateInt8 &) = delete;
+ ConditionStateInt8(const SelfType &) = delete;
+ void operator=(const SelfType &) = delete;
void initialize(Parser &p) override;
void set_qualifier(const std::string &q) override;
@@ -792,7 +841,9 @@ private:
// INT16 state variables
class ConditionStateInt16 : public Condition
{
- using MatcherType = Matchers<uint16_t>;
+ using DataType = uint16_t;
+ using MatcherType = Matchers<DataType>;
+ using SelfType = ConditionStateInt16;
public:
explicit ConditionStateInt16()
@@ -802,8 +853,8 @@ public:
}
// noncopyable
- ConditionStateInt16(const ConditionStateInt8 &) = delete;
- void operator=(const ConditionStateInt8 &) = delete;
+ ConditionStateInt16(const SelfType &) = delete;
+ void operator=(const SelfType &) = delete;
void initialize(Parser &p) override;
void set_qualifier(const std::string &q) override;
@@ -832,14 +883,16 @@ private:
// Last regex capture
class ConditionLastCapture : public Condition
{
- using MatcherType = Matchers<std::string>;
+ using DataType = std::string;
+ using MatcherType = Matchers<DataType>;
+ using SelfType = ConditionLastCapture;
public:
explicit ConditionLastCapture() { Dbg(dbg_ctl, "Calling CTOR for
ConditionLastCapture"); }
// noncopyable
- ConditionLastCapture(const ConditionLastCapture &) = delete;
- void operator=(const ConditionLastCapture &) = delete;
+ ConditionLastCapture(const SelfType &) = delete;
+ void operator=(const SelfType &) = delete;
void set_qualifier(const std::string &q) override;
void append_value(std::string &s, const Resources &res) override;
diff --git a/plugins/header_rewrite/lulu.h b/plugins/header_rewrite/lulu.h
index 70db9bde8e..9058d11552 100644
--- a/plugins/header_rewrite/lulu.h
+++ b/plugins/header_rewrite/lulu.h
@@ -36,6 +36,8 @@ std::string getIP(sockaddr const *s_sockaddr);
char *getIP(sockaddr const *s_sockaddr, char res[INET6_ADDRSTRLEN]);
uint16_t getPort(sockaddr const *s_sockaddr);
+template <typename T> constexpr bool ALWAYS_FALSE_V = false;
+
namespace header_rewrite_ns
{
extern const char PLUGIN_NAME[];
diff --git a/plugins/header_rewrite/matcher.cc
b/plugins/header_rewrite/matcher.cc
index e7a267e5a5..6d7909f965 100644
--- a/plugins/header_rewrite/matcher.cc
+++ b/plugins/header_rewrite/matcher.cc
@@ -26,43 +26,12 @@
#include "matcher.h"
-// Special case for strings, to make the distinction between regexes and
string matching
-template <>
-void
-Matchers<std::string>::set(const std::string &d, CondModifiers mods)
-{
- _data = d;
- _mods = mods;
-
- if (_op == MATCH_REGULAR_EXPRESSION) {
- if (!_reHelper.setRegexMatch(_data, has_modifier(_mods,
CondModifiers::MOD_NOCASE))) {
- std::stringstream ss;
-
- ss << _data;
- TSError("[%s] Invalid regex: failed to precompile: %s", PLUGIN_NAME,
ss.str().c_str());
- Dbg(pi_dbg_ctl, "Invalid regex: failed to precompile: %s",
ss.str().c_str());
- throw std::runtime_error("Malformed regex");
- } else {
- Dbg(pi_dbg_ctl, "Regex precompiled successfully");
- }
- }
-}
-
-template <>
-bool
-Matchers<std::string>::test_eq(const std::string &t) const
+static bool
+match_with_modifiers(std::string_view rhs, std::string_view lhs, CondModifiers
mods)
{
- std::string_view lhs = _data;
- std::string_view rhs = t;
- bool result = false;
-
- // ToDo: in C++20, we should be able to use std::ranges::equal, but this
breaks on Ubuntu CI
- // return std::ranges::equal(a, b, [](char c1, char c2) {
- // return std::tolower(static_cast<unsigned char>(c1)) ==
std::tolower(static_cast<unsigned char>(c2));
- // });
- // Case-aware comparison
- auto compare = [&](const std::string_view a, const std::string_view b) ->
bool {
- if (has_modifier(_mods, CondModifiers::MOD_NOCASE)) {
+ // Case-aware equality
+ static auto equals = [](std::string_view a, std::string_view b,
CondModifiers mods) -> bool {
+ if (has_modifier(mods, CondModifiers::MOD_NOCASE)) {
return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin(),
[](char c1, char c2) {
return std::tolower(static_cast<unsigned char>(c1)) ==
std::tolower(static_cast<unsigned char>(c2));
});
@@ -70,9 +39,9 @@ Matchers<std::string>::test_eq(const std::string &t) const
return a == b;
};
- // Case-aware substring match
- auto contains = [&](const std::string_view haystack, const std::string_view
&needle) -> bool {
- if (!has_modifier(_mods, CondModifiers::MOD_NOCASE)) {
+ // Case-aware substring search
+ static auto contains = [](std::string_view haystack, std::string_view
needle, CondModifiers mods) -> bool {
+ if (!has_modifier(mods, CondModifiers::MOD_NOCASE)) {
return haystack.find(needle) != std::string_view::npos;
}
auto it = std::search(haystack.begin(), haystack.end(), needle.begin(),
needle.end(), [](char c1, char c2) {
@@ -81,30 +50,76 @@ Matchers<std::string>::test_eq(const std::string &t) const
return it != haystack.end();
};
- if (has_modifier(_mods, CondModifiers::MOD_EXT)) {
+ if (has_modifier(mods, CondModifiers::MOD_EXT)) {
auto dot = rhs.rfind('.');
- if (dot != std::string_view::npos && dot + 1 < rhs.size()) {
- result = compare(rhs.substr(dot + 1), lhs);
- }
- } else if (has_modifier(_mods, CondModifiers::MOD_SUF)) {
- if (rhs.size() >= lhs.size()) {
- result = compare(rhs.substr(rhs.size() - lhs.size()), lhs);
- }
- } else if (has_modifier(_mods, CondModifiers::MOD_PRE)) {
- if (rhs.size() >= lhs.size()) {
- result = compare(rhs.substr(0, lhs.size()), lhs);
- }
- } else if (has_modifier(_mods, CondModifiers::MOD_MID)) {
- result = contains(rhs, lhs);
- } else {
- if (rhs.size() == lhs.size()) {
- result = compare(rhs, lhs);
- }
+ return dot != std::string_view::npos && dot + 1 < rhs.size() &&
equals(rhs.substr(dot + 1), lhs, mods);
+ }
+
+ if (has_modifier(mods, CondModifiers::MOD_SUF)) {
+ return rhs.size() >= lhs.size() && equals(rhs.substr(rhs.size() -
lhs.size()), lhs, mods);
+ }
+
+ if (has_modifier(mods, CondModifiers::MOD_PRE)) {
+ return rhs.size() >= lhs.size() && equals(rhs.substr(0, lhs.size()), lhs,
mods);
}
+ if (has_modifier(mods, CondModifiers::MOD_MID)) {
+ return contains(rhs, lhs, mods);
+ }
+
+ return equals(rhs, lhs, mods);
+}
+
+// Special case for strings, to allow for insensitive case comparisons for
std::string matchers.
+template <>
+bool
+Matchers<std::string>::test_eq(const std::string &t) const
+{
+ std::string_view lhs = std::get<std::string>(_data);
+ std::string_view rhs = t;
+ bool result = match_with_modifiers(rhs, lhs, _mods);
+
if (pi_dbg_ctl.on()) {
debug_helper(t, " == ", result);
}
return result;
}
+
+template <>
+bool
+Matchers<std::string>::test_set(const std::string &t) const
+{
+ TSAssert(std::holds_alternative<std::set<std::string>>(_data));
+ std::string_view rhs = t;
+
+ for (const auto &entry : std::get<std::set<std::string>>(_data)) {
+ if (match_with_modifiers(rhs, entry, _mods)) {
+ if (pi_dbg_ctl.on()) {
+ debug_helper(t, " ∈ ", true);
+ return true;
+ }
+ }
+ }
+
+ debug_helper(t, " ∈ ", false);
+ return false;
+}
+
+template <>
+bool
+Matchers<const sockaddr *>::test(const sockaddr *const &addr, const Resources
& /* Not used */) const
+{
+ TSAssert(std::holds_alternative<swoc::IPRangeSet>(_data));
+ const auto &ranges = std::get<swoc::IPRangeSet>(_data);
+
+ if (ranges.contains(swoc::IPAddr(addr))) {
+ if (pi_dbg_ctl.on()) {
+ char text[INET6_ADDRSTRLEN];
+ Dbg(pi_dbg_ctl, "Successfully found IP-range match on %s", getIP(addr,
text));
+ }
+ return true;
+ }
+
+ return false;
+}
diff --git a/plugins/header_rewrite/matcher.h b/plugins/header_rewrite/matcher.h
index f085985c17..9f1a36a15b 100644
--- a/plugins/header_rewrite/matcher.h
+++ b/plugins/header_rewrite/matcher.h
@@ -25,6 +25,8 @@
#include <sstream>
#include <stdexcept>
#include <type_traits>
+#include <variant>
+#include <set>
#include "swoc/swoc_ip.h"
@@ -41,6 +43,7 @@ enum MatcherOps {
MATCH_GREATER_THEN,
MATCH_REGULAR_EXPRESSION,
MATCH_IP_RANGES,
+ MATCH_SET,
MATCH_ERROR,
};
@@ -119,18 +122,97 @@ template <class T> class Matchers : public Matcher
{
public:
explicit Matchers(const MatcherOps op) : Matcher(op), _data() {}
- // Getters / setters
- const T &
- get() const
+
+ void
+ set(const T &d, CondModifiers mods)
{
- return _data;
+ _mods = mods;
+ if constexpr (std::is_same_v<T, std::string>) {
+ set(d, mods, [](const std::string &in) { return in; });
+ } else {
+ std::get<T>(_data) = d;
+ }
}
+ template <typename FN>
void
- set(const T &d, CondModifiers mods)
+ set(const std::string &s, CondModifiers mods, FN convert)
{
- _data = d;
+ static_assert(std::is_same_v<decltype(convert(s)), T>, "Converter must
return a value of type T");
_mods = mods;
+
+ // MATCH_REGULAR_EXPRESSION (only valid for std::string)
+ if constexpr (std::is_same_v<T, std::string>) {
+ if (_op == MATCH_REGULAR_EXPRESSION) {
+ _data.template emplace<regexHelper>();
+
+ auto &re = std::get<regexHelper>(_data);
+
+ if (!re.setRegexMatch(s, has_modifier(mods,
CondModifiers::MOD_NOCASE))) {
+ TSError("[%s] Invalid regex: failed to precompile: %s", PLUGIN_NAME,
s.c_str());
+ Dbg(pi_dbg_ctl, "Invalid regex: failed to precompile: %s",
s.c_str());
+ throw std::runtime_error("Malformed regex");
+ }
+
+ Dbg(pi_dbg_ctl, "Regex precompiled successfully");
+ return;
+ }
+ }
+
+ // MATCH_IP_RANGES (only valid for const sockaddr *)
+ if constexpr (std::is_same_v<T, const sockaddr *>) {
+ if (_op == MATCH_IP_RANGES) {
+ _data.template emplace<swoc::IPRangeSet>();
+
+ auto &ranges = std::get<swoc::IPRangeSet>(_data);
+ std::istringstream stream(s);
+ std::string part;
+ size_t count = 0;
+
+ while (std::getline(stream, part, ',')) {
+ swoc::IPRange r;
+
+ if (r.load(part)) {
+ ranges.mark(r);
+ ++count;
+ }
+ }
+
+ if (count > 0) {
+ Dbg(pi_dbg_ctl, "IP-range precompiled successfully with %zu
entries", count);
+ } else {
+ TSError("[%s] Invalid IP-range: failed to parse: %s", PLUGIN_NAME,
s.c_str());
+ Dbg(pi_dbg_ctl, "Invalid IP-range: failed to parse: %s", s.c_str());
+ throw std::runtime_error("Malformed IP-range");
+ }
+ return;
+ } else {
+ TSReleaseAssert(false); // This should never happen
+ }
+ }
+
+ // MATCH_SET (allowed for any T)
+ if (_op == MATCH_SET) {
+ _data.template emplace<std::set<T>>();
+
+ auto &values = std::get<std::set<T>>(_data);
+ std::istringstream stream(s);
+ std::string part;
+
+ while (std::getline(stream, part, ',')) {
+ values.insert(convert(part));
+ }
+
+ if (!values.empty()) {
+ Dbg(pi_dbg_ctl, " Added %zu set values while parsing",
values.size());
+ } else {
+ Dbg(pi_dbg_ctl, " No set values added, possibly bad input");
+ throw std::runtime_error("Empty sets not allowed");
+ }
+ } else {
+ // Default: single value
+ _data.template emplace<T>(convert(s));
+ }
}
// Evaluate this matcher
@@ -150,6 +232,9 @@ public:
case MATCH_REGULAR_EXPRESSION:
return test_reg(t, res); // Only the regex matcher needs the resource
break;
+ case MATCH_SET:
+ return test_set(t);
+ break;
case MATCH_IP_RANGES:
// This is an error, the Matcher doesn't make sense to match on IP ranges
TSError("[%s] Invalid matcher: MATCH_IP_RANGES", PLUGIN_NAME);
@@ -168,7 +253,20 @@ private:
{
std::stringstream ss;
- ss << '"' << t << '"' << op << '"' << _data << '"' << " -> " << r;
+ std::visit(
+ [&](const auto &val) {
+ using V = std::decay_t<decltype(val)>;
+ if constexpr (std::is_same_v<V, T>) {
+ ss << '"' << t << '"' << op << '"' << val << '"';
+ } else if constexpr (std::is_same_v<V, std::set<T>>) {
+ ss << '"' << t << '"' << op << " set[" << val.size() << " entries]";
+ } else {
+ ss << '"' << t << '"' << op << " type<" << typeid(V).name() << ">";
+ }
+ },
+ _data);
+
+ ss << " -> " << r;
Dbg(pi_dbg_ctl, "\ttesting: %s", ss.str().c_str());
}
@@ -176,7 +274,8 @@ private:
bool
test_eq(const T &t) const
{
- bool r = (t == _data);
+ TSAssert(std::holds_alternative<T>(_data));
+ bool r = (t == std::get<T>(_data));
if (pi_dbg_ctl.on()) {
debug_helper(t, " == ", r);
@@ -188,7 +287,8 @@ private:
bool
test_lt(const T &t) const
{
- bool r = (t < _data);
+ TSAssert(std::holds_alternative<T>(_data));
+ bool r = (t < std::get<T>(_data));
if (pi_dbg_ctl.on()) {
debug_helper(t, " < ", r);
@@ -200,7 +300,8 @@ private:
bool
test_gt(const T &t) const
{
- bool r = t > _data;
+ TSAssert(std::holds_alternative<T>(_data));
+ bool r = (t > std::get<T>(_data));
if (pi_dbg_ctl.on()) {
debug_helper(t, " > ", r);
@@ -209,6 +310,13 @@ private:
return r;
}
+ bool
+ test_set(const T &c) const
+ {
+ TSAssert(std::holds_alternative<std::set<T>>(_data));
+ return std::get<std::set<T>>(_data).contains(c);
+ }
+
bool
test_reg(const unsigned int /* t ATS_UNUSED */, const Resources & /* Not
used */) const
{
@@ -217,7 +325,7 @@ private:
}
bool
- test_reg(const TSHttpStatus /* t ATS_UNUSED */, const Resources & /* Not
used */) const
+ test_reg(const sockaddr * /* t ATS_UNUSED */, const Resources & /* Not used
*/) const
{
// Not supported
return false;
@@ -226,9 +334,11 @@ private:
bool
test_reg(const std::string &t, const Resources &res) const
{
- Dbg(pi_dbg_ctl, "Test regular expression %s : %s (NOCASE = %s)",
_data.c_str(), t.c_str(),
+ TSAssert(std::holds_alternative<regexHelper>(_data));
+ Dbg(pi_dbg_ctl, "Test regular expression against: %s (NOCASE = %s)",
t.c_str(),
has_modifier(_mods, CondModifiers::MOD_NOCASE) ? "true" : "false");
- int count = _reHelper.regexMatch(t.c_str(), t.length(),
const_cast<Resources &>(res).ovector);
+ const auto &re = std::get<regexHelper>(_data);
+ int count = re.regexMatch(t.c_str(), t.length(),
const_cast<Resources &>(res).ovector);
if (count > 0) {
Dbg(pi_dbg_ctl, "Successfully found regular expression match");
@@ -241,66 +351,6 @@ private:
return false;
}
- T _data;
- regexHelper _reHelper;
- CondModifiers _mods = CondModifiers::NONE;
-};
-
-// Specializations for the strings, since they can be both strings and regexes
-template <> void Matchers<std::string>::set(const std::string &d,
CondModifiers mods);
-template <> bool Matchers<std::string>::test_eq(const std::string &t) const;
-
-// Specialized case matcher for the IP addresses matches.
-template <> class Matchers<const sockaddr *> : public Matcher
-{
-public:
- explicit Matchers(const MatcherOps op) : Matcher(op) {}
-
- void
- set(const std::string &data)
- {
- if (!extract_ranges(data)) {
- TSError("[%s] Invalid IP-range: failed to parse: %s", PLUGIN_NAME,
data.c_str());
- Dbg(pi_dbg_ctl, "Invalid IP-range: failed to parse: %s", data.c_str());
- throw std::runtime_error("Malformed IP-range");
- } else {
- Dbg(pi_dbg_ctl, "IP-range precompiled successfully");
- }
- }
-
- bool
- test(const sockaddr *addr, const Resources & /* Not used */) const
- {
- if (_ipHelper.contains(swoc::IPAddr(addr))) {
- if (pi_dbg_ctl.on()) {
- char text[INET6_ADDRSTRLEN];
-
- Dbg(pi_dbg_ctl, "Successfully found IP-range match on %s", getIP(addr,
text));
- }
- return true;
- }
-
- return false;
- }
-
-private:
- bool
- extract_ranges(swoc::TextView text)
- {
- while (text) {
- if (swoc::IPRange r; r.load(text.take_prefix_at(','))) {
- _ipHelper.mark(r);
- }
- }
-
- if (_ipHelper.count() > 0) {
- Dbg(pi_dbg_ctl, " Added %zu IP ranges while parsing",
_ipHelper.count());
- return true;
- } else {
- Dbg(pi_dbg_ctl, " No IP ranges added, possibly bad input");
- return false;
- }
- }
-
- swoc::IPRangeSet _ipHelper;
+ std::variant<T, std::set<T>, swoc::IPRangeSet, regexHelper> _data;
+ CondModifiers _mods =
CondModifiers::NONE;
};
diff --git a/plugins/header_rewrite/parser.h b/plugins/header_rewrite/parser.h
index 17777d3999..35f056936d 100644
--- a/plugins/header_rewrite/parser.h
+++ b/plugins/header_rewrite/parser.h
@@ -24,6 +24,10 @@
#include <string>
#include <vector>
#include <algorithm>
+#include <type_traits>
+#include <charconv>
+#include <optional>
+#include <limits>
#include "ts/ts.h"
#include "lulu.h"
@@ -105,6 +109,53 @@ public:
bool parse_line(const std::string &original_line);
+ template <typename NumericT>
+ static NumericT
+ parseNumeric(const std::string &s)
+ {
+ if (s.size() == 0) {
+ return 0; // For the case where we have conditions that are "values".
+ }
+
+ try {
+ if constexpr (std::is_same_v<NumericT, int>) {
+ return std::stoi(s);
+ } else if constexpr (std::is_same_v<NumericT, long>) {
+ return std::stol(s);
+ } else if constexpr (std::is_same_v<NumericT, long long>) {
+ return std::stoll(s);
+ } else if constexpr (std::is_same_v<NumericT, int8_t> ||
std::is_same_v<NumericT, int16_t> ||
+ std::is_same_v<NumericT, int32_t> ||
std::is_same_v<NumericT, int64_t>) {
+ long long val = std::stoll(s);
+ if (val < std::numeric_limits<NumericT>::min() || val >
std::numeric_limits<NumericT>::max()) {
+ throw std::out_of_range("Value out of range for signed type");
+ }
+ return static_cast<NumericT>(val);
+ } else if constexpr (std::is_same_v<NumericT, unsigned long>) {
+ return std::stoul(s);
+ } else if constexpr (std::is_same_v<NumericT, unsigned long long>) {
+ return std::stoull(s);
+ } else if constexpr (std::is_same_v<NumericT, uint8_t> ||
std::is_same_v<NumericT, uint16_t> ||
+ std::is_same_v<NumericT, uint32_t> ||
std::is_same_v<NumericT, uint64_t>) {
+ unsigned long long val = std::stoull(s);
+ if (val > std::numeric_limits<NumericT>::max()) {
+ throw std::out_of_range("Value out of range for unsigned type");
+ }
+ return static_cast<NumericT>(val);
+ } else if constexpr (std::is_same_v<NumericT, float>) {
+ return std::stof(s);
+ } else if constexpr (std::is_same_v<NumericT, double>) {
+ return std::stod(s);
+ } else if constexpr (std::is_same_v<NumericT, long double>) {
+ return std::stold(s);
+ } else {
+ static_assert(ALWAYS_FALSE_V<NumericT>, "Unsupported numeric type");
+ }
+ } catch (const std::exception &e) {
+ throw std::runtime_error("Failed to parse numeric value: \"" + s + "\"");
+ }
+ }
+
private:
bool preprocess(std::vector<std::string> tokens);
diff --git a/tests/gold_tests/pluginTest/header_rewrite/gold/ext-sets.gold
b/tests/gold_tests/pluginTest/header_rewrite/gold/ext-sets.gold
new file mode 100644
index 0000000000..c71cee3ac7
--- /dev/null
+++ b/tests/gold_tests/pluginTest/header_rewrite/gold/ext-sets.gold
@@ -0,0 +1,10 @@
+``
+> 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
+``
diff --git
a/tests/gold_tests/pluginTest/header_rewrite/gold/header_rewrite-client.gold
b/tests/gold_tests/pluginTest/header_rewrite/gold/header_rewrite-client.gold
index 418608bcb5..8d5d99ba35 100644
--- a/tests/gold_tests/pluginTest/header_rewrite/gold/header_rewrite-client.gold
+++ b/tests/gold_tests/pluginTest/header_rewrite/gold/header_rewrite-client.gold
@@ -9,5 +9,5 @@
< Proxy-Connection: keep-alive
< Server: ATS/``
< Cache-Control: no-store
-<
+< X-Pre-Else: Yes
``
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 db4fb86e1f..c86ea42e6c 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
@@ -28,8 +28,18 @@ server = Test.MakeOriginServer("server")
# Configure the server to return 200 responses. The rewrite rules below set a
# non-200 status, so if curl gets a 200 response something went wrong.
Test.testName = ""
-request_header = {"headers": "GET / HTTP/1.1\r\nHost:
www.example.com\r\n\r\n", "timestamp": "1469733493.993", "body": ""}
response_header = {"headers": "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n",
"timestamp": "1469733493.993", "body": ""}
+request_header = {
+ "headers": "GET /to_path/hello?=foo=bar HTTP/1.1\r\nHost:
www.example.com\r\n\r\n",
+ "timestamp": "1469733493.993",
+ "body": ""
+}
+server.addResponse("sessionfile.log", request_header, response_header)
+request_header = {
+ "headers": "GET /to_path/hrw-sets.png HTTP/1.1\r\nHost:
www.example.com\r\n\r\n",
+ "timestamp": "1469733493.993",
+ "body": ""
+}
server.addResponse("sessionfile.log", request_header, response_header)
request_header = {"headers": "GET / HTTP/1.1\r\nHost: no_path.com\r\n\r\n",
"timestamp": "1469733493.993", "body": ""}
server.addResponse("sessionfile.log", request_header, response_header)
@@ -46,10 +56,10 @@ ts.Setup.CopyAs('rules/set_redirect.conf',
Test.RunDirectory)
# This configuration makes use of CLIENT-URL in conditions.
ts.Disk.remap_config.AddLine(
- 'map http://www.example.com/from_path/ https://127.0.0.1:{0}/to_path/ '
+ 'map http://www.example.com/from_path/ http://127.0.0.1:{0}/to_path/ '
'@plugin=header_rewrite.so
@pparam={1}/rule_client.conf'.format(server.Variables.Port, Test.RunDirectory))
ts.Disk.remap_config.AddLine(
- 'map http://www.example.com:8080/from_path/ https://127.0.0.1:{0}/to_path/
'
+ 'map http://www.example.com:8080/from_path/ http://127.0.0.1:{0}/to_path/ '
'@plugin=header_rewrite.so
@pparam={1}/rule_client.conf'.format(server.Variables.Port, Test.RunDirectory))
# This configuration makes use of TO-URL in a set-redirect operator.
ts.Disk.remap_config.AddLine(
@@ -75,3 +85,13 @@ tr.Processes.Default.ReturnCode = 0
tr.Processes.Default.Streams.stderr = "gold/set-redirect.gold"
tr.StillRunningAfter = server
ts.Disk.traffic_out.Content = "gold/header_rewrite-tag.gold"
+
+# Test HRW sets matching
+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" --verbose'.format(ts.Variables.port))
+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"
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 a481775573..1b6d845084 100644
--- a/tests/gold_tests/pluginTest/header_rewrite/rules/rule_client.conf
+++ b/tests/gold_tests/pluginTest/header_rewrite/rules/rule_client.conf
@@ -19,4 +19,14 @@ 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
\ No newline at end of file
+set-status 304
+
+cond %{SEND_RESPONSE_HDR_HOOK}
+cond %{CLIENT-URL:PATH} (png,gif,jpeg) [EXT,NOCASE]
+ set-header X-Extension "Yes"
+
+cond %{SEND_RESPONSE_HDR_HOOK}
+cond %{CLIENT-URL:PATH} (hrw,foo) [MID,NOCASE]
+ no-op
+else
+ set-header X-Pre-Else "Yes"
diff --git
a/tests/gold_tests/pluginTest/header_rewrite/rules/rule_cond_method.conf
b/tests/gold_tests/pluginTest/header_rewrite/rules/rule_cond_method.conf
index deb77ab539..0b600f4560 100644
--- a/tests/gold_tests/pluginTest/header_rewrite/rules/rule_cond_method.conf
+++ b/tests/gold_tests/pluginTest/header_rewrite/rules/rule_cond_method.conf
@@ -16,7 +16,7 @@
# limitations under the License.
cond %{READ_REQUEST_HDR_HOOK}
-cond %{METHOD} =GET
+cond %{METHOD} (GET,PUSH)
set-config proxy.config.http.insert_response_via_str 1 [L]
cond %{SEND_REQUEST_HDR_HOOK}