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 cf4b17822e Adds a %{LAST-CAPTURE} condition to HRW (#12117)
cf4b17822e is described below

commit cf4b17822edf7b7772d2ea3eae6ef6026e96b9b0
Author: Leif Hedstrom <[email protected]>
AuthorDate: Fri Apr 4 17:03:04 2025 -0600

    Adds a %{LAST-CAPTURE} condition to HRW (#12117)
---
 doc/admin-guide/plugins/header_rewrite.en.rst | 21 ++++++
 plugins/header_rewrite/conditions.cc          | 98 +++++++++++++++++++--------
 plugins/header_rewrite/conditions.h           | 22 ++++++
 plugins/header_rewrite/factory.cc             |  3 +-
 plugins/header_rewrite/lulu.h                 |  1 +
 plugins/header_rewrite/matcher.h              | 22 +++---
 plugins/header_rewrite/regex_helper.cc        |  1 +
 plugins/header_rewrite/regex_helper.h         |  2 -
 plugins/header_rewrite/resources.cc           |  4 ++
 plugins/header_rewrite/resources.h            |  5 +-
 10 files changed, 138 insertions(+), 41 deletions(-)

diff --git a/doc/admin-guide/plugins/header_rewrite.en.rst 
b/doc/admin-guide/plugins/header_rewrite.en.rst
index f058d15a75..ecec2f30d4 100644
--- a/doc/admin-guide/plugins/header_rewrite.en.rst
+++ b/doc/admin-guide/plugins/header_rewrite.en.rst
@@ -518,6 +518,27 @@ Returns true if the current transaction was 
internally-generated by |TS| (using
 external client requests, but are triggered (often by plugins) entirely within
 the |TS| process.
 
+LAST-CAPTURE
+~~~~~~~~~~~~
+::
+
+    cond %{LAST-CAPTURE:<part>} <operand>
+
+If a previous condition has been a regular expression match, and capture groups
+were used in the match, this condition can be used to access the last capture
+group. The ``<part>`` is the index of the capture group, starting at ``0``
+with a max index of ``9``. The index ``0`` is special, just like in PCRE, 
having
+the value of the entire match.
+
+If there was no regex match in a previous condition, these conditions have the
+implicit value of an empty string. The capture groups are also only available
+within a rule, and not across rules.
+
+This condition may be most useful as a value to an operand, such as::
+
+    cond %{HEADER:X-Foo} /foo-(.*)/
+      set-header X-Foo %{LAST-CAPTURE:1}"
+
 METHOD
 ~~~~~~~
 ::
diff --git a/plugins/header_rewrite/conditions.cc 
b/plugins/header_rewrite/conditions.cc
index 1858735d1f..cae6e911ab 100644
--- a/plugins/header_rewrite/conditions.cc
+++ b/plugins/header_rewrite/conditions.cc
@@ -60,7 +60,7 @@ bool
 ConditionStatus::eval(const Resources &res)
 {
   Dbg(pi_dbg_ctl, "Evaluating STATUS()");
-  return static_cast<MatcherType *>(_matcher)->test(res.resp_status);
+  return static_cast<MatcherType *>(_matcher)->test(res.resp_status, res);
 }
 
 void
@@ -91,7 +91,7 @@ ConditionMethod::eval(const Resources &res)
   append_value(s, res);
   Dbg(pi_dbg_ctl, "Evaluating METHOD()");
 
-  return static_cast<const MatcherType *>(_matcher)->test(s);
+  return static_cast<const MatcherType *>(_matcher)->test(s, res);
 }
 
 void
@@ -128,10 +128,10 @@ ConditionRandom::initialize(Parser &p)
 }
 
 bool
-ConditionRandom::eval(const Resources & /* res ATS_UNUSED */)
+ConditionRandom::eval(const Resources &res)
 {
   Dbg(pi_dbg_ctl, "Evaluating RANDOM()");
-  return static_cast<const MatcherType *>(_matcher)->test(rand_r(&_seed) % 
_max);
+  return static_cast<const MatcherType *>(_matcher)->test(rand_r(&_seed) % 
_max, res);
 }
 
 void
@@ -246,7 +246,7 @@ ConditionHeader::eval(const Resources &res)
   append_value(s, res);
   Dbg(pi_dbg_ctl, "Evaluating HEADER()");
 
-  return static_cast<const MatcherType *>(_matcher)->test(s);
+  return static_cast<const MatcherType *>(_matcher)->test(s, res);
 }
 
 // ConditionUrl: request or response header. TODO: This is not finished, at 
