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 196ac8cbe9 HRW: Allow sets to have quoted strings (#12256)
196ac8cbe9 is described below
commit 196ac8cbe9ee6bddd78d9e8c20d9c0a9a9747c62
Author: Leif Hedstrom <[email protected]>
AuthorDate: Fri Jun 6 14:53:13 2025 -0500
HRW: Allow sets to have quoted strings (#12256)
---
doc/admin-guide/plugins/header_rewrite.en.rst | 26 +++++++++++-----
plugins/header_rewrite/matcher.h | 36 +++++++++++++++++++---
plugins/header_rewrite/parser.cc | 13 ++++++--
plugins/header_rewrite/parser.h | 1 +
.../pluginTest/header_rewrite/gold/ext-sets.gold | 1 +
.../header_rewrite/gold/header_rewrite-client.gold | 1 +
.../header_rewrite/header_rewrite_url.test.py | 3 +-
.../header_rewrite/rules/rule_client.conf | 6 ++++
8 files changed, 71 insertions(+), 16 deletions(-)
diff --git a/doc/admin-guide/plugins/header_rewrite.en.rst
b/doc/admin-guide/plugins/header_rewrite.en.rst
index de9f87226d..cc8c6d62a0 100644
--- a/doc/admin-guide/plugins/header_rewrite.en.rst
+++ b/doc/admin-guide/plugins/header_rewrite.en.rst
@@ -739,6 +739,11 @@ The absence of an operand for conditions which accept them
simply requires that
a value exists (e.g. the content of the header is not an empty string) for the
condition to be considered true.
+.. note::
+ Strings within a set can be quoted, but the quotes are not necessary. This
+ can be important if a matching string can include spaces or commas,
+ e.g. ``(foo,"bar,fie",baz)``.
+
Condition Flags
---------------
@@ -764,9 +769,10 @@ EXT The substring match only applies to the file
extension following a dot.
This is generally mostly useful for the ``URL:PATH`` part.
====== ========================================================================
-**Note**: At most, one of ``[PRE]``, ``[SUF]``, ``[MID]``, or ``[EXT]`` may be
-used at any time. They can however be used together with ``[NOCASE]`` and the
-other flags.
+.. note::
+ At most, one of ``[PRE]``, ``[SUF]``, ``[MID]``, or ``[EXT]`` may be
+ used at any time. They can however be used together with ``[NOCASE]`` and
the
+ other flags.
Operators
---------
@@ -898,7 +904,9 @@ Will call ``<URL>`` (see URL in `URL Parts`_) to retrieve a
custom error respons
and set the body with the result. Triggering this rule on an OK transaction
will
send a 500 status code to the client with the desired response. If this is
triggered
on any error status code, that original status code will be sent to the client.
-**Note**: This config should only be set using READ_RESPONSE_HDR_HOOK
+
+.. note::
+ This config should only be set using READ_RESPONSE_HDR_HOOK
An example config would look like::
@@ -958,8 +966,9 @@ the appropriate logs even when the debug tag has not been
enabled. For
additional information on |TS| debugging statements, refer to
:ref:`developer-debug-tags` in the developer's documentation.
-**Note**: This operator is deprecated, use the `set-http-cntl`_ operator
instead,
-with the ``TXN_DEBUG`` control.
+.. note::
+ This operator is deprecated, use the `set-http-cntl`_ operator instead,
+ with the ``TXN_DEBUG`` control.
set-destination
~~~~~~~~~~~~~~~
@@ -1074,8 +1083,9 @@ When invoked, and when ``<value>`` is any of ``1``,
``true``, or ``TRUE``, this
operator causes |TS| to abort further request remapping. Any other value and
the operator will effectively be a no-op.
-**Note**: This operator is deprecated, use the `set-http-cntl`_ operator
instead,
-with the ``SKIP_REMAP`` control.
+.. note::
+ This operator is deprecated, use the `set-http-cntl`_ operator instead,
+ with the ``SKIP_REMAP`` control.
set-cookie
~~~~~~~~~~
diff --git a/plugins/header_rewrite/matcher.h b/plugins/header_rewrite/matcher.h
index 9f1a36a15b..3099b300f8 100644
--- a/plugins/header_rewrite/matcher.h
+++ b/plugins/header_rewrite/matcher.h
@@ -194,13 +194,39 @@ public:
// MATCH_SET (allowed for any T)
if (_op == MATCH_SET) {
_data.template emplace<std::set<T>>();
+ auto &values = std::get<std::set<T>>(_data);
+ swoc::TextView src{s};
+ bool in_quotes = false;
+ size_t start = 0, cur = 0, skip_quotes = 0;
+
+ while (cur < src.size()) {
+ if (src[cur] == '"') {
+ skip_quotes = 1;
+ in_quotes = !in_quotes;
+ ++cur;
+ } else if (src[cur] == ',' && !in_quotes) {
+ swoc::TextView field = src.substr(start + skip_quotes, cur - start -
skip_quotes * 2);
+
+ field.ltrim_if(&isspace).rtrim_if(&isspace);
+ values.insert(convert(std::string(field)));
+ start = ++cur + skip_quotes;
+ skip_quotes = 0;
+ } else {
+ ++cur;
+ }
+ }
+
+ if (in_quotes) {
+ Dbg(pi_dbg_ctl, "Invalid set: unmatched quotes in: %s", s.c_str());
+ throw std::runtime_error("Malformed set, unmatched quotes");
+ }
- auto &values = std::get<std::set<T>>(_data);
- std::istringstream stream(s);
- std::string part;
+ // Last field (if any)
+ if (start < src.size()) {
+ swoc::TextView field = src.substr(start + skip_quotes, src.size() -
start - skip_quotes * 2);
- while (std::getline(stream, part, ',')) {
- values.insert(convert(part));
+ field.ltrim_if(&isspace).rtrim_if(&isspace);
+ values.insert(convert(std::string(field)));
}
if (!values.empty()) {
diff --git a/plugins/header_rewrite/parser.cc b/plugins/header_rewrite/parser.cc
index 3dbd63de4a..fb73b5aea7 100644
--- a/plugins/header_rewrite/parser.cc
+++ b/plugins/header_rewrite/parser.cc
@@ -28,7 +28,7 @@
#include "parser.h"
-enum ParserState { PARSER_DEFAULT, PARSER_IN_QUOTE, PARSER_IN_REGEX,
PARSER_IN_EXPANSION, PARSER_IN_BRACE };
+enum ParserState { PARSER_DEFAULT, PARSER_IN_QUOTE, PARSER_IN_REGEX,
PARSER_IN_EXPANSION, PARSER_IN_BRACE, PARSER_IN_PAREN };
bool
Parser::parse_line(const std::string &original_line)
@@ -71,7 +71,7 @@ Parser::parse_line(const std::string &original_line)
cur_token_start = i;
}
line.erase(i, 1);
- } else if ((state != PARSER_IN_REGEX) && (line[i] == '"')) {
+ } else if ((state != PARSER_IN_REGEX) && (state != PARSER_IN_PAREN) &&
(line[i] == '"')) {
if ((state != PARSER_IN_QUOTE) && !extracting_token) {
state = PARSER_IN_QUOTE;
extracting_token = true;
@@ -97,6 +97,15 @@ Parser::parse_line(const std::string &original_line)
_tokens.push_back(line.substr(cur_token_start, cur_token_length));
state = PARSER_DEFAULT;
extracting_token = false;
+ } else if ((state == PARSER_DEFAULT) && (line[i] == '(')) {
+ state = PARSER_IN_PAREN;
+ extracting_token = true;
+ cur_token_start = i;
+ } else if ((state == PARSER_IN_PAREN) && (line[i] == ')')) {
+ cur_token_length = i - cur_token_start + 1;
+ _tokens.push_back(line.substr(cur_token_start, cur_token_length));
+ state = PARSER_DEFAULT;
+ extracting_token = false;
} else if (!extracting_token) {
if (_tokens.empty() && line[i] == '#') {
// this is a comment line (it may have had leading whitespace before
the #)
diff --git a/plugins/header_rewrite/parser.h b/plugins/header_rewrite/parser.h
index 35f056936d..cc0f3d985a 100644
--- a/plugins/header_rewrite/parser.h
+++ b/plugins/header_rewrite/parser.h
@@ -109,6 +109,7 @@ public:
bool parse_line(const std::string &original_line);
+ // We chose to have this take a std::string, since some of these conversions
can not take a TextView easily
template <typename NumericT>
static NumericT
parseNumeric(const std::string &s)
diff --git a/tests/gold_tests/pluginTest/header_rewrite/gold/ext-sets.gold
b/tests/gold_tests/pluginTest/header_rewrite/gold/ext-sets.gold
index c71cee3ac7..9636d50f95 100644
--- a/tests/gold_tests/pluginTest/header_rewrite/gold/ext-sets.gold
+++ b/tests/gold_tests/pluginTest/header_rewrite/gold/ext-sets.gold
@@ -7,4 +7,5 @@
< Date: ``
``
< X-Extension: Yes
+< X-Testing: 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 8d5d99ba35..510f7b3dbc 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
@@ -10,4 +10,5 @@
< Server: ATS/``
< Cache-Control: no-store
< X-Pre-Else: Yes
+< X-Testing: No
``
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 c86ea42e6c..e77b5ae2a7 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
@@ -90,7 +90,8 @@ ts.Disk.traffic_out.Content = "gold/header_rewrite-tag.gold"
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))
+ '-H "Proxy-Connection: keep-alive" -H "X-Testing: foo,bar" '
+ '--verbose'.format(ts.Variables.port))
tr.Processes.Default.ReturnCode = 0
tr.Processes.Default.Streams.stderr = "gold/ext-sets.gold"
tr.StillRunningAfter = server
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 1b6d845084..1796078a53 100644
--- a/tests/gold_tests/pluginTest/header_rewrite/rules/rule_client.conf
+++ b/tests/gold_tests/pluginTest/header_rewrite/rules/rule_client.conf
@@ -30,3 +30,9 @@ cond %{CLIENT-URL:PATH} (hrw,foo) [MID,NOCASE]
no-op
else
set-header X-Pre-Else "Yes"
+
+cond %{SEND_RESPONSE_HDR_HOOK}
+cond %{CLIENT-HEADER:X-Testing} (foo,bar,"foo,bar")
+ set-header X-Testing "Yes"
+else
+ set-header X-Testing "No"