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 76ffacca2c hrw4u: Check operators, add http header validation, url 
validation (#12389)
76ffacca2c is described below

commit 76ffacca2c8dff322a50016ee9338018e4542acb
Author: Leif Hedstrom <[email protected]>
AuthorDate: Tue Jul 29 01:19:20 2025 +0200

    hrw4u: Check operators, add http header validation, url validation (#12389)
---
 tools/hrw4u/src/symbols.py                         | 22 +++++++++++-----------
 tools/hrw4u/src/validation.py                      | 16 +++++++++++++++-
 tools/hrw4u/tests/data/ops/bad_path.fail.error.txt |  3 +++
 tools/hrw4u/tests/data/ops/bad_path.fail.input.txt |  3 +++
 4 files changed, 32 insertions(+), 12 deletions(-)

diff --git a/tools/hrw4u/src/symbols.py b/tools/hrw4u/src/symbols.py
index a44d50d486..9ee2957ec9 100644
--- a/tools/hrw4u/src/symbols.py
+++ b/tools/hrw4u/src/symbols.py
@@ -29,19 +29,17 @@ class SymbolResolver:
         "http.status.reason": ("set-status-reason", 
Validator.quoted_or_simple(), False, None),
         "http.status": ("set-status", Validator.range(0, 999), False, None),
         "inbound.conn.dscp": ("set-conn-dscp", Validator.nbit_int(6), False, 
None),
-        "inbound.cookie.": (["rm-cookie", "set-cookie"], 
Validator.quoted_or_simple(), False, None),
-        "inbound.req.": (["rm-header", "set-header"], 
Validator.quoted_or_simple(), False, None),
+        "inbound.cookie.": (["rm-cookie", "set-cookie"], 
Validator.http_token(), False, None),
+        "inbound.req.": (["rm-header", "set-header"], Validator.http_token(), 
False, None),
         "inbound.resp.body": ("set-body", Validator.quoted_or_simple(), False, 
None),
-        "inbound.resp.": (["rm-header", "set-header"], 
Validator.quoted_or_simple(), False, None),
+        "inbound.resp.": (["rm-header", "set-header"], Validator.http_token(), 
False, None),
         "inbound.status.reason": ("set-status-reason", Validator.range(0, 
999), False, None),
         "inbound.status": ("set-status", Validator.range(0, 999), False, None),
-        "inbound.url.": (["rm-destination", "set-destination"], 
Validator.quoted_or_simple(), True, None),
-        "outbound.cookie.": (["rm-cookie", "set-cookie"], 
Validator.quoted_or_simple(), False, None),
-        "outbound.req.": (["rm-header", "set-header"], 
Validator.quoted_or_simple(), False, {"PRE_REMAP", "REMAP", "READ_REQUEST"}),
+        "inbound.url.": (["rm-destination", "set-destination"], 
Validator.suffix_group(types.SuffixGroup.URL_FIELDS), True, None),
+        "outbound.cookie.": (["rm-cookie", "set-cookie"], 
Validator.http_token(), False, None),
+        "outbound.req.": (["rm-header", "set-header"], Validator.http_token(), 
False, {"PRE_REMAP", "REMAP", "READ_REQUEST"}),
         "outbound.resp.":
-            (
-                ["rm-header",
-                 "set-header"], Validator.quoted_or_simple(), False, 
{"PRE_REMAP", "REMAP", "READ_REQUEST", "SEND_REQUEST"}),
+            (["rm-header", "set-header"], Validator.http_token(), False, 
{"PRE_REMAP", "REMAP", "READ_REQUEST", "SEND_REQUEST"}),
         "outbound.status.reason": ("set-status-reason", Validator.range(0, 
999), False, {"PRE_REMAP", "REMAP", "READ_REQUEST"}),
         "outbound.status": ("set-status", Validator.range(0, 999), False, 
{"PRE_REMAP", "REMAP", "READ_REQUEST"}),
     }
@@ -95,13 +93,13 @@ class SymbolResolver:
         "http.cntl.": ("HTTP-CNTL", 
Validator.suffix_group(types.SuffixGroup.HTTP_CNTL_FIELDS), True, None, False),
         "id.": ("ID", Validator.suffix_group(types.SuffixGroup.ID_FIELDS), 
True, None, False),
         "inbound.conn.": ("INBOUND", 
Validator.suffix_group(types.SuffixGroup.CONN_FIELDS), True, None, False),
-        "inbound.cookie.": ("COOKIE", Validator.quoted_or_simple(), False, 
None, True),
+        "inbound.cookie.": ("COOKIE", Validator.http_token(), False, None, 
True),
         "inbound.req.": ("CLIENT-HEADER", None, False, None, True),
         "inbound.resp.": ("HEADER", None, False, None, True),
         "inbound.url.": ("CLIENT-URL", 
Validator.suffix_group(types.SuffixGroup.URL_FIELDS), True, None, True),
         "now.": ("NOW", Validator.suffix_group(types.SuffixGroup.DATE_FIELDS), 
True, None, False),
         "outbound.conn.": ("OUTBOUND", 
Validator.suffix_group(types.SuffixGroup.CONN_FIELDS), True, None, False),
-        "outbound.cookie.": ("COOKIE", Validator.quoted_or_simple(), False, 
None, True),
+        "outbound.cookie.": ("COOKIE", Validator.http_token(), False, None, 
True),
         "outbound.req.": ("HEADER", None, False, {"PRE_REMAP", "REMAP", 
"READ_REQUEST"}, True),
         "outbound.resp.": ("HEADER", None, False, {"PRE_REMAP", "REMAP", 
"READ_REQUEST", "SEND_REQUEST"}, True),
         "outbound.url.":
@@ -178,6 +176,8 @@ class SymbolResolver:
                     qualifier = name[len(op_key):]
                     if uppercase:
                         qualifier = qualifier.upper()
+                    if validator:
+                        validator(qualifier)
                     if isinstance(commands, list):  # rm- / -set- operator
                         if value == '""':
                             result = f"{commands[0]} {qualifier}"
diff --git a/tools/hrw4u/src/validation.py b/tools/hrw4u/src/validation.py
index d206667492..aa590f7abe 100644
--- a/tools/hrw4u/src/validation.py
+++ b/tools/hrw4u/src/validation.py
@@ -75,6 +75,9 @@ class ValidatorChain:
     def quoted_or_simple(self) -> 'ValidatorChain':
         return self._add(self._wrap_args(Validator.quoted_or_simple()))
 
+    def http_token(self) -> 'ValidatorChain':
+        return self._add(self._wrap_args(Validator.http_token()))
+
     def nbit_int(self, nbits: int) -> 'ValidatorChain':
         return self._add(self._wrap_args(Validator.nbit_int(nbits)))
 
@@ -134,7 +137,7 @@ class Validator:
 
     @staticmethod
     def quoted_or_simple() -> Callable[[str], None]:
-        simple_re = re.compile(r'^[a-zA-Z0-9_-]+$')
+        simple_re = re.compile(r'^[@a-zA-Z0-9_-]+$')
 
         def validator(value: str) -> None:
             if (value.startswith('"') and value.endswith('"')) or 
simple_re.fullmatch(value):
@@ -144,6 +147,17 @@ class Validator:
 
         return validator
 
+    @staticmethod
+    def http_token() -> Callable[[str], None]:
+        header_re = re.compile(r'^[@!#$%&\'*+\-.0-9A-Z^_`a-z|~]+$')
+
+        def validator(value: str) -> None:
+            if header_re.fullmatch(value):
+                return
+            raise SymbolResolutionError(value, "HTTP token/header not valid, 
illegal characters or format.")
+
+        return validator
+
     @staticmethod
     def suffix_group(group: types.SuffixGroup) -> Callable[[str], None]:
 
diff --git a/tools/hrw4u/tests/data/ops/bad_path.fail.error.txt 
b/tools/hrw4u/tests/data/ops/bad_path.fail.error.txt
new file mode 100644
index 0000000000..d5ab37f64f
--- /dev/null
+++ b/tools/hrw4u/tests/data/ops/bad_path.fail.error.txt
@@ -0,0 +1,3 @@
+tests/data/ops/bad_path.fail.input.txt:2:2: error: Invalid suffix 'ATH' for 
group 'URL_FIELDS'. Must be one of: HOST, PATH, PORT, QUERY, SCHEME, URL
+   2 |   inbound.url.ath="foo";
+     |   ^
diff --git a/tools/hrw4u/tests/data/ops/bad_path.fail.input.txt 
b/tools/hrw4u/tests/data/ops/bad_path.fail.input.txt
new file mode 100644
index 0000000000..f0ba09ac15
--- /dev/null
+++ b/tools/hrw4u/tests/data/ops/bad_path.fail.input.txt
@@ -0,0 +1,3 @@
+REMAP {
+  inbound.url.ath="foo";
+}

Reply via email to