all!!!
@@ -360,7 +360,7 @@ ConditionUrl::eval(const Resources &res)
 
   append_value(s, res);
 
-  return static_cast<const Matchers<std::string> *>(_matcher)->test(s);
+  return static_cast<const Matchers<std::string> *>(_matcher)->test(s, res);
 }
 
 // ConditionDBM: do a lookup against a DBM
@@ -424,7 +424,7 @@ ConditionDBM::eval(const Resources &res)
   append_value(s, res);
   Dbg(pi_dbg_ctl, "Evaluating DBM()");
 
-  return static_cast<const MatcherType *>(_matcher)->test(s);
+  return static_cast<const MatcherType *>(_matcher)->test(s, res);
 }
 
 // ConditionCookie: request or response header
@@ -494,7 +494,7 @@ ConditionCookie::eval(const Resources &res)
   append_value(s, res);
   Dbg(pi_dbg_ctl, "Evaluating COOKIE()");
 
-  return static_cast<const MatcherType *>(_matcher)->test(s);
+  return static_cast<const MatcherType *>(_matcher)->test(s, res);
 }
 
 // ConditionInternalTxn: Is the txn internal?
@@ -567,7 +567,7 @@ ConditionIp::eval(const Resources &res)
     }
 
     if (addr) {
-      return static_cast<const Matchers<const sockaddr *> 
*>(_matcher)->test(addr);
+      return static_cast<const Matchers<const sockaddr *> 
*>(_matcher)->test(addr, res);
     } else {
       return false;
     }
@@ -575,7 +575,7 @@ ConditionIp::eval(const Resources &res)
     std::string s;
 
     append_value(s, res);
-    bool rval = static_cast<const Matchers<std::string> *>(_matcher)->test(s);
+    bool rval = static_cast<const Matchers<std::string> *>(_matcher)->test(s, 
res);
 
     Dbg(pi_dbg_ctl, "Evaluating IP(): %s - rval: %d", s.c_str(), rval);
 
@@ -631,7 +631,7 @@ ConditionTransactCount::eval(const Resources &res)
     int n = TSHttpSsnTransactionCount(ssn);
 
     Dbg(pi_dbg_ctl, "Evaluating TXN-COUNT()");
-    return static_cast<MatcherType *>(_matcher)->test(n);
+    return static_cast<MatcherType *>(_matcher)->test(n, res);
   }
 
   Dbg(pi_dbg_ctl, "\tNo session found, returning false");
@@ -748,12 +748,12 @@ ConditionNow::append_value(std::string &s, const 
Resources & /* res ATS_UNUSED *
 }
 
 bool
-ConditionNow::eval(const Resources & /* res ATS_UNUSED */)
+ConditionNow::eval(const Resources &res)
 {
   int64_t now = get_now_qualified(_now_qual);
 
   Dbg(pi_dbg_ctl, "Evaluating NOW()");
-  return static_cast<const MatcherType *>(_matcher)->test(now);
+  return static_cast<const MatcherType *>(_matcher)->test(now, res);
 }
 
 std::string
@@ -833,12 +833,12 @@ ConditionGeo::eval(const Resources &res)
   if (is_int_type()) {
     int64_t geo = get_geo_int(TSHttpTxnClientAddrGet(res.txnp));
 
-    ret = static_cast<const Matchers<int64_t> *>(_matcher)->test(geo);
+    ret = static_cast<const Matchers<int64_t> *>(_matcher)->test(geo, res);
   } else {
     std::string s;
 
     append_value(s, res);
-    ret = static_cast<const Matchers<std::string> *>(_matcher)->test(s);
+    ret = static_cast<const Matchers<std::string> *>(_matcher)->test(s, res);
   }
 
   return ret;
@@ -917,12 +917,12 @@ ConditionId::eval(const Resources &res)
     uint64_t id = TSHttpTxnIdGet(res.txnp);
 
     Dbg(pi_dbg_ctl, "Evaluating GEO() -> %" PRIu64, id);
-    return static_cast<const Matchers<uint64_t> *>(_matcher)->test(id);
+    return static_cast<const Matchers<uint64_t> *>(_matcher)->test(id, res);
   } else {
     std::string s;
 
     append_value(s, res);
-    bool rval = static_cast<const Matchers<std::string> *>(_matcher)->test(s);
+    bool rval = static_cast<const Matchers<std::string> *>(_matcher)->test(s, 
res);
 
     Dbg(pi_dbg_ctl, "Evaluating ID(): %s - rval: %d", s.c_str(), rval);
     return rval;
@@ -982,7 +982,7 @@ ConditionCidr::eval(const Resources &res)
   append_value(s, res);
   Dbg(pi_dbg_ctl, "Evaluating CIDR()");
 
-  return static_cast<MatcherType *>(_matcher)->test(s);
+  return static_cast<MatcherType *>(_matcher)->test(s, res);
 }
 
 void
