This is an automated email from the ASF dual-hosted git repository.

cmcfarlen pushed a commit to branch 10.1.x
in repository https://gitbox.apache.org/repos/asf/trafficserver.git

commit a9a4f29c9ce9be0059ae29bd456154d3114470b4
Author: Leif Hedstrom <[email protected]>
AuthorDate: Thu Aug 21 16:12:06 2025 -0600

    HRW: Adds  %{INBOUND:SERVER-CERT} and %{INBOUND:CLIENT-CERT} conds (#12363)
    
    * Adds a %{CERT} and %{CLIENT-CERT} cond
    
    * Moved certificates to INBOUND: condition
    
    (cherry picked from commit 938b4913d5b76509306553016421777c1fddbf5e)
    (cherry picked from commit 0b6ea0e625f5cac9edac732a604468ad39f3ef45)
---
 doc/admin-guide/plugins/header_rewrite.en.rst |  40 ++++-
 include/cripts/Transaction.hpp                |   2 -
 plugins/header_rewrite/CMakeLists.txt         |   5 +
 plugins/header_rewrite/conditions.cc          | 223 ++++++++++++++++++++------
 plugins/header_rewrite/conditions.h           |   9 +-
 plugins/header_rewrite/header_rewrite.cc      |  28 ++--
 plugins/header_rewrite/operators.cc           | 111 ++++++-------
 plugins/header_rewrite/resources.cc           |  49 +++++-
 plugins/header_rewrite/resources.h            |  67 +++++---
 plugins/header_rewrite/statement.h            |  26 ++-
 plugins/header_rewrite/value.cc               |   8 +-
 plugins/header_rewrite/value.h                |   4 +-
 12 files changed, 416 insertions(+), 156 deletions(-)

diff --git a/doc/admin-guide/plugins/header_rewrite.en.rst 
b/doc/admin-guide/plugins/header_rewrite.en.rst
index 540bd9598c..91e4811138 100644
--- a/doc/admin-guide/plugins/header_rewrite.en.rst
+++ b/doc/admin-guide/plugins/header_rewrite.en.rst
@@ -454,7 +454,7 @@ INBOUND
 ~~~~~~~
 ::
 
-   cond %{INBOUND:TLS} /./
+   cond %{INBOUND:TLS} ="" [NOT]
 
 This condition provides access to information about the inbound (client, user 
agent) connection to ATS.
 The data that can be checked is ::
@@ -482,7 +482,7 @@ will be true for *non*-TLS connections because it will be 
true when ``%{INBOUND:
 string. This happens because the default matching is equality and the default 
value the empty
 string. Therefore the condition is treated as if it were ::
 
-  cond %{INBOUND:TLS}=""
+  cond %{INBOUND:TLS} =""
 
 which is true when the connection is not TLS. The arguments ``H2``, ``IPV4``, 
and ``IPV6`` work the
 same way.
@@ -497,6 +497,42 @@ As a special matcher, the inbound IP addresses can be 
matched against a list of
     and the configuration parser will error out. The format here is very 
specific, in particular no
     white spaces are allowed between the ranges.
 
+If |ATS| is built with :ref:`Cripts <developer-guide-cripts>` support, a 
number of additional
+qualifiers are available exclusively on TLS sessions, for X509 certificate 
introspection.
+The client certificate (for mutual TLS) is accessed with qualifier prefix of 
``CLIENT-CERT:``
+and the server certificate with a prefix qualifier of ``SERVER-CERT:``. The 
X509 naming of the
+specific fields are the same for the two certificates::
+
+   PEM              The PEM-encoded certificate, as a string.
+   SIG              The signature of the certificate.
+   SUBJECT          The subject of the certificate.
+   ISSUER           The issuer of the certificate.
+   SERIAL           The serial number of the certificate.
+   NOT_BEFORE       The date and time when the certificate becomes valid.
+   NOT_AFTER        The date and time when the certificate expires.
+   VERSION          The version of the certificate.
+   SAN:DNS          The Subject Alternative Name (SAN) DNS entries.
+   SAN:IP           The Subject Alternative Name (SAN) IP addresses.
+   SAN:EMAIL        The Subject Alternative Name (SAN) email addresses.
+   SAN:URI          The Subject Alternative Name (SAN) URIs.
+
+These conditions and qualifiers can be used in conditions of course, but more 
importantly,
+are also very useful when adding or modifying headers. For example, you can 
add some
+client certificate and server subject to the response headers, so that the 
client can see it
+as part of the response ::
+
+    cond %{SEND_RESPONSE_HDR_HOOK} [AND]
+    cond %{INBOUND:TLS} ="" [NOT]
+      set-header X-Client-Cert "%{INBOUND:CLIENT-CERT:PEM}"
+      set-header X-Client-Cert-Subject "%{INBOUND:CLIENT-CERT:SUBJECT}"
+      set-header X-Client-Cert-Issuer "%{INBOUND:CLIENT-CERT:ISSUER}"
+      set-header X-Server-Cert-Subject "%{INBOUND:SERVER-CERT:SUBJECT}""
+
+The ``SAN:`` fields will return a semicolon-separated list of the respective
+values, there can be zero, one or many of each SAN type. Example ::
+
+    cond %{CLIENT-CERT:SAN:DNS} /example\.com/
+
 IP
 ~~
 ::
diff --git a/include/cripts/Transaction.hpp b/include/cripts/Transaction.hpp
index 9bcdb83d33..1831406b1e 100644
--- a/include/cripts/Transaction.hpp
+++ b/include/cripts/Transaction.hpp
@@ -17,8 +17,6 @@
 */
 #pragma once
 
-#include <fmt/core.h>
-
 #include "ts/ts.h"
 
 #include "cripts/Error.hpp"
diff --git a/plugins/header_rewrite/CMakeLists.txt 
b/plugins/header_rewrite/CMakeLists.txt
index e1a8ef7bc6..7527a1efd3 100644
--- a/plugins/header_rewrite/CMakeLists.txt
+++ b/plugins/header_rewrite/CMakeLists.txt
@@ -42,6 +42,11 @@ target_link_libraries(
   PUBLIC libswoc::libswoc
 )
 
+if(ENABLE_CRIPTS)
+  target_link_libraries(header_rewrite PRIVATE ts::cripts)
+  target_compile_definitions(header_rewrite PRIVATE TS_HAS_CRIPTS=1)
+endif()
+
 if(maxminddb_FOUND)
   target_compile_definitions(header_rewrite PUBLIC TS_USE_HRW_MAXMINDDB=1)
   target_sources(header_rewrite PRIVATE conditions_geo_maxmind.cc)
diff --git a/plugins/header_rewrite/conditions.cc 
b/plugins/header_rewrite/conditions.cc
index 4cd109fa9e..522a60bc69 100644
--- a/plugins/header_rewrite/conditions.cc
+++ b/plugins/header_rewrite/conditions.cc
@@ -36,6 +36,10 @@
 
 static const sockaddr *getClientAddr(TSHttpTxn txnp, int txn_private_slot);
 
+#if TS_HAS_CRIPTS
+#include "cripts/Certs.hpp"
+#endif
+
 // ConditionStatus
 void
 ConditionStatus::initialize(Parser &p)
@@ -280,7 +284,7 @@ ConditionUrl::append_value(std::string &s, const Resources 
&res)
   if (_type == CLIENT) {
     // CLIENT always uses the pristine URL
     Dbg(pi_dbg_ctl, "   Using the pristine url");
-    if (TSHttpTxnPristineUrlGet(res.txnp, &bufp, &url) != TS_SUCCESS) {
+    if (TSHttpTxnPristineUrlGet(res.state.txnp, &bufp, &url) != TS_SUCCESS) {
       TSError("[%s] Error getting the pristine URL", PLUGIN_NAME);
       return;
     }
@@ -503,7 +507,7 @@ ConditionCookie::eval(const Resources &res)
 bool
 ConditionInternalTxn::eval(const Resources &res)
 {
-  bool ret = (0 != TSHttpTxnIsInternal(res.txnp));
+  bool ret = (0 != TSHttpTxnIsInternal(res.state.txnp));
 
   Dbg(pi_dbg_ctl, "Evaluating INTERNAL-TRANSACTION() -> %d", ret);
   return ret;
@@ -555,16 +559,16 @@ ConditionIp::eval(const Resources &res)
 
     switch (_ip_qual) {
     case IP_QUAL_CLIENT:
-      addr = getClientAddr(res.txnp, _txn_private_slot);
+      addr = getClientAddr(res.state.txnp, _txn_private_slot);
       break;
     case IP_QUAL_INBOUND:
-      addr = TSHttpTxnIncomingAddrGet(res.txnp);
+      addr = TSHttpTxnIncomingAddrGet(res.state.txnp);
       break;
     case IP_QUAL_SERVER:
-      addr = TSHttpTxnServerAddrGet(res.txnp);
+      addr = TSHttpTxnServerAddrGet(res.state.txnp);
       break;
     case IP_QUAL_OUTBOUND:
-      addr = TSHttpTxnOutgoingAddrGet(res.txnp);
+      addr = TSHttpTxnOutgoingAddrGet(res.state.txnp);
       break;
     }
 
@@ -593,17 +597,17 @@ ConditionIp::append_value(std::string &s, const Resources 
&res)
 
   switch (_ip_qual) {
   case IP_QUAL_CLIENT:
-    ip_set = (nullptr != getIP(getClientAddr(res.txnp, _txn_private_slot), 
ip));
+    ip_set = (nullptr != getIP(getClientAddr(res.state.txnp, 
_txn_private_slot), ip));
     break;
   case IP_QUAL_INBOUND:
-    ip_set = (nullptr != getIP(TSHttpTxnIncomingAddrGet(res.txnp), ip));
+    ip_set = (nullptr != getIP(TSHttpTxnIncomingAddrGet(res.state.txnp), ip));
     break;
   case IP_QUAL_SERVER:
-    ip_set = (nullptr != getIP(TSHttpTxnServerAddrGet(res.txnp), ip));
+    ip_set = (nullptr != getIP(TSHttpTxnServerAddrGet(res.state.txnp), ip));
     break;
   case IP_QUAL_OUTBOUND:
     Dbg(pi_dbg_ctl, "Requesting output ip");
-    ip_set = (nullptr != getIP(TSHttpTxnOutgoingAddrGet(res.txnp), ip));
+    ip_set = (nullptr != getIP(TSHttpTxnOutgoingAddrGet(res.state.txnp), ip));
     break;
   }
 
@@ -627,10 +631,8 @@ ConditionTransactCount::initialize(Parser &p)
 bool
 ConditionTransactCount::eval(const Resources &res)
 {
-  TSHttpSsn ssn = TSHttpTxnSsnGet(res.txnp);
-
-  if (ssn) {
-    int n = TSHttpSsnTransactionCount(ssn);
+  if (res.state.ssnp) {
+    int n = TSHttpSsnTransactionCount(res.state.ssnp);
 
     Dbg(pi_dbg_ctl, "Evaluating TXN-COUNT()");
     return static_cast<MatcherType *>(_matcher)->test(n, res);
@@ -643,11 +645,9 @@ ConditionTransactCount::eval(const Resources &res)
 void
 ConditionTransactCount::append_value(std::string &s, Resources const &res)
 {
-  TSHttpSsn ssn = TSHttpTxnSsnGet(res.txnp);
-
-  if (ssn) {
+  if (res.state.ssnp) {
     char value[32]; // enough for UINT64_MAX
-    int  count  = TSHttpSsnTransactionCount(ssn);
+    int  count  = TSHttpSsnTransactionCount(res.state.ssnp);
     int  length = ink_fast_itoa(count, value, sizeof(value));
 
     if (length > 0) {
@@ -673,7 +673,7 @@ ConditionNow::get_now_qualified(NowQualifiers qual, const 
Resources &resources)
     struct tm res;
 
     PrivateSlotData private_data;
-    private_data.raw = reinterpret_cast<uint64_t>(TSUserArgGet(resources.txnp, 
_txn_private_slot));
+    private_data.raw = 
reinterpret_cast<uint64_t>(TSUserArgGet(resources.state.txnp, 
_txn_private_slot));
     if (private_data.timezone == 1) {
       gmtime_r(&now, &res);
     } else {
@@ -826,9 +826,9 @@ void
 ConditionGeo::append_value(std::string &s, const Resources &res)
 {
   if (is_int_type()) {
-    s += std::to_string(get_geo_int(getClientAddr(res.txnp, 
_txn_private_slot)));
+    s += std::to_string(get_geo_int(getClientAddr(res.state.txnp, 
_txn_private_slot)));
   } else {
-    s += get_geo_string(getClientAddr(res.txnp, _txn_private_slot));
+    s += get_geo_string(getClientAddr(res.state.txnp, _txn_private_slot));
   }
   Dbg(pi_dbg_ctl, "Appending GEO() to evaluation value -> %s", s.c_str());
 }
@@ -840,7 +840,7 @@ ConditionGeo::eval(const Resources &res)
 
   Dbg(pi_dbg_ctl, "Evaluating GEO()");
   if (is_int_type()) {
-    int64_t geo = get_geo_int(getClientAddr(res.txnp, _txn_private_slot));
+    int64_t geo = get_geo_int(getClientAddr(res.state.txnp, 
_txn_private_slot));
 
     ret = static_cast<const Matchers<int64_t> *>(_matcher)->test(geo, res);
   } else {
@@ -899,7 +899,7 @@ ConditionId::append_value(std::string &s, const Resources 
&res ATS_UNUSED)
 {
   switch (_id_qual) {
   case ID_QUAL_REQUEST: {
-    s += std::to_string(TSHttpTxnIdGet(res.txnp));
+    s += std::to_string(TSHttpTxnIdGet(res.state.txnp));
   } break;
   case ID_QUAL_PROCESS: {
     TSUuid process = TSProcessUuidGet();
@@ -911,7 +911,7 @@ ConditionId::append_value(std::string &s, const Resources 
&res ATS_UNUSED)
   case ID_QUAL_UNIQUE: {
     char uuid[TS_CRUUID_STRING_LEN + 1];
 
-    if (TS_SUCCESS == TSClientRequestUuidGet(res.txnp, uuid)) {
+    if (TS_SUCCESS == TSClientRequestUuidGet(res.state.txnp, uuid)) {
       s += uuid;
     }
   } break;
@@ -923,7 +923,7 @@ bool
 ConditionId::eval(const Resources &res)
 {
   if (_id_qual == ID_QUAL_REQUEST) {
-    uint64_t id = TSHttpTxnIdGet(res.txnp);
+    uint64_t id = TSHttpTxnIdGet(res.state.txnp);
 
     Dbg(pi_dbg_ctl, "Evaluating GEO() -> %" PRIu64, id);
     return static_cast<const Matchers<uint64_t> *>(_matcher)->test(id, res);
@@ -997,7 +997,7 @@ ConditionCidr::eval(const Resources &res)
 void
 ConditionCidr::append_value(std::string &s, const Resources &res)
 {
-  struct sockaddr const *addr = getClientAddr(res.txnp, _txn_private_slot);
+  struct sockaddr const *addr = getClientAddr(res.state.txnp, 
_txn_private_slot);
 
   if (addr) {
     switch (addr->sa_family) {
@@ -1057,6 +1057,17 @@ ConditionInbound::initialize(Parser &p)
     match->set(p.get_arg(), mods());
     _matcher = match;
   }
+
+#if TS_HAS_CRIPTS
+  if (_net_qual >= NET_QUAL_CERT_PEM && _net_qual <= NET_QUAL_CERT_SAN_URI) {
+    if (_mtls_cert) {
+      require_resources(RSRC_MTLS_CERTIFICATE);
+    } else {
+      require_resources(RSRC_SERVER_CERTIFICATE);
+    }
+    require_resources(RSRC_CLIENT_CONNECTION);
+  }
+#endif
 }
 
 void
@@ -1086,6 +1097,68 @@ ConditionInbound::set_qualifier(const std::string &q)
     _net_qual = NET_QUAL_IP_FAMILY;
   } else if (q == "STACK") {
     _net_qual = NET_QUAL_STACK;
+#if TS_HAS_CRIPTS
+  } else if (q == "SERVER-CERT:PEM") {
+    _net_qual = NET_QUAL_CERT_PEM;
+  } else if (q == "SERVER-CERT:SIG") {
+    _net_qual = NET_QUAL_CERT_SIG;
+  } else if (q == "SERVER-CERT:SUBJECT") {
+    _net_qual = NET_QUAL_CERT_SUBJECT;
+  } else if (q == "SERVER-CERT:ISSUER") {
+    _net_qual = NET_QUAL_CERT_ISSUER;
+  } else if (q == "SERVER-CERT:SERIAL") {
+    _net_qual = NET_QUAL_CERT_SERIAL;
+  } else if (q == "SERVER-CERT:NOT_BEFORE") {
+    _net_qual = NET_QUAL_CERT_NOT_BEFORE;
+  } else if (q == "SERVER-CERT:NOT_AFTER") {
+    _net_qual = NET_QUAL_CERT_NOT_AFTER;
+  } else if (q == "SERVER-CERT:VERSION") {
+    _net_qual = NET_QUAL_CERT_VERSION;
+  } else if (q == "SERVER-CERT:SAN:DNS") {
+    _net_qual = NET_QUAL_CERT_SAN_DNS;
+  } else if (q == "SERVER-CERT:SAN:IP") {
+    _net_qual = NET_QUAL_CERT_SAN_IP;
+  } else if (q == "SERVER-CERT:SAN:EMAIL") {
+    _net_qual = NET_QUAL_CERT_SAN_EMAIL;
+  } else if (q == "SERVER-CERT:SAN:URI") {
+    _net_qual = NET_QUAL_CERT_SAN_URI;
+  } else if (q == "CLIENT-CERT:PEM") {
+    _net_qual  = NET_QUAL_CERT_PEM;
+    _mtls_cert = true;
+  } else if (q == "CLIENT-CERT:SIG") {
+    _net_qual  = NET_QUAL_CERT_SIG;
+    _mtls_cert = true;
+  } else if (q == "CLIENT-CERT:SUBJECT") {
+    _net_qual  = NET_QUAL_CERT_SUBJECT;
+    _mtls_cert = true;
+  } else if (q == "CLIENT-CERT:ISSUER") {
+    _net_qual  = NET_QUAL_CERT_ISSUER;
+    _mtls_cert = true;
+  } else if (q == "CLIENT-CERT:SERIAL") {
+    _net_qual  = NET_QUAL_CERT_SERIAL;
+    _mtls_cert = true;
+  } else if (q == "CLIENT-CERT:NOT_BEFORE") {
+    _net_qual  = NET_QUAL_CERT_NOT_BEFORE;
+    _mtls_cert = true;
+  } else if (q == "CLIENT-CERT:NOT_AFTER") {
+    _net_qual  = NET_QUAL_CERT_NOT_AFTER;
+    _mtls_cert = true;
+  } else if (q == "CLIENT-CERT:VERSION") {
+    _net_qual  = NET_QUAL_CERT_VERSION;
+    _mtls_cert = true;
+  } else if (q == "CLIENT-CERT:SAN:DNS") {
+    _net_qual  = NET_QUAL_CERT_SAN_DNS;
+    _mtls_cert = true;
+  } else if (q == "CLIENT-CERT:SAN:IP") {
+    _net_qual  = NET_QUAL_CERT_SAN_IP;
+    _mtls_cert = true;
+  } else if (q == "CLIENT-CERT:SAN:EMAIL") {
+    _net_qual  = NET_QUAL_CERT_SAN_EMAIL;
+    _mtls_cert = true;
+  } else if (q == "CLIENT-CERT:SAN:URI") {
+    _net_qual  = NET_QUAL_CERT_SAN_URI;
+    _mtls_cert = true;
+#endif
   } else {
     TSError("[%s] Unknown %s() qualifier: %s", PLUGIN_NAME, TAG, q.c_str());
   }
@@ -1100,10 +1173,10 @@ ConditionInbound::eval(const Resources &res)
 
     switch (_net_qual) {
     case NET_QUAL_LOCAL_ADDR:
-      addr = TSHttpTxnIncomingAddrGet(res.txnp);
+      addr = TSHttpTxnIncomingAddrGet(res.state.txnp);
       break;
     case NET_QUAL_REMOTE_ADDR:
-      addr = getClientAddr(res.txnp, _txn_private_slot);
+      addr = getClientAddr(res.state.txnp, _txn_private_slot);
       break;
     default:
       // Only support actual IP addresses of course...
@@ -1140,43 +1213,59 @@ ConditionInbound::append_value(std::string &s, const 
Resources &res, NetworkSess
   const char *zret = nullptr;
   char        text[INET6_ADDRSTRLEN];
 
+#if TS_HAS_CRIPTS
+  detail::CertBase *cert = nullptr;
+
+  if (_net_qual >= NET_QUAL_CERT_PEM && _net_qual <= NET_QUAL_CERT_SAN_URI) {
+    if (_mtls_cert) {
+      cert = static_cast<detail::CertBase *>(res.mtls_cert);
+    } else {
+      cert = static_cast<detail::CertBase *>(res.server_cert);
+    }
+
+    if (!cert) {
+      return;
+    }
+  }
+#endif
+
   switch (qual) {
   case NET_QUAL_LOCAL_ADDR: {
-    zret = getIP(TSHttpTxnIncomingAddrGet(res.txnp), text);
+    zret = getIP(TSHttpTxnIncomingAddrGet(res.state.txnp), text);
   } break;
   case NET_QUAL_LOCAL_PORT: {
-    uint16_t port = getPort(TSHttpTxnIncomingAddrGet(res.txnp));
+    uint16_t port = getPort(TSHttpTxnIncomingAddrGet(res.state.txnp));
     snprintf(text, sizeof(text), "%d", port);
     zret = text;
   } break;
   case NET_QUAL_REMOTE_ADDR: {
-    zret = getIP(getClientAddr(res.txnp, _txn_private_slot), text);
+    zret = getIP(getClientAddr(res.state.txnp, _txn_private_slot), text);
   } break;
   case NET_QUAL_REMOTE_PORT: {
-    uint16_t port = getPort(getClientAddr(res.txnp, _txn_private_slot));
+    uint16_t port = getPort(getClientAddr(res.state.txnp, _txn_private_slot));
     snprintf(text, sizeof(text), "%d", port);
     zret = text;
   } break;
   case NET_QUAL_TLS:
-    zret = TSHttpTxnClientProtocolStackContains(res.txnp, "tls/");
+    zret = TSHttpTxnClientProtocolStackContains(res.state.txnp, "tls/");
     break;
   case NET_QUAL_H2:
-    zret = TSHttpTxnClientProtocolStackContains(res.txnp, "h2");
+    zret = TSHttpTxnClientProtocolStackContains(res.state.txnp, "h2");
     break;
   case NET_QUAL_IPV4:
-    zret = TSHttpTxnClientProtocolStackContains(res.txnp, "ipv4");
+    zret = TSHttpTxnClientProtocolStackContains(res.state.txnp, "ipv4");
     break;
   case NET_QUAL_IPV6:
-    zret = TSHttpTxnClientProtocolStackContains(res.txnp, "ipv6");
+    zret = TSHttpTxnClientProtocolStackContains(res.state.txnp, "ipv6");
     break;
   case NET_QUAL_IP_FAMILY:
-    zret = TSHttpTxnClientProtocolStackContains(res.txnp, "ip");
+    zret = TSHttpTxnClientProtocolStackContains(res.state.txnp, "ip");
     break;
   case NET_QUAL_STACK: {
     std::array<char const *, 8> tags  = {};
     int                         count = 0;
     size_t                      len   = 0;
-    TSHttpTxnClientProtocolStackGet(res.txnp, tags.size(), tags.data(), 
&count);
+    TSHttpTxnClientProtocolStackGet(res.state.txnp, tags.size(), tags.data(), 
&count);
     for (int i = 0; i < count; ++i) {
       len += 1 + strlen(tags[i]);
     }
@@ -1188,6 +1277,44 @@ ConditionInbound::append_value(std::string &s, const 
Resources &res, NetworkSess
       s += tags[i];
     }
   } break;
+#if TS_HAS_CRIPTS
+  case NET_QUAL_CERT_PEM:
+    s += cert->certificate;
+    break;
+  case NET_QUAL_CERT_SIG:
+    s += cert->signature;
+    break;
+  case NET_QUAL_CERT_SUBJECT:
+    s += cert->subject;
+    break;
+  case NET_QUAL_CERT_ISSUER:
+    s += cert->issuer;
+    break;
+  case NET_QUAL_CERT_SERIAL:
+    s += cert->serialNumber;
+    break;
+  case NET_QUAL_CERT_NOT_BEFORE:
+    s += cert->notBefore;
+    break;
+  case NET_QUAL_CERT_NOT_AFTER:
+    s += cert->notAfter;
+    break;
+  case NET_QUAL_CERT_VERSION:
+    s += cert->version;
+    break;
+  case NET_QUAL_CERT_SAN_DNS:
+    s += cert->san.dns.Join(";");
+    break;
+  case NET_QUAL_CERT_SAN_IP:
+    s += cert->san.ipadd.Join(";");
+    break;
+  case NET_QUAL_CERT_SAN_EMAIL:
+    s += cert->san.email.Join(";");
+    break;
+  case NET_QUAL_CERT_SAN_URI:
+    s += cert->san.uri.Join(";");
+    break;
+#endif
   }
 
   if (zret) {
@@ -1231,7 +1358,7 @@ ConditionSessionTransactCount::initialize(Parser &p)
 bool
 ConditionSessionTransactCount::eval(const Resources &res)
 {
-  int const val = TSHttpTxnServerSsnTransactionCount(res.txnp);
+  int const val = TSHttpTxnServerSsnTransactionCount(res.state.txnp);
 
   Dbg(pi_dbg_ctl, "Evaluating SSN-TXN-COUNT()");
   return static_cast<MatcherType *>(_matcher)->test(val, res);
@@ -1241,7 +1368,7 @@ void
 ConditionSessionTransactCount::append_value(std::string &s, Resources const 
&res)
 {
   char      value[32]; // enough for UINT64_MAX
-  int const count  = TSHttpTxnServerSsnTransactionCount(res.txnp);
+  int const count  = TSHttpTxnServerSsnTransactionCount(res.state.txnp);
   int const length = ink_fast_itoa(count, value, sizeof(value));
 
   if (length > 0) {
@@ -1287,7 +1414,7 @@ void
 ConditionTcpInfo::append_value(std::string &s, [[maybe_unused]] Resources 
const &res)
 {
 #if defined(TCP_INFO) && defined(HAVE_STRUCT_TCP_INFO)
-  if (TSHttpTxnIsInternal(res.txnp)) {
+  if (TSHttpTxnIsInternal(res.state.txnp)) {
     Dbg(pi_dbg_ctl, "No TCP-INFO available for internal transactions");
     return;
   }
@@ -1295,7 +1422,7 @@ ConditionTcpInfo::append_value(std::string &s, 
[[maybe_unused]] Resources const
   int             fd;
   struct tcp_info info;
   socklen_t       tcp_info_len = sizeof(info);
-  tsSsn                        = TSHttpTxnClientFdGet(res.txnp, &fd);
+  tsSsn                        = TSHttpTxnClientFdGet(res.state.txnp, &fd);
   if (tsSsn != TS_SUCCESS || fd <= 0) {
     Dbg(pi_dbg_ctl, "error getting the client socket fd from ssn");
   }
@@ -1347,7 +1474,7 @@ ConditionCache::eval(const Resources &res)
 void
 ConditionCache::append_value(std::string &s, const Resources &res)
 {
-  TSHttpTxn txn = res.txnp;
+  TSHttpTxn txn = res.state.txnp;
   int       status;
 
   static const char *names[] = {
@@ -1394,7 +1521,7 @@ ConditionNextHop::append_value(std::string &s, const 
Resources &res)
 {
   switch (_next_hop_qual) {
   case NEXT_HOP_HOST: {
-    char const *const name = TSHttpTxnNextHopNameGet(res.txnp);
+    char const *const name = TSHttpTxnNextHopNameGet(res.state.txnp);
     if (nullptr != name) {
       Dbg(pi_dbg_ctl, "Appending '%s' to evaluation value", name);
       s.append(name);
@@ -1403,7 +1530,7 @@ ConditionNextHop::append_value(std::string &s, const 
Resources &res)
     }
   } break;
   case NEXT_HOP_PORT: {
-    int const port = TSHttpTxnNextHopPortGet(res.txnp);
+    int const port = TSHttpTxnNextHopPortGet(res.state.txnp);
     Dbg(pi_dbg_ctl, "Appending '%d' to evaluation value", port);
     s.append(std::to_string(port));
   } break;
@@ -1436,7 +1563,7 @@ ConditionHttpCntl::set_qualifier(const std::string &q)
 void
 ConditionHttpCntl::append_value(std::string &s, const Resources &res)
 {
-  s += TSHttpTxnCntlGet(res.txnp, _http_cntl_qual) ? "TRUE" : "FALSE";
+  s += TSHttpTxnCntlGet(res.state.txnp, _http_cntl_qual) ? "TRUE" : "FALSE";
   Dbg(pi_dbg_ctl, "Evaluating HTTP-CNTL(%s)", _qualifier.c_str());
 }
 
@@ -1444,7 +1571,7 @@ bool
 ConditionHttpCntl::eval(const Resources &res)
 {
   Dbg(pi_dbg_ctl, "Evaluating HTTP-CNTL()");
-  return TSHttpTxnCntlGet(res.txnp, _http_cntl_qual);
+  return TSHttpTxnCntlGet(res.state.txnp, _http_cntl_qual);
 }
 
 // ConditionStateFlag
@@ -1472,7 +1599,7 @@ ConditionStateFlag::append_value(std::string &s, const 
Resources &res)
 bool
 ConditionStateFlag::eval(const Resources &res)
 {
-  auto data = reinterpret_cast<uint64_t>(TSUserArgGet(res.txnp, _txn_slot));
+  auto data = reinterpret_cast<uint64_t>(TSUserArgGet(res.state.txnp, 
_txn_slot));
 
   Dbg(pi_dbg_ctl, "Evaluating STATE-FLAG()");
 
diff --git a/plugins/header_rewrite/conditions.h 
b/plugins/header_rewrite/conditions.h
index 8d3edfec93..10d05fbaac 100644
--- a/plugins/header_rewrite/conditions.h
+++ b/plugins/header_rewrite/conditions.h
@@ -552,7 +552,10 @@ protected:
 
 private:
   NetworkSessionQualifiers _net_qual = NET_QUAL_STACK;
-  void                     append_value(std::string &s, const Resources &res, 
NetworkSessionQualifiers qual);
+#if TS_HAS_CRIPTS
+  bool _mtls_cert = false;
+#endif
+  void append_value(std::string &s, const Resources &res, 
NetworkSessionQualifiers qual);
 };
 
 class ConditionStringLiteral : public Condition
@@ -810,7 +813,7 @@ private:
   _get_data(const Resources &res) const
   {
     TSAssert(_byte_ix >= 0 && _byte_ix < NUM_STATE_INT8S);
-    auto    ptr  = reinterpret_cast<uint64_t>(TSUserArgGet(res.txnp, 
_txn_slot));
+    auto    ptr  = reinterpret_cast<uint64_t>(TSUserArgGet(res.state.txnp, 
_txn_slot));
     uint8_t data = (ptr & STATE_INT8_MASKS[_byte_ix]) >> (NUM_STATE_FLAGS + 
_byte_ix * 8);
 
     return data;
@@ -853,7 +856,7 @@ private:
   uint16_t
   _get_data(const Resources &res) const
   {
-    auto ptr = reinterpret_cast<uint64_t>(TSUserArgGet(res.txnp, _txn_slot));
+    auto ptr = reinterpret_cast<uint64_t>(TSUserArgGet(res.state.txnp, 
_txn_slot));
 
     return ((ptr & STATE_INT16_MASK) >> 48);
   }
diff --git a/plugins/header_rewrite/header_rewrite.cc 
b/plugins/header_rewrite/header_rewrite.cc
index b4f2da2505..91ac9a7f4c 100644
--- a/plugins/header_rewrite/header_rewrite.cc
+++ b/plugins/header_rewrite/header_rewrite.cc
@@ -300,7 +300,7 @@ RulesConfig::parse_config(const std::string &fname, 
TSHttpHookID default_hook, c
   }
 
   // Collect all resource IDs that we need
-  for (int i = TS_HTTP_READ_REQUEST_HDR_HOOK; i < TS_HTTP_LAST_HOOK; ++i) {
+  for (int i = TS_HTTP_READ_REQUEST_HDR_HOOK; i <= TS_HTTP_LAST_HOOK; ++i) {
     if (_rules[i]) {
       _resids[i] = _rules[i]->get_all_resource_ids();
     }
@@ -573,22 +573,24 @@ TSRemapDoRemap(void *ih, TSHttpTxn rh, TSRemapRequestInfo 
*rri)
   RuleSet  *rule = conf->rule(TS_REMAP_PSEUDO_HOOK);
   Resources res(rh, rri);
 
-  res.gather(RSRC_CLIENT_REQUEST_HEADERS, TS_REMAP_PSEUDO_HOOK);
-  while (rule) {
-    const RuleSet::OperatorPair &ops = rule->eval(res);
-    const OperModifiers          rt  = rule->exec(ops, res);
+  if (rule) {
+    res.gather(conf->resid(TS_REMAP_PSEUDO_HOOK), TS_REMAP_PSEUDO_HOOK);
 
-    ink_assert((rt & OPER_NO_REENABLE) == 0);
+    do {
+      const RuleSet::OperatorPair &ops = rule->eval(res);
+      const OperModifiers          rt  = rule->exec(ops, res);
 
-    if (res.changed_url == true) {
-      rval = TSREMAP_DID_REMAP;
-    }
+      ink_assert((rt & OPER_NO_REENABLE) == 0);
 
-    if (rule->last() || (rt & OPER_LAST)) {
-      break; // Conditional break, force a break with [L]
-    }
+      if (res.changed_url == true) {
+        rval = TSREMAP_DID_REMAP;
+      }
+
+      if (rule->last() || (rt & OPER_LAST)) {
+        break; // Conditional break, force a break with [L]
+      }
 
-    rule = rule->next;
+    } while ((rule = rule->next));
   }
 
   Dbg(dbg_ctl, "Returning from TSRemapDoRemap with status: %d", rval);
diff --git a/plugins/header_rewrite/operators.cc 
b/plugins/header_rewrite/operators.cc
index 9666231491..126a928879 100644
--- a/plugins/header_rewrite/operators.cc
+++ b/plugins/header_rewrite/operators.cc
@@ -52,6 +52,7 @@ handleFetchEvents(TSCont cont, TSEvent event, void *edata)
       TSHttpParser parser   = TSHttpParserCreate();
       TSMBuffer    hdr_buf  = TSMBufferCreate();
       TSMLoc       hdr_loc  = TSHttpHdrCreate(hdr_buf);
+
       TSHttpHdrTypeSet(hdr_buf, hdr_loc, TS_HTTP_TYPE_RESPONSE);
       if (TSHttpHdrParseResp(parser, hdr_buf, hdr_loc, &data_start, data_end) 
== TS_PARSE_DONE) {
         TSHttpTxnErrorBodySet(http_txn, TSstrdup(data_start), (data_end - 
data_start), nullptr);
@@ -120,7 +121,7 @@ OperatorSetConfig::initialize(Parser &p)
   _config = p.get_arg();
 
   if (TS_SUCCESS == TSHttpTxnConfigFind(_config.c_str(), _config.size(), 
&_key, &_type)) {
-    _value.set_value(p.get_value());
+    _value.set_value(p.get_value(), this);
   } else {
     _key = TS_CONFIG_NULL;
     TSError("[%s] no such records config: %s", PLUGIN_NAME, _config.c_str());
@@ -133,21 +134,21 @@ OperatorSetConfig::exec(const Resources &res) const
   if (TS_CONFIG_NULL != _key) {
     switch (_type) {
     case TS_RECORDDATATYPE_INT:
-      if (TS_SUCCESS == TSHttpTxnConfigIntSet(res.txnp, _key, 
_value.get_int_value())) {
+      if (TS_SUCCESS == TSHttpTxnConfigIntSet(res.state.txnp, _key, 
_value.get_int_value())) {
         Dbg(pi_dbg_ctl, "OperatorSetConfig::exec() invoked on %s=%d", 
_config.c_str(), _value.get_int_value());
       } else {
         Dbg(pi_dbg_ctl, "OperatorSetConfig::exec() invocation failed on 
%s=%d", _config.c_str(), _value.get_int_value());
       }
       break;
     case TS_RECORDDATATYPE_FLOAT:
-      if (TS_SUCCESS == TSHttpTxnConfigFloatSet(res.txnp, _key, 
_value.get_float_value())) {
+      if (TS_SUCCESS == TSHttpTxnConfigFloatSet(res.state.txnp, _key, 
_value.get_float_value())) {
         Dbg(pi_dbg_ctl, "OperatorSetConfig::exec() invoked on %s=%f", 
_config.c_str(), _value.get_float_value());
       } else {
         Dbg(pi_dbg_ctl, "OperatorSetConfig::exec() invocation failed on 
%s=%f", _config.c_str(), _value.get_float_value());
       }
       break;
     case TS_RECORDDATATYPE_STRING:
-      if (TS_SUCCESS == TSHttpTxnConfigStringSet(res.txnp, _key, 
_value.get_value().c_str(), _value.size())) {
+      if (TS_SUCCESS == TSHttpTxnConfigStringSet(res.state.txnp, _key, 
_value.get_value().c_str(), _value.size())) {
         Dbg(pi_dbg_ctl, "OperatorSetConfig::exec() invoked on %s=%s", 
_config.c_str(), _value.get_value().c_str());
       } else {
         Dbg(pi_dbg_ctl, "OperatorSetConfig::exec() invocation failed on 
%s=%s", _config.c_str(), _value.get_value().c_str());
@@ -167,7 +168,7 @@ OperatorSetStatus::initialize(Parser &p)
 {
   Operator::initialize(p);
 
-  _status.set_value(p.get_arg());
+  _status.set_value(p.get_arg(), this);
 
   if (nullptr == (_reason = 
TSHttpHdrReasonLookup(static_cast<TSHttpStatus>(_status.get_int_value())))) {
     TSError("[%s] unknown status %d", PLUGIN_NAME, _status.get_int_value());
@@ -205,7 +206,7 @@ OperatorSetStatus::exec(const Resources &res) const
     }
     break;
   default:
-    TSHttpTxnStatusSet(res.txnp, 
static_cast<TSHttpStatus>(_status.get_int_value()));
+    TSHttpTxnStatusSet(res.state.txnp, 
static_cast<TSHttpStatus>(_status.get_int_value()));
     break;
   }
 
@@ -220,7 +221,7 @@ OperatorSetStatusReason::initialize(Parser &p)
 {
   Operator::initialize(p);
 
-  _reason.set_value(p.get_arg());
+  _reason.set_value(p.get_arg(), this);
   require_resources(RSRC_CLIENT_RESPONSE_HEADERS);
   require_resources(RSRC_SERVER_RESPONSE_HEADERS);
 }
@@ -254,7 +255,7 @@ OperatorSetDestination::initialize(Parser &p)
   Operator::initialize(p);
 
   _url_qual = parse_url_qualifier(p.get_arg());
-  _value.set_value(p.get_value());
+  _value.set_value(p.get_value(), this);
   require_resources(RSRC_CLIENT_REQUEST_HEADERS);
   require_resources(RSRC_SERVER_REQUEST_HEADERS);
 }
@@ -264,10 +265,10 @@ OperatorSetDestination::exec(const Resources &res) const
 {
   if (res._rri || (res.bufp && res.hdr_loc)) {
     std::string value;
+    TSMBuffer   bufp;
+    TSMLoc      url_m_loc;
 
     // Determine which TSMBuffer and TSMLoc to use
-    TSMBuffer bufp;
-    TSMLoc    url_m_loc;
     if (res._rri && !res.changed_url) {
       bufp      = res._rri->requestBufp;
       url_m_loc = res._rri->requestUrl;
@@ -480,8 +481,8 @@ OperatorSetRedirect::initialize(Parser &p)
 {
   Operator::initialize(p);
 
-  _status.set_value(p.get_arg());
-  _location.set_value(p.get_value());
+  _status.set_value(p.get_arg(), this);
+  _location.set_value(p.get_value(), this);
   auto status = _status.get_int_value();
   if (status < 300 || status > 399 || status == TS_HTTP_STATUS_NOT_MODIFIED) {
     TSError("[%s] unsupported redirect status %d", PLUGIN_NAME, status);
@@ -590,7 +591,7 @@ OperatorSetRedirect::exec(const Resources &res) const
         Dbg(pi_dbg_ctl, "Could not set Location field value to: %s", 
value.c_str());
       }
       // Set the new status.
-      TSHttpTxnStatusSet(res.txnp, 
static_cast<TSHttpStatus>(_status.get_int_value()));
+      TSHttpTxnStatusSet(res.state.txnp, 
static_cast<TSHttpStatus>(_status.get_int_value()));
       const_cast<Resources &>(res).changed_url = true;
       res._rri->redirect                       = 1;
     } else {
@@ -598,7 +599,7 @@ OperatorSetRedirect::exec(const Resources &res) const
       // Set the new status code and reason.
       TSHttpStatus status = static_cast<TSHttpStatus>(_status.get_int_value());
       TSHttpHdrStatusSet(res.bufp, res.hdr_loc, status);
-      EditRedirectResponse(res.txnp, value, status, res.bufp, res.hdr_loc);
+      EditRedirectResponse(res.state.txnp, value, status, res.bufp, 
res.hdr_loc);
     }
     Dbg(pi_dbg_ctl, "OperatorSetRedirect::exec() invoked with destination=%s 
and status code=%d", value.c_str(),
         _status.get_int_value());
@@ -625,7 +626,7 @@ OperatorSetTimeoutOut::initialize(Parser &p)
     TSError("[%s] unsupported timeout qualifier: %s", PLUGIN_NAME, 
p.get_arg().c_str());
   }
 
-  _timeout.set_value(p.get_value());
+  _timeout.set_value(p.get_value(), this);
 }
 
 bool
@@ -634,22 +635,22 @@ OperatorSetTimeoutOut::exec(const Resources &res) const
   switch (_type) {
   case TO_OUT_ACTIVE:
     Dbg(pi_dbg_ctl, "OperatorSetTimeoutOut::exec(active, %d)", 
_timeout.get_int_value());
-    TSHttpTxnActiveTimeoutSet(res.txnp, _timeout.get_int_value());
+    TSHttpTxnActiveTimeoutSet(res.state.txnp, _timeout.get_int_value());
     break;
 
   case TO_OUT_INACTIVE:
     Dbg(pi_dbg_ctl, "OperatorSetTimeoutOut::exec(inactive, %d)", 
_timeout.get_int_value());
-    TSHttpTxnNoActivityTimeoutSet(res.txnp, _timeout.get_int_value());
+    TSHttpTxnNoActivityTimeoutSet(res.state.txnp, _timeout.get_int_value());
     break;
 
   case TO_OUT_CONNECT:
     Dbg(pi_dbg_ctl, "OperatorSetTimeoutOut::exec(connect, %d)", 
_timeout.get_int_value());
-    TSHttpTxnConnectTimeoutSet(res.txnp, _timeout.get_int_value());
+    TSHttpTxnConnectTimeoutSet(res.state.txnp, _timeout.get_int_value());
     break;
 
   case TO_OUT_DNS:
     Dbg(pi_dbg_ctl, "OperatorSetTimeoutOut::exec(dns, %d)", 
_timeout.get_int_value());
-    TSHttpTxnDNSTimeoutSet(res.txnp, _timeout.get_int_value());
+    TSHttpTxnDNSTimeoutSet(res.state.txnp, _timeout.get_int_value());
     break;
   default:
     TSError("[%s] unsupported timeout", PLUGIN_NAME);
@@ -674,7 +675,7 @@ bool
 OperatorSkipRemap::exec(const Resources &res) const
 {
   Dbg(pi_dbg_ctl, "OperatorSkipRemap::exec() skipping remap: %s", _skip_remap 
? "True" : "False");
-  TSHttpTxnCntlSet(res.txnp, TS_HTTP_CNTL_SKIP_REMAPPING, _skip_remap);
+  TSHttpTxnCntlSet(res.state.txnp, TS_HTTP_CNTL_SKIP_REMAPPING, _skip_remap);
   return true;
 }
 
@@ -704,7 +705,7 @@ OperatorAddHeader::initialize(Parser &p)
 {
   OperatorHeaders::initialize(p);
 
-  _value.set_value(p.get_value());
+  _value.set_value(p.get_value(), this);
 }
 
 bool
@@ -741,7 +742,7 @@ OperatorSetHeader::initialize(Parser &p)
 {
   OperatorHeaders::initialize(p);
 
-  _value.set_value(p.get_value());
+  _value.set_value(p.get_value(), this);
 }
 
 bool
@@ -799,7 +800,7 @@ OperatorSetBody::initialize(Parser &p)
 {
   Operator::initialize(p);
   // we want the arg since body only takes one value
-  _value.set_value(p.get_arg());
+  _value.set_value(p.get_arg(), this);
 }
 
 void
@@ -819,7 +820,7 @@ OperatorSetBody::exec(const Resources &res) const
   if (!value.empty()) {
     msg = TSstrdup(value.c_str());
   }
-  TSHttpTxnErrorBodySet(res.txnp, msg, value.size(), nullptr);
+  TSHttpTxnErrorBodySet(res.state.txnp, msg, value.size(), nullptr);
   return true;
 }
 
@@ -901,7 +902,7 @@ void
 OperatorAddCookie::initialize(Parser &p)
 {
   OperatorCookies::initialize(p);
-  _value.set_value(p.get_value());
+  _value.set_value(p.get_value(), this);
 }
 
 bool
@@ -947,7 +948,7 @@ void
 OperatorSetCookie::initialize(Parser &p)
 {
   OperatorCookies::initialize(p);
-  _value.set_value(p.get_value());
+  _value.set_value(p.get_value(), this);
 }
 
 bool
@@ -1085,7 +1086,7 @@ OperatorSetConnDSCP::initialize(Parser &p)
 {
   Operator::initialize(p);
 
-  _ds_value.set_value(p.get_arg());
+  _ds_value.set_value(p.get_arg(), this);
 }
 
 void
@@ -1099,8 +1100,8 @@ OperatorSetConnDSCP::initialize_hooks()
 bool
 OperatorSetConnDSCP::exec(const Resources &res) const
 {
-  if (res.txnp) {
-    TSHttpTxnClientPacketDscpSet(res.txnp, _ds_value.get_int_value());
+  if (res.state.txnp) {
+    TSHttpTxnClientPacketDscpSet(res.state.txnp, _ds_value.get_int_value());
     Dbg(pi_dbg_ctl, "   Setting DSCP to %d", _ds_value.get_int_value());
   }
   return true;
@@ -1112,7 +1113,7 @@ OperatorSetConnMark::initialize(Parser &p)
 {
   Operator::initialize(p);
 
-  _ds_value.set_value(p.get_arg());
+  _ds_value.set_value(p.get_arg(), this);
 }
 
 void
@@ -1126,8 +1127,8 @@ OperatorSetConnMark::initialize_hooks()
 bool
 OperatorSetConnMark::exec(const Resources &res) const
 {
-  if (res.txnp) {
-    TSHttpTxnClientPacketMarkSet(res.txnp, _ds_value.get_int_value());
+  if (res.state.txnp) {
+    TSHttpTxnClientPacketMarkSet(res.state.txnp, _ds_value.get_int_value());
     Dbg(pi_dbg_ctl, "   Setting MARK to %d", _ds_value.get_int_value());
   }
   return true;
@@ -1152,7 +1153,7 @@ OperatorSetDebug::initialize_hooks()
 bool
 OperatorSetDebug::exec(const Resources &res) const
 {
-  TSHttpTxnCntlSet(res.txnp, TS_HTTP_CNTL_TXN_DEBUG, true);
+  TSHttpTxnCntlSet(res.state.txnp, TS_HTTP_CNTL_TXN_DEBUG, true);
   return true;
 }
 
@@ -1189,10 +1190,10 @@ bool
 OperatorSetHttpCntl::exec(const Resources &res) const
 {
   if (_flag) {
-    TSHttpTxnCntlSet(res.txnp, _cntl_qual, true);
+    TSHttpTxnCntlSet(res.state.txnp, _cntl_qual, true);
     Dbg(pi_dbg_ctl, "   Turning ON %s for transaction", 
HttpCntls[static_cast<size_t>(_cntl_qual)]);
   } else {
-    TSHttpTxnCntlSet(res.txnp, _cntl_qual, false);
+    TSHttpTxnCntlSet(res.state.txnp, _cntl_qual, false);
     Dbg(pi_dbg_ctl, "   Turning OFF %s for transaction", 
HttpCntls[static_cast<size_t>(_cntl_qual)]);
   }
   return true;
@@ -1244,7 +1245,7 @@ bool
 OperatorSetPluginCntl::exec(const Resources &res) const
 {
   PrivateSlotData private_data;
-  private_data.raw = reinterpret_cast<uint64_t>(TSUserArgGet(res.txnp, 
_txn_private_slot));
+  private_data.raw = reinterpret_cast<uint64_t>(TSUserArgGet(res.state.txnp, 
_txn_private_slot));
 
   switch (_name) {
   case PluginCtrl::TIMEZONE:
@@ -1256,7 +1257,7 @@ OperatorSetPluginCntl::exec(const Resources &res) const
   }
 
   Dbg(pi_dbg_ctl, "   Setting plugin control %d to %d", 
static_cast<int>(_name), _value);
-  TSUserArgSet(res.txnp, _txn_private_slot, reinterpret_cast<void 
*>(private_data.raw));
+  TSUserArgSet(res.state.txnp, _txn_private_slot, reinterpret_cast<void 
*>(private_data.raw));
 
   return true;
 }
@@ -1326,8 +1327,8 @@ OperatorRunPlugin::exec(const Resources &res) const
 {
   TSReleaseAssert(_plugin != nullptr);
 
-  if (res._rri && res.txnp) {
-    _plugin->doRemap(res.txnp, res._rri);
+  if (res._rri && res.state.txnp) {
+    _plugin->doRemap(res.state.txnp, res._rri);
   }
   return true;
 }
@@ -1338,7 +1339,7 @@ OperatorSetBodyFrom::initialize(Parser &p)
 {
   Operator::initialize(p);
   // we want the arg since body only takes one value
-  _value.set_value(p.get_arg());
+  _value.set_value(p.get_arg(), this);
   require_resources(RSRC_SERVER_RESPONSE_HEADERS);
   require_resources(RSRC_RESPONSE_STATUS);
 }
@@ -1352,7 +1353,7 @@ OperatorSetBodyFrom::initialize_hooks()
 bool
 OperatorSetBodyFrom::exec(const Resources &res) const
 {
-  if (TSHttpTxnIsInternal(res.txnp)) {
+  if (TSHttpTxnIsInternal(res.state.txnp)) {
     // If this is triggered by an internal transaction, a infinte loop may 
occur
     // It should only be triggered by the original transaction sent by the 
client
     Dbg(pi_dbg_ctl, "OperatorSetBodyFrom triggered by an internal 
transaction");
@@ -1363,9 +1364,9 @@ OperatorSetBodyFrom::exec(const Resources &res) const
   int  req_buf_size = 0;
   if (createRequestString(_value.get_value(), req_buf, &req_buf_size) == 
TS_SUCCESS) {
     TSCont fetchCont = TSContCreate(handleFetchEvents, TSMutexCreate());
-    TSContDataSet(fetchCont, static_cast<void *>(res.txnp));
+    TSContDataSet(fetchCont, static_cast<void *>(res.state.txnp));
 
-    TSHttpTxnHookAdd(res.txnp, TS_HTTP_TXN_CLOSE_HOOK, fetchCont);
+    TSHttpTxnHookAdd(res.state.txnp, TS_HTTP_TXN_CLOSE_HOOK, fetchCont);
 
     TSFetchEvent event_ids;
     event_ids.success_event_id = TS_EVENT_FETCHSM_SUCCESS;
@@ -1382,7 +1383,7 @@ OperatorSetBodyFrom::exec(const Resources &res) const
     // Forces original status code in event TSHttpTxnErrorBodySet changed
     // the code or another condition was set conflicting with this one.
     // Set here because res is the only structure that contains the original 
status code.
-    TSHttpTxnStatusSet(res.txnp, res.resp_status);
+    TSHttpTxnStatusSet(res.state.txnp, res.resp_status);
   } else {
     TSError(PLUGIN_NAME, "OperatorSetBodyFrom:exec:: Could not create 
request");
     return true;
@@ -1432,16 +1433,16 @@ OperatorSetStateFlag::initialize_hooks()
 bool
 OperatorSetStateFlag::exec(const Resources &res) const
 {
-  if (!res.txnp) {
+  if (!res.state.txnp) {
     TSError("[%s] OperatorSetStateFlag() failed. Transaction is null", 
PLUGIN_NAME);
     return false;
   }
 
   Dbg(pi_dbg_ctl, "   Setting state flag %d to %d", _flag_ix, _flag);
 
-  auto data = reinterpret_cast<uint64_t>(TSUserArgGet(res.txnp, _txn_slot));
+  auto data = reinterpret_cast<uint64_t>(TSUserArgGet(res.state.txnp, 
_txn_slot));
 
-  TSUserArgSet(res.txnp, _txn_slot, reinterpret_cast<void *>(_flag ? data | 
_mask : data & _mask));
+  TSUserArgSet(res.state.txnp, _txn_slot, reinterpret_cast<void *>(_flag ? 
data | _mask : data & _mask));
 
   return true;
 }
@@ -1458,7 +1459,7 @@ OperatorSetStateInt8::initialize(Parser &p)
     return;
   }
 
-  _value.set_value(p.get_value());
+  _value.set_value(p.get_value(), this);
   if (!_value.has_conds()) {
     int v = _value.get_int_value();
 
@@ -1486,12 +1487,12 @@ OperatorSetStateInt8::initialize_hooks()
 bool
 OperatorSetStateInt8::exec(const Resources &res) const
 {
-  if (!res.txnp) {
+  if (!res.state.txnp) {
     TSError("[%s] OperatorSetStateInt8() failed. Transaction is null", 
PLUGIN_NAME);
     return false;
   }
 
-  auto ptr = reinterpret_cast<uint64_t>(TSUserArgGet(res.txnp, _txn_slot));
+  auto ptr = reinterpret_cast<uint64_t>(TSUserArgGet(res.state.txnp, 
_txn_slot));
   int  val = 0;
 
   if (_value.has_conds()) { // If there are conditions, we need to evaluate 
them, which gives us a string
@@ -1511,7 +1512,7 @@ OperatorSetStateInt8::exec(const Resources &res) const
   Dbg(pi_dbg_ctl, "   Setting state int8 %d to %d", _byte_ix, val);
   ptr &= ~STATE_INT8_MASKS[_byte_ix]; // Clear any old value
   ptr |= (static_cast<uint64_t>(val) << (NUM_STATE_FLAGS + _byte_ix * 8));
-  TSUserArgSet(res.txnp, _txn_slot, reinterpret_cast<void *>(ptr));
+  TSUserArgSet(res.state.txnp, _txn_slot, reinterpret_cast<void *>(ptr));
 
   return true;
 }
@@ -1528,7 +1529,7 @@ OperatorSetStateInt16::initialize(Parser &p)
     return;
   }
 
-  _value.set_value(p.get_value());
+  _value.set_value(p.get_value(), this);
   if (!_value.has_conds()) {
     int v = _value.get_int_value();
 
@@ -1556,12 +1557,12 @@ OperatorSetStateInt16::initialize_hooks()
 bool
 OperatorSetStateInt16::exec(const Resources &res) const
 {
-  if (!res.txnp) {
+  if (!res.state.txnp) {
     TSError("[%s] OperatorSetStateInt16() failed. Transaction is null", 
PLUGIN_NAME);
     return false;
   }
 
-  auto ptr = reinterpret_cast<uint64_t>(TSUserArgGet(res.txnp, _txn_slot));
+  auto ptr = reinterpret_cast<uint64_t>(TSUserArgGet(res.state.txnp, 
_txn_slot));
   int  val = 0;
 
   if (_value.has_conds()) { // If there are conditions, we need to evaluate 
them, which gives us a string
@@ -1581,7 +1582,7 @@ OperatorSetStateInt16::exec(const Resources &res) const
   Dbg(pi_dbg_ctl, "   Setting state int16 to %d", val);
   ptr &= ~STATE_INT16_MASK; // Clear any old value
   ptr |= (static_cast<uint64_t>(val) << 48);
-  TSUserArgSet(res.txnp, _txn_slot, reinterpret_cast<void *>(ptr));
+  TSUserArgSet(res.state.txnp, _txn_slot, reinterpret_cast<void *>(ptr));
 
   return true;
 }
diff --git a/plugins/header_rewrite/resources.cc 
b/plugins/header_rewrite/resources.cc
index d54eb7d0ac..7f5a755fc8 100644
--- a/plugins/header_rewrite/resources.cc
+++ b/plugins/header_rewrite/resources.cc
@@ -24,6 +24,10 @@
 #include "resources.h"
 #include "lulu.h"
 
+#if TS_HAS_CRIPTS
+#include "cripts/Connections.hpp"
+#endif
+
 // Collect all resources
 void
 Resources::gather(const ResourceIDs ids, TSHttpHookID hook)
@@ -34,10 +38,12 @@ Resources::gather(const ResourceIDs ids, TSHttpHookID hook)
   ovector_count = 0;
   ovector_ptr   = nullptr;
 
+  Dbg(pi_dbg_ctl, "Gathering resources for hook %s with IDs %d", 
TSHttpHookNameLookup(hook), ids);
+
   // 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");
-    if (TSHttpTxnClientReqGet(txnp, &client_bufp, &client_hdr_loc) != 
TS_SUCCESS) {
+    if (TSHttpTxnClientReqGet(state.txnp, &client_bufp, &client_hdr_loc) != 
TS_SUCCESS) {
       Dbg(pi_dbg_ctl, "could not gather bufp/hdr_loc for request");
       return;
     }
@@ -48,7 +54,7 @@ Resources::gather(const ResourceIDs ids, TSHttpHookID hook)
     // Read response headers from server
     if ((ids & RSRC_SERVER_RESPONSE_HEADERS) || (ids & RSRC_RESPONSE_STATUS)) {
       Dbg(pi_dbg_ctl, "\tAdding TXN server response header buffers");
-      if (TSHttpTxnServerRespGet(txnp, &bufp, &hdr_loc) != TS_SUCCESS) {
+      if (TSHttpTxnServerRespGet(state.txnp, &bufp, &hdr_loc) != TS_SUCCESS) {
         Dbg(pi_dbg_ctl, "could not gather bufp/hdr_loc for response");
         return;
       }
@@ -64,7 +70,7 @@ Resources::gather(const ResourceIDs ids, TSHttpHookID hook)
     // Read request headers to server
     if (ids & RSRC_SERVER_REQUEST_HEADERS) {
       Dbg(pi_dbg_ctl, "\tAdding TXN server request header buffers");
-      if (TSHttpTxnServerReqGet(txnp, &bufp, &hdr_loc) != TS_SUCCESS) {
+      if (TSHttpTxnServerReqGet(state.txnp, &bufp, &hdr_loc) != TS_SUCCESS) {
         Dbg(pi_dbg_ctl, "could not gather bufp/hdr_loc for request");
         return;
       }
@@ -84,7 +90,7 @@ Resources::gather(const ResourceIDs ids, TSHttpHookID hook)
     // Send response headers to client
     if (ids & RSRC_CLIENT_RESPONSE_HEADERS) {
       Dbg(pi_dbg_ctl, "\tAdding TXN client response header buffers");
-      if (TSHttpTxnClientRespGet(txnp, &bufp, &hdr_loc) != TS_SUCCESS) {
+      if (TSHttpTxnClientRespGet(state.txnp, &bufp, &hdr_loc) != TS_SUCCESS) {
         Dbg(pi_dbg_ctl, "could not gather bufp/hdr_loc for request");
         return;
       }
@@ -116,7 +122,7 @@ Resources::gather(const ResourceIDs ids, TSHttpHookID hook)
   case TS_HTTP_TXN_CLOSE_HOOK:
     // Get TCP Info at transaction close
     Dbg(pi_dbg_ctl, "\tAdding TXN close buffers");
-    if (TSHttpTxnClientRespGet(txnp, &bufp, &hdr_loc) != TS_SUCCESS) {
+    if (TSHttpTxnClientRespGet(state.txnp, &bufp, &hdr_loc) != TS_SUCCESS) {
       Dbg(pi_dbg_ctl, "could not gather bufp/hdr_loc for request");
       return;
     }
@@ -126,6 +132,32 @@ Resources::gather(const ResourceIDs ids, TSHttpHookID hook)
     break;
   }
 
+  // The following is all the new infrastructure borrowed / reused from
+  // the Cripts library.
+#if TS_HAS_CRIPTS
+  if (ids & (RSRC_CLIENT_CONNECTION | RSRC_MTLS_CERTIFICATE | 
RSRC_SERVER_CERTIFICATE)) {
+    Dbg(pi_dbg_ctl, "\tAdding Cripts Client::Connection");
+    client_conn = new cripts::Client::Connection();
+    client_conn->set_state(&state);
+  }
+
+  if (ids & RSRC_SERVER_CONNECTION) {
+    Dbg(pi_dbg_ctl, "\tAdding Cripts Server::Connection");
+    server_conn = new cripts::Server::Connection();
+    server_conn->set_state(&state);
+  }
+
+  if (ids & RSRC_MTLS_CERTIFICATE) {
+    Dbg(pi_dbg_ctl, "\tAdding Cripts Certs::Client");
+    mtls_cert = new cripts::Certs::Client(*client_conn);
+  }
+
+  if (ids & RSRC_SERVER_CERTIFICATE) {
+    Dbg(pi_dbg_ctl, "\tAdding Cripts Certs::Server");
+    server_cert = new cripts::Certs::Server(*client_conn);
+  }
+#endif
+
   _ready = true;
 }
 
@@ -144,5 +176,12 @@ Resources::destroy()
     }
   }
 
+#if TS_HAS_CRIPTS
+  delete client_conn;
+  delete server_conn;
+  delete mtls_cert;
+  delete server_cert;
+#endif
+
   _ready = false;
 }
diff --git a/plugins/header_rewrite/resources.h 
b/plugins/header_rewrite/resources.h
index 0aa0d82def..a56c808dee 100644
--- a/plugins/header_rewrite/resources.h
+++ b/plugins/header_rewrite/resources.h
@@ -28,30 +28,42 @@
 
 #include "lulu.h"
 
+#if TS_HAS_CRIPTS
+#include "cripts/Certs.hpp"
+#include "cripts/Transaction.hpp"
+#endif
+
 enum ResourceIDs {
   RSRC_NONE                    = 0,
-  RSRC_SERVER_RESPONSE_HEADERS = 1,
-  RSRC_SERVER_REQUEST_HEADERS  = 2,
-  RSRC_CLIENT_REQUEST_HEADERS  = 4,
-  RSRC_CLIENT_RESPONSE_HEADERS = 8,
-  RSRC_RESPONSE_STATUS         = 16,
+  RSRC_SERVER_RESPONSE_HEADERS = 1 << 0, // 1
+  RSRC_SERVER_REQUEST_HEADERS  = 1 << 1, // 2
+  RSRC_CLIENT_REQUEST_HEADERS  = 1 << 2, // 4
+  RSRC_CLIENT_RESPONSE_HEADERS = 1 << 3, // 8
+  RSRC_RESPONSE_STATUS         = 1 << 4, // 16
+#if TS_HAS_CRIPTS
+  RSRC_CLIENT_CONNECTION  = 1 << 5, // 32
+  RSRC_SERVER_CONNECTION  = 1 << 6, // 64
+  RSRC_SERVER_CERTIFICATE = 1 << 7, // 128
+  RSRC_MTLS_CERTIFICATE   = 1 << 8, // 256
+#endif
 };
 
 ///////////////////////////////////////////////////////////////////////////////
 // Resources holds the minimum resources required to process a request.
 //
+#if !TS_HAS_CRIPTS
+struct TransactionState {
+  TSHttpTxn txnp = nullptr;
+  TSHttpSsn ssnp = nullptr;
+};
+#endif
+
 class Resources
 {
 public:
-  explicit Resources(TSHttpTxn txnptr, TSCont contptr) : txnp(txnptr), 
contp(contptr)
-  {
-    Dbg(dbg_ctl, "Calling CTOR for Resources (InkAPI)");
-  }
+  explicit Resources(TSHttpTxn txnptr, TSCont contptr) : contp(contptr) { 
_initialize(txnptr, "InkAPI"); }
 
-  Resources(TSHttpTxn txnptr, TSRemapRequestInfo *rri) : txnp(txnptr), 
_rri(rri)
-  {
-    Dbg(dbg_ctl, "Calling CTOR for Resources (RemapAPI)");
-  }
+  explicit Resources(TSHttpTxn txnptr, TSRemapRequestInfo *rri) : _rri(rri) { 
_initialize(txnptr, "RemapAPI"); }
 
   ~Resources() { destroy(); }
 
@@ -66,20 +78,37 @@ public:
     return _ready;
   }
 
-  TSHttpTxn           txnp;
   TSCont              contp          = nullptr;
   TSRemapRequestInfo *_rri           = nullptr;
   TSMBuffer           bufp           = nullptr;
   TSMLoc              hdr_loc        = nullptr;
   TSMBuffer           client_bufp    = nullptr;
   TSMLoc              client_hdr_loc = nullptr;
-  TSHttpStatus        resp_status    = TS_HTTP_STATUS_NONE;
-  const char         *ovector_ptr    = nullptr;
-  int                 ovector[OVECCOUNT];
-  int                 ovector_count = 0;
-  bool                changed_url   = false;
+#if TS_HAS_CRIPTS
+  cripts::Transaction         state; // This now holds txpn / ssnp
+  cripts::Client::Connection *client_conn = nullptr;
+  cripts::Server::Connection *server_conn = nullptr;
+  cripts::Certs::Client      *mtls_cert   = nullptr;
+  cripts::Certs::Server      *server_cert = nullptr;
+#else
+  TransactionState state; // Without cripts, txnp / ssnp goes here
+#endif
+  const char  *ovector_ptr = nullptr;
+  TSHttpStatus resp_status = TS_HTTP_STATUS_NONE;
+  int          ovector[OVECCOUNT];
+  int          ovector_count = 0;
+  bool         changed_url   = false;
 
 private:
+  void
+  _initialize(TSHttpTxn txnptr, const char *api_type)
+  {
+    state.txnp = txnptr;
+    state.ssnp = TSHttpTxnSsnGet(txnptr); // This is cheap, even if not used
+
+    Dbg(dbg_ctl, "Calling CTOR for Resources (%s)", api_type);
+  }
+
   void destroy();
 
   bool _ready = false;
diff --git a/plugins/header_rewrite/statement.h 
b/plugins/header_rewrite/statement.h
index ce187b5cec..d711b9915c 100644
--- a/plugins/header_rewrite/statement.h
+++ b/plugins/header_rewrite/statement.h
@@ -112,6 +112,20 @@ enum NetworkSessionQualifiers {
   NET_QUAL_IPV6,        ///< 'ipv6' or not.
   NET_QUAL_IP_FAMILY,   ///< IP protocol family.
   NET_QUAL_STACK,       ///< Full protocol stack.
+#if TS_HAS_CRIPTS
+  NET_QUAL_CERT_PEM,        ///< The PEM encoded certificate
+  NET_QUAL_CERT_SIG,        ///< The signature of the certificate
+  NET_QUAL_CERT_SUBJECT,    ///< The subject of the certificate
+  NET_QUAL_CERT_ISSUER,     ///< The issuer of the certificate
+  NET_QUAL_CERT_SERIAL,     ///< The serial number of the certificate
+  NET_QUAL_CERT_NOT_BEFORE, ///< The not before date of the certificate
+  NET_QUAL_CERT_NOT_AFTER,  ///< The not after date of the certificate
+  NET_QUAL_CERT_VERSION,    ///< The version of the certificate
+  NET_QUAL_CERT_SAN_DNS,    ///< The DNS names in the SAN
+  NET_QUAL_CERT_SAN_IP,     ///< The IP addresses in the SAN
+  NET_QUAL_CERT_SAN_EMAIL,  ///< The email addresses in the SAN
+  NET_QUAL_CERT_SAN_URI,    ///< The URIs in the SAN
+#endif
 };
 
 class Statement
@@ -174,6 +188,12 @@ public:
   static int acquire_txn_slot();
   static int acquire_txn_private_slot();
 
+  void
+  require_resources(const ResourceIDs ids)
+  {
+    _rsrc = static_cast<ResourceIDs>(_rsrc | ids);
+  }
+
 protected:
   virtual void initialize_hooks();
 
@@ -181,12 +201,6 @@ protected:
   NextHopQualifiers parse_next_hop_qualifier(const std::string &q) const;
   TSHttpCntlType    parse_http_cntl_qualifier(const std::string &q) const;
 
-  void
-  require_resources(const ResourceIDs ids)
-  {
-    _rsrc = static_cast<ResourceIDs>(_rsrc | ids);
-  }
-
   virtual bool
   need_txn_slot() const
   {
diff --git a/plugins/header_rewrite/value.cc b/plugins/header_rewrite/value.cc
index 69e32cbd06..28966cc292 100644
--- a/plugins/header_rewrite/value.cc
+++ b/plugins/header_rewrite/value.cc
@@ -39,7 +39,7 @@ Value::~Value()
 }
 
 void
-Value::set_value(const std::string &val)
+Value::set_value(const std::string &val, Statement *owner)
 {
   _value = val;
 
@@ -58,6 +58,7 @@ Value::set_value(const std::string &val)
 
           if (parser.parse_line(cond_token)) {
             tcond_val->initialize(parser);
+            require_resources(tcond_val->get_resource_ids());
           } else {
             // TODO: should we produce error here?
             Dbg(dbg_ctl, "Error parsing value '%s'", _value.c_str());
@@ -71,6 +72,11 @@ Value::set_value(const std::string &val)
         _cond_vals.push_back(tcond_val);
       }
     }
+
+    // If we have an owner (e.g. an Operator) hoist up the resource 
requirements
+    if (owner) {
+      owner->require_resources(get_resource_ids());
+    }
   } else {
     _int_value   = strtol(_value.c_str(), nullptr, 10);
     _float_value = strtod(_value.c_str(), nullptr);
diff --git a/plugins/header_rewrite/value.h b/plugins/header_rewrite/value.h
index d27d3f8be3..f15d6b2537 100644
--- a/plugins/header_rewrite/value.h
+++ b/plugins/header_rewrite/value.h
@@ -36,7 +36,7 @@
 // TODO: This is very incomplete, we need to support linked lists of these,
 // which evaluate each component and create a "joined" final string.
 //
-class Value : Statement
+class Value : public Statement
 {
 public:
   Value() { Dbg(dbg_ctl, "Calling CTOR for Value"); }
@@ -47,7 +47,7 @@ public:
   Value(const Value &)          = delete;
   void operator=(const Value &) = delete;
 
-  void set_value(const std::string &val);
+  void set_value(const std::string &val, Statement *owner = nullptr);
 
   void
   append_value(std::string &s, const Resources &res) const

Reply via email to