Skip to content

Commit 196ac8c

Browse files
authored
HRW: Allow sets to have quoted strings (#12256)
1 parent f96d214 commit 196ac8c

8 files changed

Lines changed: 71 additions & 16 deletions

File tree

doc/admin-guide/plugins/header_rewrite.en.rst

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,11 @@ The absence of an operand for conditions which accept them simply requires that
739739
a value exists (e.g. the content of the header is not an empty string) for the
740740
condition to be considered true.
741741

742+
.. note::
743+
Strings within a set can be quoted, but the quotes are not necessary. This
744+
can be important if a matching string can include spaces or commas,
745+
e.g. ``(foo,"bar,fie",baz)``.
746+
742747
Condition Flags
743748
---------------
744749

@@ -764,9 +769,10 @@ EXT The substring match only applies to the file extension following a dot.
764769
This is generally mostly useful for the ``URL:PATH`` part.
765770
====== ========================================================================
766771

767-
**Note**: At most, one of ``[PRE]``, ``[SUF]``, ``[MID]``, or ``[EXT]`` may be
768-
used at any time. They can however be used together with ``[NOCASE]`` and the
769-
other flags.
772+
.. note::
773+
At most, one of ``[PRE]``, ``[SUF]``, ``[MID]``, or ``[EXT]`` may be
774+
used at any time. They can however be used together with ``[NOCASE]`` and the
775+
other flags.
770776

771777
Operators
772778
---------
@@ -898,7 +904,9 @@ Will call ``<URL>`` (see URL in `URL Parts`_) to retrieve a custom error respons
898904
and set the body with the result. Triggering this rule on an OK transaction will
899905
send a 500 status code to the client with the desired response. If this is triggered
900906
on any error status code, that original status code will be sent to the client.
901-
**Note**: This config should only be set using READ_RESPONSE_HDR_HOOK
907+
908+
.. note::
909+
This config should only be set using READ_RESPONSE_HDR_HOOK
902910

903911
An example config would look like::
904912

@@ -958,8 +966,9 @@ the appropriate logs even when the debug tag has not been enabled. For
958966
additional information on |TS| debugging statements, refer to
959967
:ref:`developer-debug-tags` in the developer's documentation.
960968

961-
**Note**: This operator is deprecated, use the `set-http-cntl`_ operator instead,
962-
with the ``TXN_DEBUG`` control.
969+
.. note::
970+
This operator is deprecated, use the `set-http-cntl`_ operator instead,
971+
with the ``TXN_DEBUG`` control.
963972

964973
set-destination
965974
~~~~~~~~~~~~~~~
@@ -1074,8 +1083,9 @@ When invoked, and when ``<value>`` is any of ``1``, ``true``, or ``TRUE``, this
10741083
operator causes |TS| to abort further request remapping. Any other value and
10751084
the operator will effectively be a no-op.
10761085

1077-
**Note**: This operator is deprecated, use the `set-http-cntl`_ operator instead,
1078-
with the ``SKIP_REMAP`` control.
1086+
.. note::
1087+
This operator is deprecated, use the `set-http-cntl`_ operator instead,
1088+
with the ``SKIP_REMAP`` control.
10791089

10801090
set-cookie
10811091
~~~~~~~~~~

plugins/header_rewrite/matcher.h

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -194,13 +194,39 @@ template <class T> class Matchers : public Matcher
194194
// MATCH_SET (allowed for any T)
195195
if (_op == MATCH_SET) {
196196
_data.template emplace<std::set<T>>();
197+
auto &values = std::get<std::set<T>>(_data);
198+
swoc::TextView src{s};
199+
bool in_quotes = false;
200+
size_t start = 0, cur = 0, skip_quotes = 0;
201+
202+
while (cur < src.size()) {
203+
if (src[cur] == '"') {
204+
skip_quotes = 1;
205+
in_quotes = !in_quotes;
206+
++cur;
207+
} else if (src[cur] == ',' && !in_quotes) {
208+
swoc::TextView field = src.substr(start + skip_quotes, cur - start - skip_quotes * 2);
209+
210+
field.ltrim_if(&isspace).rtrim_if(&isspace);
211+
values.insert(convert(std::string(field)));
212+
start = ++cur + skip_quotes;
213+
skip_quotes = 0;
214+
} else {
215+
++cur;
216+
}
217+
}
218+
219+
if (in_quotes) {
220+
Dbg(pi_dbg_ctl, "Invalid set: unmatched quotes in: %s", s.c_str());
221+
throw std::runtime_error("Malformed set, unmatched quotes");
222+
}
197223

198-
auto &values = std::get<std::set<T>>(_data);
199-
std::istringstream stream(s);
200-
std::string part;
224+
// Last field (if any)
225+
if (start < src.size()) {
226+
swoc::TextView field = src.substr(start + skip_quotes, src.size() - start - skip_quotes * 2);
201227

202-
while (std::getline(stream, part, ',')) {
203-
values.insert(convert(part));
228+
field.ltrim_if(&isspace).rtrim_if(&isspace);
229+
values.insert(convert(std::string(field)));
204230
}
205231

206232
if (!values.empty()) {

plugins/header_rewrite/parser.cc

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828

2929
#include "parser.h"
3030

31-
enum ParserState { PARSER_DEFAULT, PARSER_IN_QUOTE, PARSER_IN_REGEX, PARSER_IN_EXPANSION, PARSER_IN_BRACE };
31+
enum ParserState { PARSER_DEFAULT, PARSER_IN_QUOTE, PARSER_IN_REGEX, PARSER_IN_EXPANSION, PARSER_IN_BRACE, PARSER_IN_PAREN };
3232

3333
bool
3434
Parser::parse_line(const std::string &original_line)
@@ -71,7 +71,7 @@ Parser::parse_line(const std::string &original_line)
7171
cur_token_start = i;
7272
}
7373
line.erase(i, 1);
74-
} else if ((state != PARSER_IN_REGEX) && (line[i] == '"')) {
74+
} else if ((state != PARSER_IN_REGEX) && (state != PARSER_IN_PAREN) && (line[i] == '"')) {
7575
if ((state != PARSER_IN_QUOTE) && !extracting_token) {
7676
state = PARSER_IN_QUOTE;
7777
extracting_token = true;
@@ -97,6 +97,15 @@ Parser::parse_line(const std::string &original_line)
9797
_tokens.push_back(line.substr(cur_token_start, cur_token_length));
9898
state = PARSER_DEFAULT;
9999
extracting_token = false;
100+
} else if ((state == PARSER_DEFAULT) && (line[i] == '(')) {
101+
state = PARSER_IN_PAREN;
102+
extracting_token = true;
103+
cur_token_start = i;
104+
} else if ((state == PARSER_IN_PAREN) && (line[i] == ')')) {
105+
cur_token_length = i - cur_token_start + 1;
106+
_tokens.push_back(line.substr(cur_token_start, cur_token_length));
107+
state = PARSER_DEFAULT;
108+
extracting_token = false;
100109
} else if (!extracting_token) {
101110
if (_tokens.empty() && line[i] == '#') {
102111
// this is a comment line (it may have had leading whitespace before the #)

plugins/header_rewrite/parser.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ class Parser
109109

110110
bool parse_line(const std::string &original_line);
111111

112+
// We chose to have this take a std::string, since some of these conversions can not take a TextView easily
112113
template <typename NumericT>
113114
static NumericT
114115
parseNumeric(const std::string &s)

tests/gold_tests/pluginTest/header_rewrite/gold/ext-sets.gold

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@
77
< Date: ``
88
``
99
< X-Extension: Yes
10+
< X-Testing: Yes
1011
``

tests/gold_tests/pluginTest/header_rewrite/gold/header_rewrite-client.gold

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@
1010
< Server: ATS/``
1111
< Cache-Control: no-store
1212
< X-Pre-Else: Yes
13+
< X-Testing: No
1314
``

tests/gold_tests/pluginTest/header_rewrite/header_rewrite_url.test.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@
9090
tr = Test.AddTestRun()
9191
tr.MakeCurlCommand(
9292
'--proxy 127.0.0.1:{0} "http://www.example.com/from_path/hrw-sets.png" '
93-
'-H "Proxy-Connection: keep-alive" --verbose'.format(ts.Variables.port))
93+
'-H "Proxy-Connection: keep-alive" -H "X-Testing: foo,bar" '
94+
'--verbose'.format(ts.Variables.port))
9495
tr.Processes.Default.ReturnCode = 0
9596
tr.Processes.Default.Streams.stderr = "gold/ext-sets.gold"
9697
tr.StillRunningAfter = server

tests/gold_tests/pluginTest/header_rewrite/rules/rule_client.conf

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,9 @@ cond %{CLIENT-URL:PATH} (hrw,foo) [MID,NOCASE]
3030
no-op
3131
else
3232
set-header X-Pre-Else "Yes"
33+
34+
cond %{SEND_RESPONSE_HDR_HOOK}
35+
cond %{CLIENT-HEADER:X-Testing} (foo,bar,"foo,bar")
36+
set-header X-Testing "Yes"
37+
else
38+
set-header X-Testing "No"

0 commit comments

Comments
 (0)