@@ -1103,7 +1103,7 @@ ConditionInbound::eval(const Resources &res)
     }
 
     if (addr) {
-      return static_cast<const Matchers<const sockaddr *> 
*>(_matcher)->test(addr);
+      return static_cast<const Matchers<const sockaddr *> 
*>(_matcher)->test(addr, res);
     } else {
       return false;
     }
@@ -1111,7 +1111,7 @@ ConditionInbound::eval(const Resources &res)
     std::string s;
 
     append_value(s, res);
-    bool rval = static_cast<const Matchers<std::string> *>(_matcher)->test(s);
+    bool rval = static_cast<const Matchers<std::string> *>(_matcher)->test(s, 
res);
 
     Dbg(pi_dbg_ctl, "Evaluating %s(): %s - rval: %d", TAG, s.c_str(), rval);
 
@@ -1200,11 +1200,11 @@ ConditionStringLiteral::append_value(std::string &s, 
const Resources & /* res AT
 }
 
 bool
-ConditionStringLiteral::eval(const Resources & /* res ATS_UNUSED */)
+ConditionStringLiteral::eval(const Resources &res)
 {
   Dbg(pi_dbg_ctl, "Evaluating StringLiteral");
 
-  return static_cast<const MatcherType *>(_matcher)->test(_literal);
+  return static_cast<const MatcherType *>(_matcher)->test(_literal, res);
 }
 
 // ConditionSessionTransactCount
@@ -1225,7 +1225,7 @@ ConditionSessionTransactCount::eval(const Resources &res)
   int const val = TSHttpTxnServerSsnTransactionCount(res.txnp);
 
   Dbg(pi_dbg_ctl, "Evaluating SSN-TXN-COUNT()");
-  return static_cast<MatcherType *>(_matcher)->test(val);
+  return static_cast<MatcherType *>(_matcher)->test(val, res);
 }
 
 void
@@ -1267,7 +1267,7 @@ ConditionTcpInfo::eval(const Resources &res)
   std::string s;
 
   append_value(s, res);
-  bool rval = static_cast<const Matchers<std::string> *>(_matcher)->test(s);
+  bool rval = static_cast<const Matchers<std::string> *>(_matcher)->test(s, 
res);
 
   Dbg(pi_dbg_ctl, "Evaluating TCP-Info: %s - rval: %d", s.c_str(), rval);
 
@@ -1332,7 +1332,7 @@ ConditionCache::eval(const Resources &res)
   append_value(s, res);
   Dbg(pi_dbg_ctl, "Evaluating CACHE()");
 
-  return static_cast<const MatcherType *>(_matcher)->test(s);
+  return static_cast<const MatcherType *>(_matcher)->test(s, res);
 }
 
 void
@@ -1411,7 +1411,7 @@ ConditionNextHop::eval(const Resources &res)
 
   append_value(s, res);
 
-  return static_cast<const Matchers<std::string> *>(_matcher)->test(s);
+  return static_cast<const Matchers<std::string> *>(_matcher)->test(s, res);
 }
 
 // ConditionHttpCntl: request header.
@@ -1511,7 +1511,7 @@ ConditionStateInt8::eval(const Resources &res)
 
   Dbg(pi_dbg_ctl, "Evaluating STATE-INT8()");
 
-  return static_cast<const MatcherType *>(_matcher)->test(data);
+  return static_cast<const MatcherType *>(_matcher)->test(data, res);
 }
 
 // ConditionStateInt16
@@ -1557,5 +1557,47 @@ ConditionStateInt16::eval(const Resources &res)
 
   Dbg(pi_dbg_ctl, "Evaluating STATE-INT8()");
 
