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 7cd2c9a71c hrw4u: Fix section validations and operators (#12820)
7cd2c9a71c is described below
commit 7cd2c9a71cd6106687f10f1f2c9c2cef76fa4980
Author: Leif Hedstrom <[email protected]>
AuthorDate: Tue Jan 20 13:52:11 2026 -0700
hrw4u: Fix section validations and operators (#12820)
* hrw4u: Fix section validations and operators
* Fixes per review
---
tools/hrw4u/.gitignore | 3 +-
tools/hrw4u/pyproject.toml | 2 +-
tools/hrw4u/src/lsp/completions.py | 8 +-
tools/hrw4u/src/symbols.py | 14 +--
tools/hrw4u/src/symbols_base.py | 4 +-
tools/hrw4u/src/tables.py | 114 +++++++++++----------
tools/hrw4u/src/visitor.py | 5 +-
.../hrw4u/tests/data/examples/all-nonsense.ast.txt | 2 +-
.../tests/data/examples/all-nonsense.input.txt | 2 +-
.../tests/data/examples/all-nonsense.output.txt | 2 +-
.../data/hooks/inbound_resp_section.fail.error.txt | 3 +
.../data/hooks/inbound_resp_section.fail.input.txt | 3 +
.../hooks/outbound_resp_section.fail.error.txt | 3 +
.../hooks/outbound_resp_section.fail.input.txt | 3 +
tools/hrw4u/tests/data/ops/skip-remap.ast.txt | 2 +-
tools/hrw4u/tests/data/ops/skip-remap.input.txt | 2 +-
tools/hrw4u/tests/data/ops/skip-remap.output.txt | 2 +-
.../data/ops/skip_remap_quoted_bool.fail.input.txt | 2 +-
tools/hrw4u/tests/test_lsp.py | 42 +++-----
19 files changed, 115 insertions(+), 103 deletions(-)
diff --git a/tools/hrw4u/.gitignore b/tools/hrw4u/.gitignore
index 5df9179ad7..c61b1049d7 100644
--- a/tools/hrw4u/.gitignore
+++ b/tools/hrw4u/.gitignore
@@ -1,2 +1,3 @@
build/
-dist/
\ No newline at end of file
+dist/
+uv.lock
diff --git a/tools/hrw4u/pyproject.toml b/tools/hrw4u/pyproject.toml
index bf6babdf57..ce607fd5b6 100644
--- a/tools/hrw4u/pyproject.toml
+++ b/tools/hrw4u/pyproject.toml
@@ -20,7 +20,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "hrw4u"
-version = "1.4.0"
+version = "1.4.1"
description = "HRW4U CLI tool for Apache Traffic Server header rewrite rules"
authors = [
{name = "Leif Hedstrom", email = "[email protected]"}
diff --git a/tools/hrw4u/src/lsp/completions.py
b/tools/hrw4u/src/lsp/completions.py
index 1e8cf04197..42771ee66f 100644
--- a/tools/hrw4u/src/lsp/completions.py
+++ b/tools/hrw4u/src/lsp/completions.py
@@ -100,7 +100,7 @@ class CompletionBuilder:
cls, label: str, commands: str | list[str] | tuple[str, ...],
sections: set[SectionType] | None,
current_section: SectionType | None, replacement_range: dict[str,
Any]) -> CompletionItem | None:
"""Create completion item for operators."""
- if sections and current_section and current_section in sections:
+ if sections and current_section and current_section not in sections:
return None
cmd_str = commands if isinstance(commands, str) else " /
".join(commands)
@@ -108,7 +108,7 @@ class CompletionBuilder:
if sections:
section_names = [s.value for s in sections]
- detail += f" (Restricted in: {', '.join(section_names)})"
+ detail += f" (Available in: {', '.join(section_names)})"
documentation = cls.create_markdown_doc(f"**{label}** - HRW4U
Operator\n\nMaps to: `{cmd_str}`")
@@ -127,14 +127,14 @@ class CompletionBuilder:
cls, label: str, tag: str, sections: set[SectionType] | None,
current_section: SectionType | None,
replacement_range: dict[str, Any]) -> CompletionItem | None:
"""Create completion item for conditions."""
- if sections and current_section and current_section in sections:
+ if sections and current_section and current_section not in sections:
return None
detail = f"Condition: {tag}"
if sections:
section_names = [s.value for s in sections]
- detail += f" (Restricted in: {', '.join(section_names)})"
+ detail += f" (Available in: {', '.join(section_names)})"
documentation = cls.create_markdown_doc(f"**{label}** - HRW4U
Condition\n\nMaps to: `{tag}`")
diff --git a/tools/hrw4u/src/symbols.py b/tools/hrw4u/src/symbols.py
index c40010c64d..bb5210923f 100644
--- a/tools/hrw4u/src/symbols.py
+++ b/tools/hrw4u/src/symbols.py
@@ -140,8 +140,8 @@ class SymbolResolver(SymbolResolverBase):
if params := self._lookup_condition_cached(name):
tag = params.target if params else None
- restricted = params.sections if params else None
- self.validate_section_access(name, section, restricted)
+ allowed_sections = params.sections if params else None
+ self.validate_section_access(name, section, allowed_sections)
# For exact matches, default_expr is determined by whether
it's a prefix pattern
return tag, False
@@ -150,9 +150,9 @@ class SymbolResolver(SymbolResolverBase):
for prefix, params in prefix_matches:
tag = params.target if params else None
validator = params.validate if params else None
- restricted = params.sections if params else None
+ allowed_sections = params.sections if params else None
- self.validate_section_access(name, section, restricted)
+ self.validate_section_access(name, section, allowed_sections)
suffix = name[len(prefix):]
suffix_norm = suffix.upper() if (params and params.upper) else
suffix
if validator:
@@ -190,9 +190,11 @@ class SymbolResolver(SymbolResolverBase):
error.add_symbol_suggestion(suggestions)
raise error
- def resolve_statement_func(self, func_name: str, args: list[str]) -> str:
- with self.debug_context("resolve_statement_func", func_name, args):
+ def resolve_statement_func(self, func_name: str, args: list[str], section:
SectionType | None = None) -> str:
+ with self.debug_context("resolve_statement_func", func_name, args,
section):
if params := self._lookup_statement_function_cached(func_name):
+ allowed_sections = params.sections if params else None
+ self.validate_section_access(func_name, section,
allowed_sections)
command = params.target
validator = params.validate
if validator:
diff --git a/tools/hrw4u/src/symbols_base.py b/tools/hrw4u/src/symbols_base.py
index 5749065074..1fb95cd60c 100644
--- a/tools/hrw4u/src/symbols_base.py
+++ b/tools/hrw4u/src/symbols_base.py
@@ -57,8 +57,8 @@ class SymbolResolverBase:
def _reverse_resolution_map(self) -> dict[str, Any]:
return tables.REVERSE_RESOLUTION_MAP
- def validate_section_access(self, name: str, section: SectionType | None,
restricted: set[SectionType] | None) -> None:
- if section and restricted and section in restricted:
+ def validate_section_access(self, name: str, section: SectionType | None,
allowed_sections: set[SectionType] | None) -> None:
+ if section and allowed_sections and section not in allowed_sections:
raise SymbolResolutionError(name, f"{name} is not available in the
{section.value} section")
@lru_cache(maxsize=256)
diff --git a/tools/hrw4u/src/tables.py b/tools/hrw4u/src/tables.py
index 85d93394ba..94253fba0a 100644
--- a/tools/hrw4u/src/tables.py
+++ b/tools/hrw4u/src/tables.py
@@ -24,43 +24,51 @@ from hrw4u.types import MapParams, SuffixGroup
from hrw4u.states import SectionType
from hrw4u.common import HeaderOperations
+# Common section sets for validation
+# HTTP_SECTIONS: All hooks where HTTP transaction data is available (excludes
TXN_START/TXN_CLOSE)
+HTTP_SECTIONS: Final[frozenset[SectionType]] = frozenset(
+ {
+ SectionType.PRE_REMAP, SectionType.REMAP, SectionType.READ_REQUEST,
SectionType.SEND_REQUEST, SectionType.READ_RESPONSE,
+ SectionType.SEND_RESPONSE
+ })
+
# yapf: disable
OPERATOR_MAP: dict[str, MapParams] = {
- "http.cntl.": MapParams(target="set-http-cntl", upper=True,
validate=Validator.suffix_group(SuffixGroup.HTTP_CNTL_FIELDS)),
- "http.status.reason": MapParams(target="set-status-reason",
validate=Validator.quoted_or_simple()),
- "http.status": MapParams(target="set-status", validate=Validator.range(0,
999)),
- "inbound.conn.dscp": MapParams(target="set-conn-dscp",
validate=Validator.nbit_int(6)),
- "inbound.conn.mark": MapParams(target="set-conn-mark",
validate=Validator.nbit_int(32)),
- "outbound.conn.dscp": MapParams(target="set-conn-dscp",
validate=Validator.nbit_int(6), sections={SectionType.PRE_REMAP,
SectionType.REMAP, SectionType.READ_REQUEST}),
- "outbound.conn.mark": MapParams(target="set-conn-mark",
validate=Validator.nbit_int(32), sections={SectionType.PRE_REMAP,
SectionType.REMAP, SectionType.READ_REQUEST}),
- "inbound.cookie.": MapParams(target=HeaderOperations.COOKIE_OPERATIONS,
validate=Validator.http_token()),
- "inbound.req.": MapParams(target=HeaderOperations.OPERATIONS, add=True,
validate=Validator.http_header_name()),
- "inbound.resp.body": MapParams(target="set-body",
validate=Validator.quoted_or_simple()),
- "inbound.resp.": MapParams(target=HeaderOperations.OPERATIONS, add=True,
validate=Validator.http_header_name()),
- "inbound.status.reason": MapParams(target="set-status-reason",
validate=Validator.quoted_or_simple()),
- "inbound.status": MapParams(target="set-status",
validate=Validator.range(0, 999)),
- "inbound.url.": MapParams(target=HeaderOperations.DESTINATION_OPERATIONS,
upper=True, validate=Validator.suffix_group(SuffixGroup.URL_FIELDS)),
- "outbound.cookie.": MapParams(target=HeaderOperations.COOKIE_OPERATIONS,
validate=Validator.http_token(), sections={SectionType.PRE_REMAP,
SectionType.REMAP, SectionType.READ_REQUEST}),
- "outbound.req.": MapParams(target=HeaderOperations.OPERATIONS, add=True,
validate=Validator.http_header_name(), sections={SectionType.PRE_REMAP,
SectionType.REMAP, SectionType.READ_REQUEST}),
- "outbound.resp.": MapParams(target=HeaderOperations.OPERATIONS, add=True,
validate=Validator.http_header_name(), sections={SectionType.PRE_REMAP,
SectionType.REMAP, SectionType.READ_REQUEST, SectionType.SEND_REQUEST}),
- "outbound.status.reason": MapParams(target="set-status-reason",
validate=Validator.quoted_or_simple(), sections={SectionType.PRE_REMAP,
SectionType.REMAP, SectionType.READ_REQUEST, SectionType.SEND_REQUEST}),
- "outbound.status": MapParams(target="set-status",
validate=Validator.range(0, 999), sections={SectionType.PRE_REMAP,
SectionType.REMAP, SectionType.READ_REQUEST, SectionType.SEND_REQUEST}),
- "outbound.url.": MapParams(target=HeaderOperations.DESTINATION_OPERATIONS,
upper=True, validate=Validator.suffix_group(SuffixGroup.URL_FIELDS),
sections={SectionType.PRE_REMAP, SectionType.REMAP, SectionType.READ_REQUEST})
+ "http.cntl.": MapParams(target="set-http-cntl", upper=True,
validate=Validator.suffix_group(SuffixGroup.HTTP_CNTL_FIELDS),
sections=HTTP_SECTIONS),
+ "http.status.reason": MapParams(target="set-status-reason",
validate=Validator.quoted_or_simple(), sections=HTTP_SECTIONS),
+ "http.status": MapParams(target="set-status", validate=Validator.range(0,
999), sections=HTTP_SECTIONS),
+ "inbound.conn.dscp": MapParams(target="set-conn-dscp",
validate=Validator.nbit_int(6), sections=HTTP_SECTIONS),
+ "inbound.conn.mark": MapParams(target="set-conn-mark",
validate=Validator.nbit_int(32), sections=HTTP_SECTIONS),
+ "outbound.conn.dscp": MapParams(target="set-conn-dscp",
validate=Validator.nbit_int(6), sections={SectionType.SEND_REQUEST,
SectionType.READ_RESPONSE, SectionType.SEND_RESPONSE}),
+ "outbound.conn.mark": MapParams(target="set-conn-mark",
validate=Validator.nbit_int(32), sections={SectionType.SEND_REQUEST,
SectionType.READ_RESPONSE, SectionType.SEND_RESPONSE}),
+ "inbound.cookie.": MapParams(target=HeaderOperations.COOKIE_OPERATIONS,
validate=Validator.http_token(), sections=HTTP_SECTIONS),
+ "inbound.req.": MapParams(target=HeaderOperations.OPERATIONS, add=True,
validate=Validator.http_header_name(), sections=HTTP_SECTIONS),
+ "inbound.resp.body": MapParams(target="set-body",
validate=Validator.quoted_or_simple(), sections=HTTP_SECTIONS),
+ "inbound.resp.": MapParams(target=HeaderOperations.OPERATIONS, add=True,
validate=Validator.http_header_name(), sections={SectionType.READ_RESPONSE,
SectionType.SEND_RESPONSE}),
+ "inbound.status.reason": MapParams(target="set-status-reason",
validate=Validator.quoted_or_simple(), sections=HTTP_SECTIONS),
+ "inbound.status": MapParams(target="set-status",
validate=Validator.range(0, 999), sections=HTTP_SECTIONS),
+ "inbound.url.": MapParams(target=HeaderOperations.DESTINATION_OPERATIONS,
upper=True, validate=Validator.suffix_group(SuffixGroup.URL_FIELDS),
sections=HTTP_SECTIONS),
+ "outbound.cookie.": MapParams(target=HeaderOperations.COOKIE_OPERATIONS,
validate=Validator.http_token(), sections={SectionType.SEND_REQUEST,
SectionType.READ_RESPONSE, SectionType.SEND_RESPONSE}),
+ "outbound.req.": MapParams(target=HeaderOperations.OPERATIONS, add=True,
validate=Validator.http_header_name(), sections={SectionType.SEND_REQUEST,
SectionType.READ_RESPONSE, SectionType.SEND_RESPONSE}),
+ "outbound.resp.": MapParams(target=HeaderOperations.OPERATIONS, add=True,
validate=Validator.http_header_name(), sections={SectionType.READ_RESPONSE,
SectionType.SEND_RESPONSE}),
+ "outbound.status.reason": MapParams(target="set-status-reason",
validate=Validator.quoted_or_simple(), sections=HTTP_SECTIONS),
+ "outbound.status": MapParams(target="set-status",
validate=Validator.range(0, 999), sections=HTTP_SECTIONS),
+ "outbound.url.": MapParams(target=HeaderOperations.DESTINATION_OPERATIONS,
upper=True, validate=Validator.suffix_group(SuffixGroup.URL_FIELDS),
sections={SectionType.PRE_REMAP, SectionType.REMAP, SectionType.READ_REQUEST,
SectionType.SEND_REQUEST})
}
STATEMENT_FUNCTION_MAP: dict[str, MapParams] = {
- "add-header": MapParams(target="add-header",
validate=Validator.arg_count(2).arg_at(0,
Validator.http_header_name()).arg_at(1, Validator.quoted_or_simple())),
+ "add-header": MapParams(target="add-header",
validate=Validator.arg_count(2).arg_at(0,
Validator.http_header_name()).arg_at(1, Validator.quoted_or_simple()),
sections=HTTP_SECTIONS),
"counter": MapParams(target="counter",
validate=Validator.arg_count(1).quoted_or_simple()),
"set-debug": MapParams(target="set-debug",
validate=Validator.arg_count(0)),
"no-op": MapParams(target="no-op", validate=Validator.arg_count(0)),
- "remove_query": MapParams(target="rm-destination QUERY",
validate=Validator.arg_count(1).quoted_or_simple()),
- "keep_query": MapParams(target="rm-destination QUERY",
validate=Validator.arg_count(1).quoted_or_simple()),
- "run-plugin": MapParams(target="run-plugin",
validate=Validator.min_args(1).quoted_or_simple()),
- "set-body-from": MapParams(target="set-body-from",
validate=Validator.arg_count(1).quoted_or_simple()),
- "set-config": MapParams(target="set-config",
validate=Validator.arg_count(2).quoted_or_simple()),
- "set-redirect": MapParams(target="set-redirect",
validate=Validator.arg_count(2).arg_at(0, Validator.range(300, 399)).arg_at(1,
Validator.quoted_or_simple())),
- "skip-remap": MapParams(target="skip-remap",
validate=Validator.arg_count(1).suffix_group(SuffixGroup.BOOL_FIELDS)._add(Validator.normalize_arg_at(0))),
- "set-plugin-cntl": MapParams(target="set-plugin-cntl",
validate=Validator.arg_count(2)._add(Validator.normalize_arg_at(0)).arg_at(0,
Validator.suffix_group(SuffixGroup.PLUGIN_CNTL_FIELDS))._add(Validator.normalize_arg_at(1))._add(Validator.conditional_arg_validation(SuffixGroup.PLUGIN_CNTL_MAPPING.value))),
+ "remove_query": MapParams(target="rm-destination QUERY",
validate=Validator.arg_count(1).quoted_or_simple(), sections=HTTP_SECTIONS),
+ "keep_query": MapParams(target="rm-destination QUERY",
validate=Validator.arg_count(1).quoted_or_simple(), sections=HTTP_SECTIONS),
+ "run-plugin": MapParams(target="run-plugin",
validate=Validator.min_args(1).quoted_or_simple(), sections=HTTP_SECTIONS),
+ "set-body-from": MapParams(target="set-body-from",
validate=Validator.arg_count(1).quoted_or_simple(), sections=HTTP_SECTIONS),
+ "set-config": MapParams(target="set-config",
validate=Validator.arg_count(2).quoted_or_simple(), sections=HTTP_SECTIONS),
+ "set-redirect": MapParams(target="set-redirect",
validate=Validator.arg_count(2).arg_at(0, Validator.range(300, 399)).arg_at(1,
Validator.quoted_or_simple()), sections=HTTP_SECTIONS),
+ "skip-remap": MapParams(target="skip-remap",
validate=Validator.arg_count(1).suffix_group(SuffixGroup.BOOL_FIELDS)._add(Validator.normalize_arg_at(0)),
sections={SectionType.PRE_REMAP, SectionType.REMAP, SectionType.READ_REQUEST}),
+ "set-plugin-cntl": MapParams(target="set-plugin-cntl",
validate=Validator.arg_count(2)._add(Validator.normalize_arg_at(0)).arg_at(0,
Validator.suffix_group(SuffixGroup.PLUGIN_CNTL_FIELDS))._add(Validator.normalize_arg_at(1))._add(Validator.conditional_arg_validation(SuffixGroup.PLUGIN_CNTL_MAPPING.value)),
sections=HTTP_SECTIONS),
}
FUNCTION_MAP: dict[str, MapParams] = {
@@ -76,21 +84,21 @@ FUNCTION_MAP: dict[str, MapParams] = {
CONDITION_MAP: dict[str, MapParams] = {
# Exact matches with reverse mapping info
"inbound.ip": MapParams(target="%{IP:CLIENT}", rev={"reverse_tag": "IP",
"reverse_payload": "CLIENT"}),
- "inbound.method": MapParams(target="%{METHOD}", rev={"reverse_tag":
"METHOD", "ambiguous": True}),
+ "inbound.method": MapParams(target="%{METHOD}", sections=HTTP_SECTIONS,
rev={"reverse_tag": "METHOD", "ambiguous": True}),
"inbound.server": MapParams(target="%{IP:INBOUND}", rev={"reverse_tag":
"IP", "reverse_payload": "INBOUND"}),
- "inbound.status": MapParams(target="%{STATUS}", rev={"reverse_tag":
"STATUS", "ambiguous": True}),
+ "inbound.status": MapParams(target="%{STATUS}", sections=HTTP_SECTIONS,
rev={"reverse_tag": "STATUS", "ambiguous": True}),
"now": MapParams(target="%{NOW}"),
- "outbound.ip": MapParams(target="%{IP:SERVER}",
sections={SectionType.PRE_REMAP, SectionType.REMAP, SectionType.READ_REQUEST},
rev={"reverse_tag": "IP", "reverse_payload": "SERVER"}),
- "outbound.method": MapParams(target="%{METHOD}",
sections={SectionType.PRE_REMAP, SectionType.REMAP, SectionType.READ_REQUEST},
rev={"reverse_tag": "METHOD", "ambiguous": True}),
- "outbound.server": MapParams(target="%{IP:OUTBOUND}",
sections={SectionType.PRE_REMAP, SectionType.REMAP, SectionType.READ_REQUEST},
rev={"reverse_tag": "IP", "reverse_payload": "OUTBOUND"}),
- "outbound.status": MapParams(target="%{STATUS}",
sections={SectionType.PRE_REMAP, SectionType.REMAP, SectionType.READ_REQUEST,
SectionType.SEND_REQUEST}, rev={"reverse_tag": "STATUS", "ambiguous": True}),
+ "outbound.ip": MapParams(target="%{IP:SERVER}",
sections={SectionType.SEND_REQUEST, SectionType.READ_RESPONSE,
SectionType.SEND_RESPONSE}, rev={"reverse_tag": "IP", "reverse_payload":
"SERVER"}),
+ "outbound.method": MapParams(target="%{METHOD}",
sections={SectionType.SEND_REQUEST, SectionType.READ_RESPONSE,
SectionType.SEND_RESPONSE}, rev={"reverse_tag": "METHOD", "ambiguous": True}),
+ "outbound.server": MapParams(target="%{IP:OUTBOUND}",
sections={SectionType.SEND_REQUEST, SectionType.READ_RESPONSE,
SectionType.SEND_RESPONSE}, rev={"reverse_tag": "IP", "reverse_payload":
"OUTBOUND"}),
+ "outbound.status": MapParams(target="%{STATUS}",
sections={SectionType.SEND_REQUEST, SectionType.READ_RESPONSE,
SectionType.SEND_RESPONSE}, rev={"reverse_tag": "STATUS", "ambiguous": True}),
"tcp.info": MapParams(target="%{TCP-INFO}"),
# Prefix matches
"capture.": MapParams(target="LAST-CAPTURE", prefix=True,
validate=Validator.range(0, 9)),
- "from.url.": MapParams(target="FROM-URL", upper=True, prefix=True,
validate=Validator.suffix_group(SuffixGroup.URL_FIELDS)),
+ "from.url.": MapParams(target="FROM-URL", upper=True, prefix=True,
validate=Validator.suffix_group(SuffixGroup.URL_FIELDS),
sections=HTTP_SECTIONS),
"geo.": MapParams(target="GEO", upper=True, prefix=True,
validate=Validator.suffix_group(SuffixGroup.GEO_FIELDS)),
- "http.cntl.": MapParams(target="HTTP-CNTL", upper=True,
validate=Validator.suffix_group(SuffixGroup.HTTP_CNTL_FIELDS)),
+ "http.cntl.": MapParams(target="HTTP-CNTL", upper=True,
validate=Validator.suffix_group(SuffixGroup.HTTP_CNTL_FIELDS),
sections=HTTP_SECTIONS),
"id.": MapParams(target="ID", upper=True,
validate=Validator.suffix_group(SuffixGroup.ID_FIELDS)),
"inbound.conn.client-cert.SAN.":
MapParams(target="INBOUND:CLIENT-CERT:SAN", upper=True, prefix=True,
validate=Validator.suffix_group(SuffixGroup.SAN_FIELDS)),
"inbound.conn.server-cert.SAN.":
MapParams(target="INBOUND:SERVER-CERT:SAN", upper=True, prefix=True,
validate=Validator.suffix_group(SuffixGroup.SAN_FIELDS)),
@@ -99,23 +107,23 @@ CONDITION_MAP: dict[str, MapParams] = {
"inbound.conn.client-cert.": MapParams(target="INBOUND:CLIENT-CERT",
upper=True, prefix=True,
validate=Validator.suffix_group(SuffixGroup.CERT_FIELDS)),
"inbound.conn.server-cert.": MapParams(target="INBOUND:SERVER-CERT",
upper=True, prefix=True,
validate=Validator.suffix_group(SuffixGroup.CERT_FIELDS)),
"inbound.conn.": MapParams(target="INBOUND", upper=True, prefix=True,
validate=Validator.suffix_group(SuffixGroup.CONN_FIELDS)),
- "inbound.cookie.": MapParams(target="COOKIE", prefix=True,
validate=Validator.http_token(), rev={"reverse_fallback": "inbound.cookie."}),
- "inbound.req.": MapParams(target="CLIENT-HEADER", prefix=True,
validate=Validator.http_header_name(), rev={"reverse_fallback":
"inbound.req."}),
- "inbound.resp.": MapParams(target="HEADER", prefix=True,
validate=Validator.http_header_name(), rev={"reverse_context":
"header_condition"}),
- "inbound.url.": MapParams(target="CLIENT-URL", upper=True, prefix=True,
validate=Validator.suffix_group(SuffixGroup.URL_FIELDS)),
+ "inbound.cookie.": MapParams(target="COOKIE", prefix=True,
validate=Validator.http_token(), sections=HTTP_SECTIONS,
rev={"reverse_fallback": "inbound.cookie."}),
+ "inbound.req.": MapParams(target="CLIENT-HEADER", prefix=True,
validate=Validator.http_header_name(), sections=HTTP_SECTIONS,
rev={"reverse_fallback": "inbound.req."}),
+ "inbound.resp.": MapParams(target="HEADER", prefix=True,
validate=Validator.http_header_name(), sections={SectionType.READ_RESPONSE,
SectionType.SEND_RESPONSE}, rev={"reverse_context": "header_condition"}),
+ "inbound.url.": MapParams(target="CLIENT-URL", upper=True, prefix=True,
validate=Validator.suffix_group(SuffixGroup.URL_FIELDS),
sections=HTTP_SECTIONS),
"now.": MapParams(target="NOW", upper=True,
validate=Validator.suffix_group(SuffixGroup.DATE_FIELDS)),
- "outbound.conn.client-cert.SAN.":
MapParams(target="OUTBOUND:CLIENT-CERT:SAN", upper=True, prefix=True,
validate=Validator.suffix_group(SuffixGroup.SAN_FIELDS),
sections={SectionType.PRE_REMAP, SectionType.REMAP, SectionType.READ_REQUEST}),
- "outbound.conn.server-cert.SAN.":
MapParams(target="OUTBOUND:SERVER-CERT:SAN", upper=True, prefix=True,
validate=Validator.suffix_group(SuffixGroup.SAN_FIELDS),
sections={SectionType.PRE_REMAP, SectionType.REMAP, SectionType.READ_REQUEST}),
- "outbound.conn.client-cert.san.":
MapParams(target="OUTBOUND:CLIENT-CERT:SAN", upper=True, prefix=True,
validate=Validator.suffix_group(SuffixGroup.SAN_FIELDS),
sections={SectionType.PRE_REMAP, SectionType.REMAP, SectionType.READ_REQUEST}),
- "outbound.conn.server-cert.san.":
MapParams(target="OUTBOUND:SERVER-CERT:SAN", upper=True, prefix=True,
validate=Validator.suffix_group(SuffixGroup.SAN_FIELDS),
sections={SectionType.PRE_REMAP, SectionType.REMAP, SectionType.READ_REQUEST}),
- "outbound.conn.client-cert.": MapParams(target="OUTBOUND:CLIENT-CERT",
upper=True, prefix=True,
validate=Validator.suffix_group(SuffixGroup.CERT_FIELDS),
sections={SectionType.PRE_REMAP, SectionType.REMAP, SectionType.READ_REQUEST}),
- "outbound.conn.server-cert.": MapParams(target="OUTBOUND:SERVER-CERT",
upper=True, prefix=True,
validate=Validator.suffix_group(SuffixGroup.CERT_FIELDS),
sections={SectionType.PRE_REMAP, SectionType.REMAP, SectionType.READ_REQUEST}),
- "outbound.conn.": MapParams(target="OUTBOUND", upper=True, prefix=True,
validate=Validator.suffix_group(SuffixGroup.CONN_FIELDS),
sections={SectionType.PRE_REMAP, SectionType.REMAP, SectionType.READ_REQUEST}),
- "outbound.cookie.": MapParams(target="COOKIE", prefix=True,
validate=Validator.http_token(), sections={SectionType.PRE_REMAP,
SectionType.REMAP, SectionType.READ_REQUEST}, rev={"reverse_fallback":
"inbound.cookie."}),
- "outbound.req.": MapParams(target="HEADER", prefix=True,
validate=Validator.http_header_name(), sections={SectionType.PRE_REMAP,
SectionType.REMAP, SectionType.READ_REQUEST}, rev={"reverse_context":
"header_condition"}),
- "outbound.resp.": MapParams(target="HEADER", prefix=True,
validate=Validator.http_header_name(), sections={SectionType.PRE_REMAP,
SectionType.REMAP, SectionType.READ_REQUEST, SectionType.SEND_REQUEST},
rev={"reverse_context": "header_condition"}),
- "outbound.url.": MapParams(target="NEXT-HOP", upper=True, prefix=True,
validate=Validator.suffix_group(SuffixGroup.URL_FIELDS),
sections={SectionType.PRE_REMAP, SectionType.REMAP, SectionType.READ_REQUEST}),
- "to.url.": MapParams(target="TO-URL", upper=True, prefix=True,
validate=Validator.suffix_group(SuffixGroup.URL_FIELDS)),
+ "outbound.conn.client-cert.SAN.":
MapParams(target="OUTBOUND:CLIENT-CERT:SAN", upper=True, prefix=True,
validate=Validator.suffix_group(SuffixGroup.SAN_FIELDS),
sections={SectionType.SEND_REQUEST, SectionType.READ_RESPONSE,
SectionType.SEND_RESPONSE}),
+ "outbound.conn.server-cert.SAN.":
MapParams(target="OUTBOUND:SERVER-CERT:SAN", upper=True, prefix=True,
validate=Validator.suffix_group(SuffixGroup.SAN_FIELDS),
sections={SectionType.SEND_REQUEST, SectionType.READ_RESPONSE,
SectionType.SEND_RESPONSE}),
+ "outbound.conn.client-cert.san.":
MapParams(target="OUTBOUND:CLIENT-CERT:SAN", upper=True, prefix=True,
validate=Validator.suffix_group(SuffixGroup.SAN_FIELDS),
sections={SectionType.SEND_REQUEST, SectionType.READ_RESPONSE,
SectionType.SEND_RESPONSE}),
+ "outbound.conn.server-cert.san.":
MapParams(target="OUTBOUND:SERVER-CERT:SAN", upper=True, prefix=True,
validate=Validator.suffix_group(SuffixGroup.SAN_FIELDS),
sections={SectionType.SEND_REQUEST, SectionType.READ_RESPONSE,
SectionType.SEND_RESPONSE}),
+ "outbound.conn.client-cert.": MapParams(target="OUTBOUND:CLIENT-CERT",
upper=True, prefix=True,
validate=Validator.suffix_group(SuffixGroup.CERT_FIELDS),
sections={SectionType.SEND_REQUEST, SectionType.READ_RESPONSE,
SectionType.SEND_RESPONSE}),
+ "outbound.conn.server-cert.": MapParams(target="OUTBOUND:SERVER-CERT",
upper=True, prefix=True,
validate=Validator.suffix_group(SuffixGroup.CERT_FIELDS),
sections={SectionType.SEND_REQUEST, SectionType.READ_RESPONSE,
SectionType.SEND_RESPONSE}),
+ "outbound.conn.": MapParams(target="OUTBOUND", upper=True, prefix=True,
validate=Validator.suffix_group(SuffixGroup.CONN_FIELDS),
sections={SectionType.SEND_REQUEST, SectionType.READ_RESPONSE,
SectionType.SEND_RESPONSE}),
+ "outbound.cookie.": MapParams(target="COOKIE", prefix=True,
validate=Validator.http_token(), sections={SectionType.SEND_REQUEST,
SectionType.READ_RESPONSE, SectionType.SEND_RESPONSE}, rev={"reverse_fallback":
"inbound.cookie."}),
+ "outbound.req.": MapParams(target="HEADER", prefix=True,
validate=Validator.http_header_name(), sections={SectionType.SEND_REQUEST,
SectionType.READ_RESPONSE, SectionType.SEND_RESPONSE}, rev={"reverse_context":
"header_condition"}),
+ "outbound.resp.": MapParams(target="HEADER", prefix=True,
validate=Validator.http_header_name(), sections={SectionType.READ_RESPONSE,
SectionType.SEND_RESPONSE}, rev={"reverse_context": "header_condition"}),
+ "outbound.url.": MapParams(target="NEXT-HOP", upper=True, prefix=True,
validate=Validator.suffix_group(SuffixGroup.URL_FIELDS),
sections={SectionType.PRE_REMAP, SectionType.REMAP, SectionType.READ_REQUEST,
SectionType.SEND_REQUEST, SectionType.READ_RESPONSE,
SectionType.SEND_RESPONSE}),
+ "to.url.": MapParams(target="TO-URL", upper=True, prefix=True,
validate=Validator.suffix_group(SuffixGroup.URL_FIELDS),
sections=HTTP_SECTIONS),
}
FALLBACK_TAG_MAP: dict[str, tuple[str, bool]] = {
diff --git a/tools/hrw4u/src/visitor.py b/tools/hrw4u/src/visitor.py
index a3cbc285da..922187a6f7 100644
--- a/tools/hrw4u/src/visitor.py
+++ b/tools/hrw4u/src/visitor.py
@@ -162,7 +162,8 @@ class HRW4UVisitor(hrw4uVisitor, BaseHRWVisitor):
return replacement
if m.group("var"):
var_name = m.group("var").strip()
- replacement, _ = self._cached_symbol_resolution(var_name,
self.current_section.value)
+ # Use resolve_condition directly to properly validate
section restrictions
+ replacement, _ =
self.symbol_resolver.resolve_condition(var_name, self.current_section)
self.debug_log(f"substitute: {{{var_name}}} ->
{replacement}")
return replacement
raise SymbolResolutionError(m.group(0), "Unrecognized
substitution format")
@@ -340,7 +341,7 @@ class HRW4UVisitor(hrw4uVisitor, BaseHRWVisitor):
subst_args = [
self._substitute_strings(arg, ctx) if
arg.startswith('"') and arg.endswith('"') else arg for arg in args
]
- symbol = self.symbol_resolver.resolve_statement_func(func,
subst_args)
+ symbol = self.symbol_resolver.resolve_statement_func(func,
subst_args, self.current_section)
self.emit_statement(symbol)
return
diff --git a/tools/hrw4u/tests/data/examples/all-nonsense.ast.txt
b/tools/hrw4u/tests/data/examples/all-nonsense.ast.txt
index 92dbbd6345..bc4bfcfed1 100644
--- a/tools/hrw4u/tests/data/examples/all-nonsense.ast.txt
+++ b/tools/hrw4u/tests/data/examples/all-nonsense.ast.txt
@@ -1 +1 @@
-(program (programItem (section (varSection VARS { (variables (variablesItem
(commentLine # Boolean and integer state you can flip/use across sections))
(variablesItem (variableDecl FlagA : bool ;)) (variablesItem (variableDecl
FlagB : bool ;)) (variablesItem (variableDecl Cnt8 : int8 ;)) (variablesItem
(variableDecl Big16 : int16 ;))) }))) (programItem (section TXN_START {
(sectionBody (commentLine # Plugin controls)) (sectionBody (statement
http.cntl.TXN_DEBUG = (value true) ;)) (sectio [...]
+(program (programItem (section (varSection VARS { (variables (variablesItem
(commentLine # Boolean and integer state you can flip/use across sections))
(variablesItem (variableDecl FlagA : bool ;)) (variablesItem (variableDecl
FlagB : bool ;)) (variablesItem (variableDecl Cnt8 : int8 ;)) (variablesItem
(variableDecl Big16 : int16 ;))) }))) (programItem (section REMAP {
(sectionBody (commentLine # Plugin controls)) (sectionBody (statement
http.cntl.TXN_DEBUG = (value true) ;)) (sectionBod [...]
diff --git a/tools/hrw4u/tests/data/examples/all-nonsense.input.txt
b/tools/hrw4u/tests/data/examples/all-nonsense.input.txt
index f756ead1c7..e9d62ea221 100644
--- a/tools/hrw4u/tests/data/examples/all-nonsense.input.txt
+++ b/tools/hrw4u/tests/data/examples/all-nonsense.input.txt
@@ -6,7 +6,7 @@ VARS {
Big16: int16;
}
-TXN_START {
+REMAP {
# Plugin controls
http.cntl.TXN_DEBUG = true;
http.cntl.LOGGING = true;
diff --git a/tools/hrw4u/tests/data/examples/all-nonsense.output.txt
b/tools/hrw4u/tests/data/examples/all-nonsense.output.txt
index ca3bf8e2c5..0134d0aa96 100644
--- a/tools/hrw4u/tests/data/examples/all-nonsense.output.txt
+++ b/tools/hrw4u/tests/data/examples/all-nonsense.output.txt
@@ -1,7 +1,7 @@
# Boolean and integer state you can flip/use across sections
-cond %{TXN_START_HOOK} [AND]
+cond %{REMAP_PSEUDO_HOOK} [AND]
# Plugin controls
set-http-cntl TXN_DEBUG true
set-http-cntl LOGGING true
diff --git a/tools/hrw4u/tests/data/hooks/inbound_resp_section.fail.error.txt
b/tools/hrw4u/tests/data/hooks/inbound_resp_section.fail.error.txt
new file mode 100644
index 0000000000..5fa65ba46a
--- /dev/null
+++ b/tools/hrw4u/tests/data/hooks/inbound_resp_section.fail.error.txt
@@ -0,0 +1,3 @@
+tests/data/hooks/inbound_resp_section.fail.input.txt:2:4: error:
inbound.resp.X-Test is not available in the REMAP section
+ 2 | inbound.resp.X-Test = "should fail";
+ | ^
diff --git a/tools/hrw4u/tests/data/hooks/inbound_resp_section.fail.input.txt
b/tools/hrw4u/tests/data/hooks/inbound_resp_section.fail.input.txt
new file mode 100644
index 0000000000..30fcfd11c4
--- /dev/null
+++ b/tools/hrw4u/tests/data/hooks/inbound_resp_section.fail.input.txt
@@ -0,0 +1,3 @@
+REMAP {
+ inbound.resp.X-Test = "should fail";
+}
diff --git a/tools/hrw4u/tests/data/hooks/outbound_resp_section.fail.error.txt
b/tools/hrw4u/tests/data/hooks/outbound_resp_section.fail.error.txt
new file mode 100644
index 0000000000..0d33d3248f
--- /dev/null
+++ b/tools/hrw4u/tests/data/hooks/outbound_resp_section.fail.error.txt
@@ -0,0 +1,3 @@
+tests/data/hooks/outbound_resp_section.fail.input.txt:2:4: error: symbol error
in {}: outbound.resp.X-Origin is not available in the REMAP section
+ 2 | inbound.req.X-Test = "resp={outbound.resp.X-Origin}";
+ | ^
diff --git a/tools/hrw4u/tests/data/hooks/outbound_resp_section.fail.input.txt
b/tools/hrw4u/tests/data/hooks/outbound_resp_section.fail.input.txt
new file mode 100644
index 0000000000..78ddf6b985
--- /dev/null
+++ b/tools/hrw4u/tests/data/hooks/outbound_resp_section.fail.input.txt
@@ -0,0 +1,3 @@
+REMAP {
+ inbound.req.X-Test = "resp={outbound.resp.X-Origin}";
+}
diff --git a/tools/hrw4u/tests/data/ops/skip-remap.ast.txt
b/tools/hrw4u/tests/data/ops/skip-remap.ast.txt
index 7a3f3fe731..f817d0995f 100644
--- a/tools/hrw4u/tests/data/ops/skip-remap.ast.txt
+++ b/tools/hrw4u/tests/data/ops/skip-remap.ast.txt
@@ -1 +1 @@
-(program (programItem (section SEND_REQUEST { (sectionBody (conditional
(ifStatement if (condition (expression (term (factor (comparison (comparable
inbound.req.path) ~ (regex /foo/)))))) (block { (blockItem (statement
(functionCall skip-remap ( (argumentList (value true)) )) ;)) })))) })) <EOF>)
+(program (programItem (section REMAP { (sectionBody (conditional (ifStatement
if (condition (expression (term (factor (comparison (comparable
inbound.req.path) ~ (regex /foo/)))))) (block { (blockItem (statement
(functionCall skip-remap ( (argumentList (value true)) )) ;)) })))) })) <EOF>)
diff --git a/tools/hrw4u/tests/data/ops/skip-remap.input.txt
b/tools/hrw4u/tests/data/ops/skip-remap.input.txt
index c852f51b6a..ac8da25f33 100644
--- a/tools/hrw4u/tests/data/ops/skip-remap.input.txt
+++ b/tools/hrw4u/tests/data/ops/skip-remap.input.txt
@@ -1,4 +1,4 @@
-SEND_REQUEST {
+REMAP {
if inbound.req.path ~ /foo/ {
skip-remap(true);
}
diff --git a/tools/hrw4u/tests/data/ops/skip-remap.output.txt
b/tools/hrw4u/tests/data/ops/skip-remap.output.txt
index f9b11d1672..5f4a5194b2 100644
--- a/tools/hrw4u/tests/data/ops/skip-remap.output.txt
+++ b/tools/hrw4u/tests/data/ops/skip-remap.output.txt
@@ -1,3 +1,3 @@
-cond %{SEND_REQUEST_HDR_HOOK} [AND]
+cond %{REMAP_PSEUDO_HOOK} [AND]
cond %{CLIENT-HEADER:path} /foo/
skip-remap TRUE
diff --git a/tools/hrw4u/tests/data/ops/skip_remap_quoted_bool.fail.input.txt
b/tools/hrw4u/tests/data/ops/skip_remap_quoted_bool.fail.input.txt
index a894cbb5c8..fdaf3fa533 100644
--- a/tools/hrw4u/tests/data/ops/skip_remap_quoted_bool.fail.input.txt
+++ b/tools/hrw4u/tests/data/ops/skip_remap_quoted_bool.fail.input.txt
@@ -1,3 +1,3 @@
-SEND_REQUEST {
+REMAP {
skip-remap("true");
}
\ No newline at end of file
diff --git a/tools/hrw4u/tests/test_lsp.py b/tools/hrw4u/tests/test_lsp.py
index 81d86b6f52..5f2896bd9e 100644
--- a/tools/hrw4u/tests/test_lsp.py
+++ b/tools/hrw4u/tests/test_lsp.py
@@ -361,9 +361,9 @@ def shared_lsp_client():
@pytest.mark.parametrize(
"section,prefix,should_allow", [
+ ("REMAP", "inbound.req.", True),
+ ("SEND_REQUEST", "outbound.req.", True),
("SEND_REQUEST", "outbound.req.", True),
- ("REMAP", "outbound.req.", False),
- ("SEND_REQUEST", "outbound.resp.", False),
("READ_RESPONSE", "outbound.resp.", True),
])
def test_section_restrictions_batch(shared_lsp_client, section, prefix,
should_allow) -> None:
@@ -408,15 +408,16 @@ def
test_multi_section_inbound_always_allowed(shared_lsp_client) -> None:
def test_outbound_restrictions_batch(shared_lsp_client) -> None:
- """Batch test outbound restrictions in early vs late sections."""
- early_sections = ["PRE_REMAP", "REMAP", "READ_REQUEST"]
- late_sections = ["SEND_REQUEST", "READ_RESPONSE"]
+ """Batch test outbound restrictions - outbound features have
section-specific availability."""
+ # outbound.url. is available in PRE_REMAP through SEND_REQUEST, plus
READ_RESPONSE, SEND_RESPONSE
+ # outbound.cookie. is only available from SEND_REQUEST onwards
+ http_sections = ["PRE_REMAP", "REMAP", "READ_REQUEST", "SEND_REQUEST",
"READ_RESPONSE"]
- for section in early_sections:
+ for section in http_sections:
test_content = f"""{section} {{
outbound.
}}"""
- uri = f"file:///test_early_{section.lower()}.hrw4u"
+ uri = f"file:///test_outbound_{section.lower()}.hrw4u"
shared_lsp_client.open_document(uri, test_content)
response = shared_lsp_client.request_completion(uri, 1, 13)
@@ -426,29 +427,16 @@ def test_outbound_restrictions_batch(shared_lsp_client)
-> None:
outbound_cookie_items = [item for item in items if
item["label"].startswith("outbound.cookie.")]
outbound_url_items = [item for item in items if
item["label"].startswith("outbound.url.")]
- assert len(outbound_cookie_items) == 0, f"outbound.cookie. should NOT
be in {section}"
- assert len(outbound_url_items) == 0, f"outbound.url. should NOT be in
{section}"
-
- for section in late_sections:
- test_content = f"""{section} {{
- outbound.
-}}"""
- uri = f"file:///test_late_{section.lower()}.hrw4u"
- shared_lsp_client.open_document(uri, test_content)
-
- response = shared_lsp_client.request_completion(uri, 1, 13)
- assert response is not None
- items = response["result"]["items"]
-
- outbound_conn_items = [item for item in items if
item["label"].startswith("outbound.conn.")]
- outbound_cookie_items = [item for item in items if
item["label"].startswith("outbound.cookie.")]
-
- assert len(outbound_conn_items) > 0, f"outbound.conn. should be in
{section}"
- assert len(outbound_cookie_items) > 0, f"outbound.cookie. should be in
{section}"
+ # outbound.cookie. is only available from SEND_REQUEST onwards
+ if section in ["SEND_REQUEST", "READ_RESPONSE"]:
+ assert len(outbound_cookie_items) > 0, f"outbound.cookie. should
be in {section}"
+ # outbound.url. is available in all these sections
+ assert len(outbound_url_items) > 0, f"outbound.url. should be in
{section}"
def test_specific_outbound_conn_completions(shared_lsp_client) -> None:
- """Test specific outbound.conn completions"""
+ """Test specific outbound.conn completions (dscp/mark only available from
SEND_REQUEST onwards)"""
+ # outbound.conn.dscp and outbound.conn.mark are only available in
SEND_REQUEST, READ_RESPONSE, SEND_RESPONSE
test_content = """SEND_REQUEST {
outbound.conn.
}"""