From 35d44a2701804023ada5cca752d9b41b6465e2ee Mon Sep 17 00:00:00 2001 From: Miles Libbey Date: Mon, 27 Apr 2026 15:23:54 -0700 Subject: [PATCH] hrw4u: disallow inbound.url.{} as an operator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When one uses inbound.url.{} as an operator, for instance: REMAP { inbound.url.host = "example.com"; } hrw4u turns this into set-destination which modifies the outbound URL, making the naming actively misleading. inbound.url reads from the pristine client URL (CLIENT-URL) which is immutable — ATS provides no mechanism to set it. So, removing this as an operator, but keeping it as a valid condition. --- doc/admin-guide/configuration/hrw4u.en.rst | 8 ++++---- tools/hrw4u/src/generators.py | 4 ++-- tools/hrw4u/src/hrw_symbols.py | 2 +- tools/hrw4u/src/tables.py | 3 +-- tools/hrw4u/tests/data/examples/all-nonsense.ast.txt | 2 +- tools/hrw4u/tests/data/examples/all-nonsense.input.txt | 4 ++-- tools/hrw4u/tests/data/examples/rm-query.ast.txt | 2 +- tools/hrw4u/tests/data/examples/rm-query.input.txt | 2 +- tools/hrw4u/tests/data/ops/bad_path.fail.error.txt | 2 +- tools/hrw4u/tests/data/ops/bad_path.fail.input.txt | 2 +- tools/hrw4u/tests/data/ops/set-destination.ast.txt | 2 +- tools/hrw4u/tests/data/ops/set-destination.input.txt | 2 +- 12 files changed, 17 insertions(+), 18 deletions(-) diff --git a/doc/admin-guide/configuration/hrw4u.en.rst b/doc/admin-guide/configuration/hrw4u.en.rst index 4a0fb3f6961..f628511b9f8 100644 --- a/doc/admin-guide/configuration/hrw4u.en.rst +++ b/doc/admin-guide/configuration/hrw4u.en.rst @@ -282,7 +282,7 @@ cond %{INBOUND:} {in,out}bound.conn. inbound (:r - ``inbound.req.
`` → ``CLIENT-HEADER`` - Headers from the client request - ``outbound.req.
`` → ``SERVER-HEADER`` - Headers in the request sent to origin - - ``inbound.url.`` → ``CLIENT-URL`` - URL from the original client request + - ``inbound.url.`` → ``CLIENT-URL`` - URL from the original client request (which is immutable) - ``outbound.url.`` → ``SERVER-URL`` - URL in the request sent to origin (after remapping) - ``nexthop.`` → ``NEXT-HOP`` - Network destination info (host, port, strategy) @@ -319,7 +319,7 @@ add-header X-bar foo inbound.{req,resp}.x-Bar += "bar" Add the header t counter my_stat counter("my_stat") Increment internal counter rm-client-header X-Foo inbound.req.X-Foo = "" Remove a client request header rm-cookie foo {in,out}bound.cookie.foo = "" Remove the cookie named foo -rm-destination inbound.url. = "" Remove an URL component, ``C`` is path, query etc. +rm-destination outbound.url. = "" Remove an URL component, ``C`` is path, query etc. rm-header X-Foo {in,out}bound.req.X-Foo = "" Context sensitive header removal rm-destination QUERY ... remove_query("foo,bar") Remove specified query keys rm-destination QUERY ... [I] keep_query("foo,bar") Keep only specified query keys @@ -330,7 +330,7 @@ set-config 12 set-config("name", 17) Set a configurat set-conn-dscp 8 inbound.conn.dscp = 8 Set the DSCP value for the connection set-conn-mark 17 inbound.conn.mark = 17 Set the MARK value for the connection set-cookie foo bar {in,out}bound.cookie.foo = "bar" Set a request/response cookie named foo -set-destination bar {in,out}bound.url. = "bar" Set a URL component, <:ref:`C`> is path, query etc. +set-destination bar outbound.url. = "bar" Set a URL component, <:ref:`C`> is path, query etc. set-header X-Bar foo inbound.{req,resp}.X-Bar = "foo" Assign a client request/origin response header set-plugin-cntl set-plugin-cntl() = Set the plugin control to , see <:ref:`C`> set-redirect set-redirect(302, "\https://...") Set a redirect response @@ -1075,7 +1075,7 @@ Remove Client Query Parameters The following ruleset removes any query parameters set by the client.:: REMAP { - inbound.url.query = ""; + outbound.url.query = ""; } Remove only a few select query parameters:: diff --git a/tools/hrw4u/src/generators.py b/tools/hrw4u/src/generators.py index 20c00ce35b4..0e73e4927b8 100644 --- a/tools/hrw4u/src/generators.py +++ b/tools/hrw4u/src/generators.py @@ -72,8 +72,8 @@ def generate_context_mappings(self) -> dict[str, dict[SectionType | frozenset[Se }, "URL_CONTEXT_MAP": { - SectionType.REMAP: "inbound.url.", - frozenset({SectionType.PRE_REMAP, SectionType.READ_REQUEST, SectionType.SEND_REQUEST}): "outbound.url." + frozenset({SectionType.REMAP, SectionType.PRE_REMAP, SectionType.READ_REQUEST, + SectionType.SEND_REQUEST}): "outbound.url." } } diff --git a/tools/hrw4u/src/hrw_symbols.py b/tools/hrw4u/src/hrw_symbols.py index 0a0339b2d0d..dd7f5160e62 100644 --- a/tools/hrw4u/src/hrw_symbols.py +++ b/tools/hrw4u/src/hrw_symbols.py @@ -480,7 +480,7 @@ def op_to_hrw4u(self, cmd: str, args: list[str], section: SectionType | None, op if len(args) > 1: func = "keep_query" if op_state.invert else "remove_query" return f"{func}({self._rewrite_inline_percents(args[1], section)})" - return 'inbound.url.query = ""' + return f'{self.get_prefix_for_context("destination_ops", section)}query = ""' toks = [cmd] + args line = " ".join(toks) diff --git a/tools/hrw4u/src/tables.py b/tools/hrw4u/src/tables.py index 86d2de67a79..a643e2f630e 100644 --- a/tools/hrw4u/src/tables.py +++ b/tools/hrw4u/src/tables.py @@ -47,7 +47,6 @@ "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}), @@ -156,7 +155,7 @@ "header_condition": ("HEADER_CONTEXT_MAP", "inbound.resp."), "header_ops": ("HEADER_CONTEXT_MAP", "inbound.resp."), "cookie_ops": "inbound.cookie.", - "destination_ops": ("URL_CONTEXT_MAP", "inbound.url.") + "destination_ops": ("URL_CONTEXT_MAP", "outbound.url.") } # Operator command mappings for reverse resolution diff --git a/tools/hrw4u/tests/data/examples/all-nonsense.ast.txt b/tools/hrw4u/tests/data/examples/all-nonsense.ast.txt index f404d038eb4..1ad0790e27f 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 REMAP { (sectionBody (commentLine # Plugin controls)) (sectionBody (statement http.cntl.TXN_DEBUG = (value true) ;)) (sectionBody (statement http.cntl.LOGGING = (value true) ;)) (sectionBody (statement http.cntl.REQ_CACHEABLE = (value true) ;)) (sectionBody (statement http.cntl.RESP_CACHEABLE = (value true) ;)) (sectionBody (statement http.cntl.SERVER_NO_STORE = (value false) ;)) (sectionBody (statement http.cntl.SKIP_REMAP = (value false) ;)) (sectionBody (statement http.cntl.INTERCEPT_RETRY = (value false) ;)) (sectionBody (commentLine # allow intercept retry)) (sectionBody (commentLine # Plugin-level knobs)) (sectionBody (statement (functionCall set-plugin-cntl ( (argumentList (value "TIMEZONE") , (value "GMT")) )) ;)) (sectionBody (statement (functionCall set-plugin-cntl ( (argumentList (value INBOUND_IP_SOURCE) , (value "PEER")) )) ;)) (sectionBody (statement (functionCall set-config ( (argumentList (value "proxy.config.http.allow_multi_range") , (value 1)) )) ;)) (sectionBody (commentLine # Connection marks (client side))) (sectionBody (statement inbound.conn.dscp = (value 8) ;)) (sectionBody (statement inbound.conn.mark = (value 1734) ;)) (sectionBody (commentLine # Initialize and twiddle user vars)) (sectionBody (statement FlagA = (value true) ;)) (sectionBody (statement FlagB = (value false) ;)) (sectionBody (statement Cnt8 = (value 7) ;)) (sectionBody (statement Big16 = (value 1024) ;)) (sectionBody (commentLine # Simple demo: count every transaction seen)) (sectionBody (statement (functionCall counter ( (argumentList (value "txn_start_count")) )) ;)) })) (programItem (section PRE_REMAP { (sectionBody (conditional (ifStatement if (condition (expression (term (factor ( (expression (term (term (factor (comparison (comparable inbound.method) == (value "GET") (modifier with (modifierList NOCASE))))) && (factor ( (expression (expression (term (factor (comparison (comparable inbound.url.path) in (set [ (value "mp3") , (value "m3u") , (value "m3u8") ]) (modifier with (modifierList EXT , NOCASE)))))) || (term (factor (comparison (comparable inbound.url.host) ~ (regex /(?i)^api\./))))) )))) ))))) (block { (blockItem (statement inbound.req.X-Prefilter = (value "static-or-api") ;)) })) (elifClause elif (condition (expression (term (factor ( (expression (expression (term (factor (functionCall access ( (argumentList (value "/var/developertesting")) ))))) || (term (factor (functionCall internal ( ))))) ))))) (block { (blockItem (statement inbound.req.X-Prefilter = (value "dev-or-internal") ;)) })) (elifClause elif (condition (expression (term (factor ( (expression (term (factor (comparison (comparable (functionCall random ( (argumentList (value 100)) ))) > (value 50))))) ))))) (block { (blockItem (statement inbound.req.X-Prefilter = (value "coinflip") ;)) })) (elseClause else (block { (blockItem (statement (functionCall no-op ( )) ;)) })))) (sectionBody (commentLine # Show CIDR and ID usage; also demonstrate list membership on method)) (sectionBody (conditional (ifStatement if (condition (expression (term (factor ( (expression (expression (term (factor (comparison (comparable (functionCall cidr ( (argumentList (value 16) , (value 48)) ))) == (value "10.0.0.0"))))) || (term (factor (comparison (comparable (functionCall cidr ( (argumentList (value 8) , (value 8)) ))) == (value "fd00::"))))) ))))) (block { (blockItem (statement inbound.req.X-Network = (value "private") ;)) })))) (sectionBody (conditional (ifStatement if (condition (expression (term (factor ( (expression (term (factor (comparison (comparable inbound.method) in (set [ (value "POST") , (value "PUT") ]))))) ))))) (block { (blockItem (statement FlagB = (value true) ;)) })))) })) (programItem (section REMAP { (sectionBody (conditional (ifStatement if (condition (expression (term (factor ( (expression (term (factor (comparison (comparable from.url.host) == (value "old.example.com") (modifier with (modifierList NOCASE)))))) ))))) (block { (blockItem (statement inbound.url.host = (value "new.example.com") ;)) (blockItem (statement (functionCall keep_query ( (argumentList (value "id,utm_campaign")) )) ;)) })))) (sectionBody (conditional (ifStatement if (condition (expression (term (factor ( (expression (term (factor (comparison (comparable to.url.path) ~ (regex /(?i)^\/legacy\//))))) ))))) (block { (blockItem (statement inbound.url.path = (value "/v2/") ;)) (blockItem (statement (functionCall remove_query ( (argumentList (value "debug,trace")) )) ;)) })))) (sectionBody (statement inbound.req.X-foo = (value inbound.conn.client-cert.SAN) ;)) (sectionBody (commentLine # Run a remap plugin conditionally (args are pass-through))) (sectionBody (conditional (ifStatement if (condition (expression (term (factor ( (expression (term (factor (comparison (comparable inbound.url.host) == (value "plugins.example"))))) ))))) (block { (blockItem (statement (functionCall run-plugin ( (argumentList (value "regex_remap.so") , (value "in:^/foo/(.*)$") , (value "out:/bar/$1")) )) ;)) })))) (sectionBody (commentLine # Demonstrate last-rule behavior)) (sectionBody (conditional (ifStatement if (condition (expression (term (factor ( (expression (term (factor (comparison (comparable inbound.req.X-Bypass) == (value "1"))))) ))))) (block { (blockItem (statement (functionCall skip-remap ( (argumentList (value true)) )) ;)) (blockItem (statement break ;)) (blockItem (commentLine # like [L])) })))) })) (programItem (section READ_REQUEST { (sectionBody (commentLine # Header presence / equality and capture groups)) (sectionBody (conditional (ifStatement if (condition (expression (term (factor ( (expression (term (factor inbound.req.Strict-Transport-Security))) ))))) (block { (blockItem (statement inbound.req.X-HSTS-Present = (value "1") ;)) })))) (sectionBody (conditional (ifStatement if (condition (expression (term (factor ( (expression (term (factor (comparison (comparable inbound.req.User-Agent) ~ (regex /(?i)foo-(\d+)/))))) ))))) (block { (blockItem (statement inbound.req.X-UA-Capture = (value "{capture.1}") ;)) })))) (sectionBody (commentLine # Cookies: read, set, and remove)) (sectionBody (conditional (ifStatement if (condition (expression (term (factor ( (expression (term (factor (comparison (comparable inbound.cookie.session) == (value "admin") (modifier with (modifierList NOCASE)))))) ))))) (block { (blockItem (statement inbound.cookie.role = (value "super") ;)) })) (elseClause else (block { (blockItem (statement inbound.cookie.role = (value "guest") ;)) })))) (sectionBody (statement inbound.cookie.obsolete = (value "") ;)) (sectionBody (commentLine # IP and URL string expansions in values)) (sectionBody (statement inbound.req.X-Client = (value "{inbound.ip} : {inbound.url.port}") ;)) (sectionBody (statement inbound.req.X-Req-Id = (value "{id.UNIQUE}") ;)) (sectionBody (commentLine # Geo lookups as values)) (sectionBody (statement inbound.req.X-Geo = (value "{geo.country}-{geo.asn}") ;)) (sectionBody (commentLine # Example of counters + vars interplay)) (sectionBody (conditional (ifStatement if (condition (expression (term (factor ( (expression (term (factor FlagB))) ))))) (block { (blockItem (statement (functionCall counter ( (argumentList (value "write_methods_seen")) )) ;)) (blockItem (statement Cnt8 = (value 42) ;)) (blockItem (commentLine # assign int8)) (blockItem (statement Big16 = (value 6553) ;)) (blockItem (commentLine # assign int16)) })))) })) (programItem (section SEND_REQUEST { (sectionBody (commentLine # Use server URL information to adjust Host header)) (sectionBody (conditional (ifStatement if (condition (expression (term (factor ( (expression (term (factor (comparison (comparable outbound.url.host) == (value "www.firstparent.com"))))) ))))) (block { (blockItem (statement outbound.req.Host = (value "vhost.firstparent.com") ;)) })) (elifClause elif (condition (expression (term (factor ( (expression (term (factor (comparison (comparable outbound.url.host) == (value "www.secondparent.com"))))) ))))) (block { (blockItem (statement outbound.req.Host = (value "vhost.secondparent.com") ;)) })))) (sectionBody (commentLine # Demonstrate HTTP control read and write)) (sectionBody (conditional (ifStatement if (condition (expression (term (factor ( (expression (term (factor ! (factor http.cntl.LOGGING)))) ))))) (block { (blockItem (statement http.cntl.LOGGING = (value true) ;)) })))) })) (programItem (section REMAP { (sectionBody (conditional (ifStatement if (condition (expression (term (factor (comparison (comparable inbound.req.X-Bar) == (value "fie") (modifier with (modifierList MID))))))) (block { })))) })) (programItem (section READ_RESPONSE { (sectionBody (commentLine # Cache lookup status checks)) (sectionBody (conditional (ifStatement if (condition (expression (term (factor ( (expression (term (factor (comparison (comparable (functionCall cache ( ))) == (value "hit-stale"))))) ))))) (block { (blockItem (statement outbound.resp.X-Cache = (value "stale") ;)) })) (elifClause elif (condition (expression (term (factor ( (expression (term (factor (comparison (comparable (functionCall cache ( ))) == (value "hit-fresh"))))) ))))) (block { (blockItem (statement outbound.resp.X-Cache = (value "fresh") ;)) })) (elifClause elif (condition (expression (term (factor ( (expression (term (factor (comparison (comparable (functionCall cache ( ))) in (set [ (value "miss") , (value "skipped") ]))))) ))))) (block { (blockItem (statement outbound.resp.X-Cache = (value "{cache()}") ;)) (blockItem (commentLine # echo the value)) })))) (sectionBody (commentLine # Status transforms + reason)) (sectionBody (conditional (ifStatement if (condition (expression (term (factor ( (expression (term (term (factor (comparison (comparable inbound.status) > (value 399)))) && (factor (comparison (comparable inbound.status) < (value 500))))) ))))) (block { (blockItem (statement http.status = (value 404) ;)) (blockItem (statement http.status.reason = (value "Not Here But Somewhere") ;)) })))) (sectionBody (commentLine # Example body overrides (allowed at READ_RESPONSE only))) (sectionBody (conditional (ifStatement if (condition (expression (term (factor (comparison (comparable outbound.status) > (value 499)))))) (block { (blockItem (statement (functionCall set-body-from ( (argumentList (value "http://errors.example.com/500?rid={id.REQUEST}")) )) ;)) })) (elifClause elif (condition (expression (term (factor ( (expression (term (factor (comparison (comparable inbound.status) == (value 418))))) ))))) (block { (blockItem (statement outbound.resp.Content-Type = (value "text/plain") ;)) (blockItem (statement outbound.resp.Server = (value "ATS-HRW4U") ;)) (blockItem (statement inbound.resp.body = (value "I am a teapot, rewritten") ;)) })))) })) (programItem (section SEND_RESPONSE { (sectionBody (statement outbound.resp.Cache-Control = (value "public, max-age=60") ;)) (sectionBody (statement outbound.resp.X-Now = (value "{now.YEAR}-{now.MONTH}-{now.DAY}T{now.HOUR}:{now.MINUTE}") ;)) (sectionBody (statement outbound.resp.X-Ports = (value "in={inbound.url.port};out={outbound.url.port}") ;)) (sectionBody (statement outbound.resp.X-IPs = (value "client={inbound.ip};server={outbound.ip}") ;)) (sectionBody (statement outbound.resp.X-ID = (value "{id.UNIQUE}") ;)) (sectionBody (statement outbound.resp.ATS-Geo-Country = (value "{geo.country}") ;)) (sectionBody (statement outbound.resp.ATS-Geo-ASN = (value "{geo.asn}") ;)) (sectionBody (statement outbound.resp.ATS-Geo-ASN-NAME = (value "{geo.ASN-NAME}") ;)) (sectionBody (statement outbound.cookie.debug = (value "on") ;)) (sectionBody (conditional (ifStatement if (condition (expression (term (factor (comparison (comparable inbound.req.X-Redirect) == (value "1")))))) (block { (blockItem (statement (functionCall set-redirect ( (argumentList (value 302) , (value "https://redirect.example/x?{inbound.url.query}")) )) ;)) })))) (sectionBody (statement (functionCall counter ( (argumentList (value "send_response_count")) )) ;)) })) (programItem (section TXN_CLOSE { (sectionBody (statement (functionCall counter ( (argumentList (value "txn_close_count")) )) ;)) (sectionBody (statement (functionCall no-op ( )) ;)) })) (programItem (section SEND_RESPONSE { (sectionBody (conditional (ifStatement if (condition (expression (term (factor inbound.conn.TLS)))) (block { (blockItem (statement inbound.resp.X-Client-Cert = (value "{inbound.conn.client-cert.PEM}") ;)) (blockItem (statement inbound.resp.X-Client-Cert-Subject = (value "{inbound.conn.client-cert.SUBJECT}") ;)) (blockItem (statement inbound.resp.X-Client-Cert-Issuer = (value "{inbound.conn.client-cert.ISSUER}") ;)) (blockItem (statement inbound.resp.X-Server-Cert-Subject = (value "{inbound.conn.server-cert.SUBJECT}") ;)) (blockItem (statement inbound.resp.X-Server-Cert-Serial = (value "{inbound.conn.server-cert.SERIAL}") ;)) (blockItem (statement inbound.resp.X-Client-SAN-DNS = (value "{inbound.conn.client-cert.SAN.DNS}") ;)) (blockItem (statement inbound.resp.X-Client-SAN-IP = (value "{inbound.conn.client-cert.SAN.IP}") ;)) (blockItem (statement inbound.resp.X-Server-SAN-Email = (value "{inbound.conn.server-cert.SAN.EMAIL}") ;)) (blockItem (statement inbound.resp.X-Server-SAN-URI = (value "{inbound.conn.server-cert.SAN.URI}") ;)) })))) })) (programItem (section SEND_REQUEST { (sectionBody (conditional (ifStatement if (condition (expression (term (factor (comparison (comparable inbound.conn.client-cert.SAN.DNS) ~ (regex /example\.com/)))))) (block { (blockItem (statement outbound.req.X-Matched-Domain = (value "true") ;)) })))) (sectionBody (conditional (ifStatement if (condition (expression (term (factor (comparison (comparable inbound.conn.client-cert.SUBJECT) ~ (regex /CN=testcert/)))))) (block { (blockItem (statement outbound.req.X-Test-Client = (value "verified") ;)) })))) })) (programItem (section READ_RESPONSE { (sectionBody (statement outbound.resp.X-Outbound-Client-Cert = (value "{outbound.conn.client-cert.PEM}") ;)) (sectionBody (statement outbound.resp.X-Outbound-Server-Subject = (value "{outbound.conn.server-cert.SUBJECT}") ;)) })) ) +(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) ;)) (sectionBody (statement http.cntl.LOGGING = (value true) ;)) (sectionBody (statement http.cntl.REQ_CACHEABLE = (value true) ;)) (sectionBody (statement http.cntl.RESP_CACHEABLE = (value true) ;)) (sectionBody (statement http.cntl.SERVER_NO_STORE = (value false) ;)) (sectionBody (statement http.cntl.SKIP_REMAP = (value false) ;)) (sectionBody (statement http.cntl.INTERCEPT_RETRY = (value false) ;)) (sectionBody (commentLine # allow intercept retry)) (sectionBody (commentLine # Plugin-level knobs)) (sectionBody (statement (functionCall set-plugin-cntl ( (argumentList (value "TIMEZONE") , (value "GMT")) )) ;)) (sectionBody (statement (functionCall set-plugin-cntl ( (argumentList (value INBOUND_IP_SOURCE) , (value "PEER")) )) ;)) (sectionBody (statement (functionCall set-config ( (argumentList (value "proxy.config.http.allow_multi_range") , (value 1)) )) ;)) (sectionBody (commentLine # Connection marks (client side))) (sectionBody (statement inbound.conn.dscp = (value 8) ;)) (sectionBody (statement inbound.conn.mark = (value 1734) ;)) (sectionBody (commentLine # Initialize and twiddle user vars)) (sectionBody (statement FlagA = (value true) ;)) (sectionBody (statement FlagB = (value false) ;)) (sectionBody (statement Cnt8 = (value 7) ;)) (sectionBody (statement Big16 = (value 1024) ;)) (sectionBody (commentLine # Simple demo: count every transaction seen)) (sectionBody (statement (functionCall counter ( (argumentList (value "txn_start_count")) )) ;)) })) (programItem (section PRE_REMAP { (sectionBody (conditional (ifStatement if (condition (expression (term (factor ( (expression (term (term (factor (comparison (comparable inbound.method) == (value "GET") (modifier with (modifierList NOCASE))))) && (factor ( (expression (expression (term (factor (comparison (comparable inbound.url.path) in (set [ (value "mp3") , (value "m3u") , (value "m3u8") ]) (modifier with (modifierList EXT , NOCASE)))))) || (term (factor (comparison (comparable inbound.url.host) ~ (regex /(?i)^api\./))))) )))) ))))) (block { (blockItem (statement inbound.req.X-Prefilter = (value "static-or-api") ;)) })) (elifClause elif (condition (expression (term (factor ( (expression (expression (term (factor (functionCall access ( (argumentList (value "/var/developertesting")) ))))) || (term (factor (functionCall internal ( ))))) ))))) (block { (blockItem (statement inbound.req.X-Prefilter = (value "dev-or-internal") ;)) })) (elifClause elif (condition (expression (term (factor ( (expression (term (factor (comparison (comparable (functionCall random ( (argumentList (value 100)) ))) > (value 50))))) ))))) (block { (blockItem (statement inbound.req.X-Prefilter = (value "coinflip") ;)) })) (elseClause else (block { (blockItem (statement (functionCall no-op ( )) ;)) })))) (sectionBody (commentLine # Show CIDR and ID usage; also demonstrate list membership on method)) (sectionBody (conditional (ifStatement if (condition (expression (term (factor ( (expression (expression (term (factor (comparison (comparable (functionCall cidr ( (argumentList (value 16) , (value 48)) ))) == (value "10.0.0.0"))))) || (term (factor (comparison (comparable (functionCall cidr ( (argumentList (value 8) , (value 8)) ))) == (value "fd00::"))))) ))))) (block { (blockItem (statement inbound.req.X-Network = (value "private") ;)) })))) (sectionBody (conditional (ifStatement if (condition (expression (term (factor ( (expression (term (factor (comparison (comparable inbound.method) in (set [ (value "POST") , (value "PUT") ]))))) ))))) (block { (blockItem (statement FlagB = (value true) ;)) })))) })) (programItem (section REMAP { (sectionBody (conditional (ifStatement if (condition (expression (term (factor ( (expression (term (factor (comparison (comparable from.url.host) == (value "old.example.com") (modifier with (modifierList NOCASE)))))) ))))) (block { (blockItem (statement outbound.url.host = (value "new.example.com") ;)) (blockItem (statement (functionCall keep_query ( (argumentList (value "id,utm_campaign")) )) ;)) })))) (sectionBody (conditional (ifStatement if (condition (expression (term (factor ( (expression (term (factor (comparison (comparable to.url.path) ~ (regex /(?i)^\/legacy\//))))) ))))) (block { (blockItem (statement outbound.url.path = (value "/v2/") ;)) (blockItem (statement (functionCall remove_query ( (argumentList (value "debug,trace")) )) ;)) })))) (sectionBody (statement inbound.req.X-foo = (value inbound.conn.client-cert.SAN) ;)) (sectionBody (commentLine # Run a remap plugin conditionally (args are pass-through))) (sectionBody (conditional (ifStatement if (condition (expression (term (factor ( (expression (term (factor (comparison (comparable inbound.url.host) == (value "plugins.example"))))) ))))) (block { (blockItem (statement (functionCall run-plugin ( (argumentList (value "regex_remap.so") , (value "in:^/foo/(.*)$") , (value "out:/bar/$1")) )) ;)) })))) (sectionBody (commentLine # Demonstrate last-rule behavior)) (sectionBody (conditional (ifStatement if (condition (expression (term (factor ( (expression (term (factor (comparison (comparable inbound.req.X-Bypass) == (value "1"))))) ))))) (block { (blockItem (statement (functionCall skip-remap ( (argumentList (value true)) )) ;)) (blockItem (statement break ;)) (blockItem (commentLine # like [L])) })))) })) (programItem (section READ_REQUEST { (sectionBody (commentLine # Header presence / equality and capture groups)) (sectionBody (conditional (ifStatement if (condition (expression (term (factor ( (expression (term (factor inbound.req.Strict-Transport-Security))) ))))) (block { (blockItem (statement inbound.req.X-HSTS-Present = (value "1") ;)) })))) (sectionBody (conditional (ifStatement if (condition (expression (term (factor ( (expression (term (factor (comparison (comparable inbound.req.User-Agent) ~ (regex /(?i)foo-(\d+)/))))) ))))) (block { (blockItem (statement inbound.req.X-UA-Capture = (value "{capture.1}") ;)) })))) (sectionBody (commentLine # Cookies: read, set, and remove)) (sectionBody (conditional (ifStatement if (condition (expression (term (factor ( (expression (term (factor (comparison (comparable inbound.cookie.session) == (value "admin") (modifier with (modifierList NOCASE)))))) ))))) (block { (blockItem (statement inbound.cookie.role = (value "super") ;)) })) (elseClause else (block { (blockItem (statement inbound.cookie.role = (value "guest") ;)) })))) (sectionBody (statement inbound.cookie.obsolete = (value "") ;)) (sectionBody (commentLine # IP and URL string expansions in values)) (sectionBody (statement inbound.req.X-Client = (value "{inbound.ip} : {inbound.url.port}") ;)) (sectionBody (statement inbound.req.X-Req-Id = (value "{id.UNIQUE}") ;)) (sectionBody (commentLine # Geo lookups as values)) (sectionBody (statement inbound.req.X-Geo = (value "{geo.country}-{geo.asn}") ;)) (sectionBody (commentLine # Example of counters + vars interplay)) (sectionBody (conditional (ifStatement if (condition (expression (term (factor ( (expression (term (factor FlagB))) ))))) (block { (blockItem (statement (functionCall counter ( (argumentList (value "write_methods_seen")) )) ;)) (blockItem (statement Cnt8 = (value 42) ;)) (blockItem (commentLine # assign int8)) (blockItem (statement Big16 = (value 6553) ;)) (blockItem (commentLine # assign int16)) })))) })) (programItem (section SEND_REQUEST { (sectionBody (commentLine # Use server URL information to adjust Host header)) (sectionBody (conditional (ifStatement if (condition (expression (term (factor ( (expression (term (factor (comparison (comparable outbound.url.host) == (value "www.firstparent.com"))))) ))))) (block { (blockItem (statement outbound.req.Host = (value "vhost.firstparent.com") ;)) })) (elifClause elif (condition (expression (term (factor ( (expression (term (factor (comparison (comparable outbound.url.host) == (value "www.secondparent.com"))))) ))))) (block { (blockItem (statement outbound.req.Host = (value "vhost.secondparent.com") ;)) })))) (sectionBody (commentLine # Demonstrate HTTP control read and write)) (sectionBody (conditional (ifStatement if (condition (expression (term (factor ( (expression (term (factor ! (factor http.cntl.LOGGING)))) ))))) (block { (blockItem (statement http.cntl.LOGGING = (value true) ;)) })))) })) (programItem (section REMAP { (sectionBody (conditional (ifStatement if (condition (expression (term (factor (comparison (comparable inbound.req.X-Bar) == (value "fie") (modifier with (modifierList MID))))))) (block { })))) })) (programItem (section READ_RESPONSE { (sectionBody (commentLine # Cache lookup status checks)) (sectionBody (conditional (ifStatement if (condition (expression (term (factor ( (expression (term (factor (comparison (comparable (functionCall cache ( ))) == (value "hit-stale"))))) ))))) (block { (blockItem (statement outbound.resp.X-Cache = (value "stale") ;)) })) (elifClause elif (condition (expression (term (factor ( (expression (term (factor (comparison (comparable (functionCall cache ( ))) == (value "hit-fresh"))))) ))))) (block { (blockItem (statement outbound.resp.X-Cache = (value "fresh") ;)) })) (elifClause elif (condition (expression (term (factor ( (expression (term (factor (comparison (comparable (functionCall cache ( ))) in (set [ (value "miss") , (value "skipped") ]))))) ))))) (block { (blockItem (statement outbound.resp.X-Cache = (value "{cache()}") ;)) (blockItem (commentLine # echo the value)) })))) (sectionBody (commentLine # Status transforms + reason)) (sectionBody (conditional (ifStatement if (condition (expression (term (factor ( (expression (term (term (factor (comparison (comparable inbound.status) > (value 399)))) && (factor (comparison (comparable inbound.status) < (value 500))))) ))))) (block { (blockItem (statement http.status = (value 404) ;)) (blockItem (statement http.status.reason = (value "Not Here But Somewhere") ;)) })))) (sectionBody (commentLine # Example body overrides (allowed at READ_RESPONSE only))) (sectionBody (conditional (ifStatement if (condition (expression (term (factor (comparison (comparable outbound.status) > (value 499)))))) (block { (blockItem (statement (functionCall set-body-from ( (argumentList (value "http://errors.example.com/500?rid={id.REQUEST}")) )) ;)) })) (elifClause elif (condition (expression (term (factor ( (expression (term (factor (comparison (comparable inbound.status) == (value 418))))) ))))) (block { (blockItem (statement outbound.resp.Content-Type = (value "text/plain") ;)) (blockItem (statement outbound.resp.Server = (value "ATS-HRW4U") ;)) (blockItem (statement inbound.resp.body = (value "I am a teapot, rewritten") ;)) })))) })) (programItem (section SEND_RESPONSE { (sectionBody (statement outbound.resp.Cache-Control = (value "public, max-age=60") ;)) (sectionBody (statement outbound.resp.X-Now = (value "{now.YEAR}-{now.MONTH}-{now.DAY}T{now.HOUR}:{now.MINUTE}") ;)) (sectionBody (statement outbound.resp.X-Ports = (value "in={inbound.url.port};out={outbound.url.port}") ;)) (sectionBody (statement outbound.resp.X-IPs = (value "client={inbound.ip};server={outbound.ip}") ;)) (sectionBody (statement outbound.resp.X-ID = (value "{id.UNIQUE}") ;)) (sectionBody (statement outbound.resp.ATS-Geo-Country = (value "{geo.country}") ;)) (sectionBody (statement outbound.resp.ATS-Geo-ASN = (value "{geo.asn}") ;)) (sectionBody (statement outbound.resp.ATS-Geo-ASN-NAME = (value "{geo.ASN-NAME}") ;)) (sectionBody (statement outbound.cookie.debug = (value "on") ;)) (sectionBody (conditional (ifStatement if (condition (expression (term (factor (comparison (comparable inbound.req.X-Redirect) == (value "1")))))) (block { (blockItem (statement (functionCall set-redirect ( (argumentList (value 302) , (value "https://redirect.example/x?{inbound.url.query}")) )) ;)) })))) (sectionBody (statement (functionCall counter ( (argumentList (value "send_response_count")) )) ;)) })) (programItem (section TXN_CLOSE { (sectionBody (statement (functionCall counter ( (argumentList (value "txn_close_count")) )) ;)) (sectionBody (statement (functionCall no-op ( )) ;)) })) (programItem (section SEND_RESPONSE { (sectionBody (conditional (ifStatement if (condition (expression (term (factor inbound.conn.TLS)))) (block { (blockItem (statement inbound.resp.X-Client-Cert = (value "{inbound.conn.client-cert.PEM}") ;)) (blockItem (statement inbound.resp.X-Client-Cert-Subject = (value "{inbound.conn.client-cert.SUBJECT}") ;)) (blockItem (statement inbound.resp.X-Client-Cert-Issuer = (value "{inbound.conn.client-cert.ISSUER}") ;)) (blockItem (statement inbound.resp.X-Server-Cert-Subject = (value "{inbound.conn.server-cert.SUBJECT}") ;)) (blockItem (statement inbound.resp.X-Server-Cert-Serial = (value "{inbound.conn.server-cert.SERIAL}") ;)) (blockItem (statement inbound.resp.X-Client-SAN-DNS = (value "{inbound.conn.client-cert.SAN.DNS}") ;)) (blockItem (statement inbound.resp.X-Client-SAN-IP = (value "{inbound.conn.client-cert.SAN.IP}") ;)) (blockItem (statement inbound.resp.X-Server-SAN-Email = (value "{inbound.conn.server-cert.SAN.EMAIL}") ;)) (blockItem (statement inbound.resp.X-Server-SAN-URI = (value "{inbound.conn.server-cert.SAN.URI}") ;)) })))) })) (programItem (section SEND_REQUEST { (sectionBody (conditional (ifStatement if (condition (expression (term (factor (comparison (comparable inbound.conn.client-cert.SAN.DNS) ~ (regex /example\.com/)))))) (block { (blockItem (statement outbound.req.X-Matched-Domain = (value "true") ;)) })))) (sectionBody (conditional (ifStatement if (condition (expression (term (factor (comparison (comparable inbound.conn.client-cert.SUBJECT) ~ (regex /CN=testcert/)))))) (block { (blockItem (statement outbound.req.X-Test-Client = (value "verified") ;)) })))) })) (programItem (section READ_RESPONSE { (sectionBody (statement outbound.resp.X-Outbound-Client-Cert = (value "{outbound.conn.client-cert.PEM}") ;)) (sectionBody (statement outbound.resp.X-Outbound-Server-Subject = (value "{outbound.conn.server-cert.SUBJECT}") ;)) })) ) diff --git a/tools/hrw4u/tests/data/examples/all-nonsense.input.txt b/tools/hrw4u/tests/data/examples/all-nonsense.input.txt index cefa1655561..5582f48076e 100644 --- a/tools/hrw4u/tests/data/examples/all-nonsense.input.txt +++ b/tools/hrw4u/tests/data/examples/all-nonsense.input.txt @@ -60,12 +60,12 @@ PRE_REMAP { REMAP { if (from.url.host == "old.example.com" with NOCASE) { - inbound.url.host = "new.example.com"; + outbound.url.host = "new.example.com"; keep_query("id,utm_campaign"); } if (to.url.path ~ /(?i)^\/legacy\//) { - inbound.url.path = "/v2/"; + outbound.url.path = "/v2/"; remove_query("debug,trace"); } diff --git a/tools/hrw4u/tests/data/examples/rm-query.ast.txt b/tools/hrw4u/tests/data/examples/rm-query.ast.txt index d1b1077f3c7..34b08b5e865 100644 --- a/tools/hrw4u/tests/data/examples/rm-query.ast.txt +++ b/tools/hrw4u/tests/data/examples/rm-query.ast.txt @@ -1 +1 @@ -(program (programItem (section REMAP { (sectionBody (statement inbound.url.query = (value "") ;)) (sectionBody (statement (functionCall remove_query ( (argumentList (value "foo,bar")) )) ;)) (sectionBody (statement (functionCall keep_query ( (argumentList (value "foo,bar")) )) ;)) })) ) +(program (programItem (section REMAP { (sectionBody (statement outbound.url.query = (value "") ;)) (sectionBody (statement (functionCall remove_query ( (argumentList (value "foo,bar")) )) ;)) (sectionBody (statement (functionCall keep_query ( (argumentList (value "foo,bar")) )) ;)) })) ) diff --git a/tools/hrw4u/tests/data/examples/rm-query.input.txt b/tools/hrw4u/tests/data/examples/rm-query.input.txt index dbfd4008a14..592f2d6945c 100644 --- a/tools/hrw4u/tests/data/examples/rm-query.input.txt +++ b/tools/hrw4u/tests/data/examples/rm-query.input.txt @@ -1,5 +1,5 @@ REMAP { - inbound.url.query = ""; + outbound.url.query = ""; remove_query("foo,bar"); keep_query("foo,bar"); } diff --git a/tools/hrw4u/tests/data/ops/bad_path.fail.error.txt b/tools/hrw4u/tests/data/ops/bad_path.fail.error.txt index d5ab37f64fb..14f8693ba8f 100644 --- a/tools/hrw4u/tests/data/ops/bad_path.fail.error.txt +++ b/tools/hrw4u/tests/data/ops/bad_path.fail.error.txt @@ -1,3 +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"; + 2 | outbound.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 index f0ba09ac155..219c4b5927f 100644 --- a/tools/hrw4u/tests/data/ops/bad_path.fail.input.txt +++ b/tools/hrw4u/tests/data/ops/bad_path.fail.input.txt @@ -1,3 +1,3 @@ REMAP { - inbound.url.ath="foo"; + outbound.url.ath="foo"; } diff --git a/tools/hrw4u/tests/data/ops/set-destination.ast.txt b/tools/hrw4u/tests/data/ops/set-destination.ast.txt index f918ca0b5ad..c2146cfc300 100644 --- a/tools/hrw4u/tests/data/ops/set-destination.ast.txt +++ b/tools/hrw4u/tests/data/ops/set-destination.ast.txt @@ -1 +1 @@ -(program (programItem (section REMAP { (sectionBody (statement inbound.url.host = (value "foo") ;)) })) (programItem (section SEND_REQUEST { (sectionBody (statement outbound.url.path = (value "foo/bar.txt") ;)) })) ) +(program (programItem (section REMAP { (sectionBody (statement outbound.url.host = (value "foo") ;)) })) (programItem (section SEND_REQUEST { (sectionBody (statement outbound.url.path = (value "foo/bar.txt") ;)) })) ) diff --git a/tools/hrw4u/tests/data/ops/set-destination.input.txt b/tools/hrw4u/tests/data/ops/set-destination.input.txt index 137066ade87..8a158c07e79 100644 --- a/tools/hrw4u/tests/data/ops/set-destination.input.txt +++ b/tools/hrw4u/tests/data/ops/set-destination.input.txt @@ -1,5 +1,5 @@ REMAP { - inbound.url.host = "foo"; + outbound.url.host = "foo"; } SEND_REQUEST {