-  return static_cast<const MatcherType *>(_matcher)->test(data);
+  return static_cast<const MatcherType *>(_matcher)->test(data, res);
+}
+
+// ConditionLastCapture
+void
+ConditionLastCapture::set_qualifier(const std::string &q)
+{
+  Condition::set_qualifier(q);
+
+  if (q.empty()) {
+    _ix = 0;
+  } else {
+    _ix = strtol(q.c_str(), nullptr, 10);
+  }
+
+  if (_ix < 0 || _ix > 9) { // Only $0 - $9
+    TSError("[%s] LAST-CAPTURE index out of range: %s", PLUGIN_NAME, 
q.c_str());
+  } else {
+    Dbg(pi_dbg_ctl, "\tParsing %%{LAST-CAPTURE:%s}", q.c_str());
+  }
+}
+
+void
+ConditionLastCapture::append_value(std::string &s, const Resources &res)
+{
+  if (res.ovector_ptr && res.ovector_count > _ix) {
+    int start = res.ovector[_ix * 2];
+    int end   = res.ovector[_ix * 2 + 1];
+
+    s.append(std::string_view(res.ovector_ptr).substr(start, (end - start)));
+    Dbg(pi_dbg_ctl, "Evaluating LAST-CAPTURE(%d)", _ix);
+  }
+}
+
+bool
+ConditionLastCapture::eval(const Resources &res)
+{
+  std::string s;
+
+  append_value(s, res);
+  Dbg(pi_dbg_ctl, "Evaluating LAST-CAPTURE()");
+
+  return static_cast<const MatcherType *>(_matcher)->test(s, res);
 }
diff --git a/plugins/header_rewrite/conditions.h 
b/plugins/header_rewrite/conditions.h
index 40ce009b89..18912f4c1c 100644
--- a/plugins/header_rewrite/conditions.h
+++ b/plugins/header_rewrite/conditions.h
@@ -828,3 +828,25 @@ private:
     return ((ptr & STATE_INT16_MASK) >> 48);
   }
 };
