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}

Reply via email to