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();