+
+// Last regex capture
+class ConditionLastCapture : public Condition
+{
+  using MatcherType = Matchers<std::string>;
+
+public:
+  explicit ConditionLastCapture() { Dbg(dbg_ctl, "Calling CTOR for 
ConditionLastCapture"); }
+
+  // noncopyable
+  ConditionLastCapture(const ConditionLastCapture &) = delete;
+  void operator=(const ConditionLastCapture &)       = delete;
+
+  void set_qualifier(const std::string &q) override;
+  void append_value(std::string &s, const Resources &res) override;
+
+protected:
+  bool eval(const Resources &res) override;
+
+private:
+  int _ix = -1;
+};
diff --git a/plugins/header_rewrite/factory.cc 
b/plugins/header_rewrite/factory.cc
index 4aa40a4b6d..2e28404377 100644
--- a/plugins/header_rewrite/factory.cc
+++ b/plugins/header_rewrite/factory.cc
@@ -178,7 +178,8 @@ condition_factory(const std::string &cond)
     c = new ConditionStateInt8();
   } else if (c_name == "STATE-INT16") {
     c = new ConditionStateInt16();
-
+  } else if (c_name == "LAST-CAPTURE") {
+    c = new ConditionLastCapture();
   } else {
     TSError("[%s] Unknown condition %s", PLUGIN_NAME, c_name.c_str());
     return nullptr;
diff --git a/plugins/header_rewrite/lulu.h b/plugins/header_rewrite/lulu.h
index 3900713b0d..70db9bde8e 100644
--- a/plugins/header_rewrite/lulu.h
+++ b/plugins/header_rewrite/lulu.h
@@ -30,6 +30,7 @@
 #include "proxy/http/remap/PluginFactory.h"
 
 #define TS_REMAP_PSEUDO_HOOK TS_HTTP_LAST_HOOK // Ugly, but use the "last 
hook" for remap instances.
+const int OVECCOUNT = 30;                      // We support $1 - $9 only, and 
this needs to be 3x that
 
 std::string getIP(sockaddr const *s_sockaddr);
 char       *getIP(sockaddr const *s_sockaddr, char res[INET6_ADDRSTRLEN]);
diff --git a/plugins/header_rewrite/matcher.h b/plugins/header_rewrite/matcher.h
index aec4c98575..3406eb389e 100644
--- a/plugins/header_rewrite/matcher.h
+++ b/plugins/header_rewrite/matcher.h
@@ -29,6 +29,7 @@
 
 #include "ts/ts.h"
 
+#include "resources.h"
 #include "regex_helper.h"
 #include "lulu.h"
 
@@ -99,7 +100,7 @@ public:
 
   // Evaluate this matcher
   bool
-  test(const T &t) const
+  test(const T &t, const Resources &res) const
   {
     switch (_op) {
     case MATCH_EQUAL:
@@ -112,7 +113,7 @@ public:
       return test_gt(t);
       break;
     case MATCH_REGULAR_EXPRESSION:
-      return test_reg(t);
+      return test_reg(t, res); // Only the regex matcher needs the resource
       break;
     case MATCH_IP_RANGES:
       // This is an error, the Matcher doesn't make sense to match on IP ranges
@@ -174,27 +175,30 @@ private:
   }
 
   bool
-  test_reg(const unsigned int /* t ATS_UNUSED */) const
+  test_reg(const unsigned int /* t ATS_UNUSED */, const Resources & /* Not 
used */) const
   {
     // Not supported
     return false;
   }
 
   bool
-  test_reg(const TSHttpStatus /* t ATS_UNUSED */) const
+  test_reg(const TSHttpStatus /* t ATS_UNUSED */, const Resources & /* Not 
used */) const
   {
     // Not supported
     return false;
   }
 
   bool
-  test_reg(const std::string &t) const
+  test_reg(const std::string &t, const Resources &res) const
   {
-    int ovector[OVECCOUNT];
-
     Dbg(pi_dbg_ctl, "Test regular expression %s : %s (NOCASE = %d)", 
_data.c_str(), t.c_str(), static_cast<int>(_nocase));
-    if (_reHelper.regexMatch(t.c_str(), t.length(), ovector) > 0) {
+    int count = _reHelper.regexMatch(t.c_str(), t.length(), 
const_cast<Resources &>(res).ovector);
+
+    if (count > 0) {
       Dbg(pi_dbg_ctl, "Successfully found regular expression match");
+      const_cast<Resources &>(res).ovector_ptr   = t.c_str();
+      const_cast<Resources &>(res).ovector_count = count;
+
       return true;
     }
 
@@ -229,7 +233,7 @@ public:
   }
 
   bool
-  test(const sockaddr *addr) const
+  test(const sockaddr *addr, const Resources & /* Not used */) const
   {
     if (_ipHelper.contains(swoc::IPAddr(addr))) {
       if (pi_dbg_ctl.on()) {
diff --git a/plugins/header_rewrite/regex_helper.cc 
b/plugins/header_rewrite/regex_helper.cc
index dfc84a0bdc..de1e7f2ef9 100644
--- a/plugins/header_rewrite/regex_helper.cc
+++ b/plugins/header_rewrite/regex_helper.cc
@@ -16,6 +16,7 @@
   limitations under the License.
 */
 #include "regex_helper.h"
+#include "lulu.h"
 
 bool
 regexHelper::setRegexMatch(const std::string &s, bool nocase)
diff --git a/plugins/header_rewrite/regex_helper.h 
b/plugins/header_rewrite/regex_helper.h
index 32c5dbffb6..98a69c0f2a 100644
--- a/plugins/header_rewrite/regex_helper.h
+++ b/plugins/header_rewrite/regex_helper.h
@@ -27,8 +27,6 @@
 
 #include <string>
 
-const int OVECCOUNT = 30; // We support $1 - $9 only, and this needs to be 3x 
that
-
 class regexHelper
 {
 public:
diff --git a/plugins/header_rewrite/resources.cc 
b/plugins/header_rewrite/resources.cc
index d51ddd8a61..a7061f286b 100644
--- a/plugins/header_rewrite/resources.cc
+++ b/plugins/header_rewrite/resources.cc
@@ -30,6 +30,10 @@ Resources::gather(const ResourceIDs ids, TSHttpHookID hook)
 {
   Dbg(pi_dbg_ctl, "Building resources, hook=%s", TSHttpHookNameLookup(hook));
 
+  // Clear the capture groups just in case
+  ovector_count = 0;
+  ovector_ptr   = nullptr;
+
   // If we need the client request headers, make sure it's also available in 
the client vars.
   if (ids & RSRC_CLIENT_REQUEST_HEADERS) {
     Dbg(pi_dbg_ctl, "\tAdding TXN client request header buffers");
diff --git a/plugins/header_rewrite/resources.h 
b/plugins/header_rewrite/resources.h
index f1ed2623ca..0aa0d82def 100644
--- a/plugins/header_rewrite/resources.h
+++ b/plugins/header_rewrite/resources.h
@@ -74,7 +74,10 @@ public:
   TSMBuffer           client_bufp    = nullptr;
   TSMLoc              client_hdr_loc = nullptr;
   TSHttpStatus        resp_status    = TS_HTTP_STATUS_NONE;
-  bool                changed_url    = false;
+  const char         *ovector_ptr    = nullptr;
+  int                 ovector[OVECCOUNT];
+  int                 ovector_count = 0;
+  bool                changed_url   = false;
 
 private:
   void destroy();

Reply via email to