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 29b0e93062 hrw: fix lost [OR] modifiers, add more autests (#12904)
29b0e93062 is described below
commit 29b0e93062cf7a45ad6a55958c8551a9cc95a39b
Author: Leif Hedstrom <[email protected]>
AuthorDate: Sat Feb 21 00:54:14 2026 -0700
hrw: fix lost [OR] modifiers, add more autests (#12904)
---
plugins/header_rewrite/condition.h | 6 +
plugins/header_rewrite/header_rewrite.cc | 5 +-
.../header_rewrite_bundle.replay.yaml | 669 +++++++++++++++++++++
.../header_rewrite/rules/complex_logics.conf | 158 +++++
4 files changed, 836 insertions(+), 2 deletions(-)
diff --git a/plugins/header_rewrite/condition.h
b/plugins/header_rewrite/condition.h
index 1404a747b6..1400f3433a 100644
--- a/plugins/header_rewrite/condition.h
+++ b/plugins/header_rewrite/condition.h
@@ -85,6 +85,12 @@ public:
return _mods;
}
+ void
+ set_mods(CondModifiers mods)
+ {
+ _mods = mods;
+ }
+
// Setters
virtual void
set_qualifier(const std::string &q)
diff --git a/plugins/header_rewrite/header_rewrite.cc
b/plugins/header_rewrite/header_rewrite.cc
index 9378f6022a..09ab73a74c 100644
--- a/plugins/header_rewrite/header_rewrite.cc
+++ b/plugins/header_rewrite/header_rewrite.cc
@@ -329,8 +329,9 @@ RulesConfig::parse_config(const std::string &fname,
TSHttpHookID default_hook, c
if (group_stack.empty()) {
throw std::runtime_error("unmatched %{GROUP}");
} else {
- delete cond; // We don't care about the closing group
condition, it's a no-op
- ngrp = group;
+ ngrp = group;
+ ngrp->set_mods(cond->mods());
+ delete cond;
group = group_stack.top();
group_stack.pop();
group->add_condition(ngrp); // Add the previous group to the
current group's conditions
diff --git
a/tests/gold_tests/pluginTest/header_rewrite/header_rewrite_bundle.replay.yaml
b/tests/gold_tests/pluginTest/header_rewrite/header_rewrite_bundle.replay.yaml
index ea1c966185..8e6e9a995c 100644
---
a/tests/gold_tests/pluginTest/header_rewrite/header_rewrite_bundle.replay.yaml
+++
b/tests/gold_tests/pluginTest/header_rewrite/header_rewrite_bundle.replay.yaml
@@ -155,6 +155,13 @@ autest:
args:
- "rules/run_plugin.conf"
+ - from: "http://www.example.com/from_15/"
+ to: "http://backend.ex:{SERVER_HTTP_PORT}/to_15/"
+ plugins:
+ - name: "header_rewrite.so"
+ args:
+ - "rules/complex_logics.conf"
+
log_validation:
traffic_out:
excludes:
@@ -1114,3 +1121,665 @@ sessions:
proxy-response:
status: 200
+
+# ==========================================================================
+# Tests 31-52: Complex GROUP logic tests (rules/complex_logics.conf)
+# ==========================================================================
+
+# Test 31: GROUP [OR] - only A header present (should match via group)
+- transactions:
+ - client-request:
+ method: "GET"
+ version: "1.1"
+ url: /from_15/logic/group-or
+ headers:
+ fields:
+ - [ Host, www.example.com ]
+ - [ X-Group-A, "yes" ]
+ - [ uuid, 37 ]
+
+ server-response:
+ status: 200
+ reason: OK
+ headers:
+ fields:
+ - [ Connection, close ]
+
+ proxy-response:
+ status: 200
+ headers:
+ fields:
+ - [ X-Group-Or-Result, { value: "matched", as: equal } ]
+
+# Test 32: GROUP [OR] - only B header present (should match via OR)
+- transactions:
+ - client-request:
+ method: "GET"
+ version: "1.1"
+ url: /from_15/logic/group-or
+ headers:
+ fields:
+ - [ Host, www.example.com ]
+ - [ X-Group-B, "yes" ]
+ - [ uuid, 38 ]
+
+ server-response:
+ status: 200
+ reason: OK
+ headers:
+ fields:
+ - [ Connection, close ]
+
+ proxy-response:
+ status: 200
+ headers:
+ fields:
+ - [ X-Group-Or-Result, { value: "matched", as: equal } ]
+
+# Test 33: GROUP [OR] - neither header present (should not match)
+- transactions:
+ - client-request:
+ method: "GET"
+ version: "1.1"
+ url: /from_15/logic/group-or
+ headers:
+ fields:
+ - [ Host, www.example.com ]
+ - [ uuid, 39 ]
+
+ server-response:
+ status: 200
+ reason: OK
+ headers:
+ fields:
+ - [ Connection, close ]
+
+ proxy-response:
+ status: 200
+ headers:
+ fields:
+ - [ X-Group-Or-Result, { as: absent } ]
+
+# Test 34: GROUP [AND] - both headers present (should match)
+- transactions:
+ - client-request:
+ method: "GET"
+ version: "1.1"
+ url: /from_15/logic/group-and
+ headers:
+ fields:
+ - [ Host, www.example.com ]
+ - [ X-And-A, "yes" ]
+ - [ X-And-B, "yes" ]
+ - [ uuid, 40 ]
+
+ server-response:
+ status: 200
+ reason: OK
+ headers:
+ fields:
+ - [ Connection, close ]
+
+ proxy-response:
+ status: 200
+ headers:
+ fields:
+ - [ X-Group-And-Result, { value: "matched", as: equal } ]
+
+# Test 35: GROUP [AND] - only A present (should not match)
+- transactions:
+ - client-request:
+ method: "GET"
+ version: "1.1"
+ url: /from_15/logic/group-and
+ headers:
+ fields:
+ - [ Host, www.example.com ]
+ - [ X-And-A, "yes" ]
+ - [ uuid, 41 ]
+
+ server-response:
+ status: 200
+ reason: OK
+ headers:
+ fields:
+ - [ Connection, close ]
+
+ proxy-response:
+ status: 200
+ headers:
+ fields:
+ - [ X-Group-And-Result, { as: absent } ]
+
+# Test 36: GROUP [NOT] - neither header present -> group=(false) ->
NOT(false)=true
+- transactions:
+ - client-request:
+ method: "GET"
+ version: "1.1"
+ url: /from_15/logic/group-not
+ headers:
+ fields:
+ - [ Host, www.example.com ]
+ - [ uuid, 42 ]
+
+ server-response:
+ status: 200
+ reason: OK
+ headers:
+ fields:
+ - [ Connection, close ]
+
+ proxy-response:
+ status: 200
+ headers:
+ fields:
+ - [ X-Group-Not-Result, { value: "matched", as: equal } ]
+
+# Test 37: GROUP [NOT] - both headers present -> group=(true) ->
NOT(true)=false
+- transactions:
+ - client-request:
+ method: "GET"
+ version: "1.1"
+ url: /from_15/logic/group-not
+ headers:
+ fields:
+ - [ Host, www.example.com ]
+ - [ X-Not-A, "yes" ]
+ - [ X-Not-B, "yes" ]
+ - [ uuid, 43 ]
+
+ server-response:
+ status: 200
+ reason: OK
+ headers:
+ fields:
+ - [ Connection, close ]
+
+ proxy-response:
+ status: 200
+ headers:
+ fields:
+ - [ X-Group-Not-Result, { as: absent } ]
+
+# Test 38: Nested GROUP - outer + inner-A present (should match)
+- transactions:
+ - client-request:
+ method: "GET"
+ version: "1.1"
+ url: /from_15/logic/nested-group
+ headers:
+ fields:
+ - [ Host, www.example.com ]
+ - [ X-Outer-A, "yes" ]
+ - [ X-Inner-A, "yes" ]
+ - [ uuid, 44 ]
+
+ server-response:
+ status: 200
+ reason: OK
+ headers:
+ fields:
+ - [ Connection, close ]
+
+ proxy-response:
+ status: 200
+ headers:
+ fields:
+ - [ X-Nested-Group-Result, { value: "matched", as: equal } ]
+
+# Test 39: Nested GROUP - outer + inner-B present (should match via inner OR)
+- transactions:
+ - client-request:
+ method: "GET"
+ version: "1.1"
+ url: /from_15/logic/nested-group
+ headers:
+ fields:
+ - [ Host, www.example.com ]
+ - [ X-Outer-A, "yes" ]
+ - [ X-Inner-B, "yes" ]
+ - [ uuid, 45 ]
+
+ server-response:
+ status: 200
+ reason: OK
+ headers:
+ fields:
+ - [ Connection, close ]
+
+ proxy-response:
+ status: 200
+ headers:
+ fields:
+ - [ X-Nested-Group-Result, { value: "matched", as: equal } ]
+
+# Test 40: Nested GROUP - no outer header (should not match)
+- transactions:
+ - client-request:
+ method: "GET"
+ version: "1.1"
+ url: /from_15/logic/nested-group
+ headers:
+ fields:
+ - [ Host, www.example.com ]
+ - [ X-Inner-A, "yes" ]
+ - [ uuid, 46 ]
+
+ server-response:
+ status: 200
+ reason: OK
+ headers:
+ fields:
+ - [ Connection, close ]
+
+ proxy-response:
+ status: 200
+ headers:
+ fields:
+ - [ X-Nested-Group-Result, { as: absent } ]
+
+# Test 41: Two GROUPs with OR - left group matches
+- transactions:
+ - client-request:
+ method: "GET"
+ version: "1.1"
+ url: /from_15/logic/two-groups
+ headers:
+ fields:
+ - [ Host, www.example.com ]
+ - [ X-Left-A, "yes" ]
+ - [ X-Left-B, "yes" ]
+ - [ uuid, 47 ]
+
+ server-response:
+ status: 200
+ reason: OK
+ headers:
+ fields:
+ - [ Connection, close ]
+
+ proxy-response:
+ status: 200
+ headers:
+ fields:
+ - [ X-Two-Groups-Result, { value: "matched", as: equal } ]
+
+# Test 42: Two GROUPs with OR - right group matches
+- transactions:
+ - client-request:
+ method: "GET"
+ version: "1.1"
+ url: /from_15/logic/two-groups
+ headers:
+ fields:
+ - [ Host, www.example.com ]
+ - [ X-Right-A, "yes" ]
+ - [ X-Right-B, "yes" ]
+ - [ uuid, 48 ]
+
+ server-response:
+ status: 200
+ reason: OK
+ headers:
+ fields:
+ - [ Connection, close ]
+
+ proxy-response:
+ status: 200
+ headers:
+ fields:
+ - [ X-Two-Groups-Result, { value: "matched", as: equal } ]
+
+# Test 43: Two GROUPs with OR - neither group matches (partial left)
+- transactions:
+ - client-request:
+ method: "GET"
+ version: "1.1"
+ url: /from_15/logic/two-groups
+ headers:
+ fields:
+ - [ Host, www.example.com ]
+ - [ X-Left-A, "yes" ]
+ - [ X-Right-B, "yes" ]
+ - [ uuid, 49 ]
+
+ server-response:
+ status: 200
+ reason: OK
+ headers:
+ fields:
+ - [ Connection, close ]
+
+ proxy-response:
+ status: 200
+ headers:
+ fields:
+ - [ X-Two-Groups-Result, { as: absent } ]
+
+# Test 44: GROUP inside if - alpha branch with X-Sub-A (should match)
+- transactions:
+ - client-request:
+ method: "GET"
+ version: "1.1"
+ url: /from_15/logic/if-group
+ headers:
+ fields:
+ - [ Host, www.example.com ]
+ - [ X-Branch, "alpha" ]
+ - [ X-Sub-A, "yes" ]
+ - [ uuid, 50 ]
+
+ server-response:
+ status: 200
+ reason: OK
+ headers:
+ fields:
+ - [ Connection, close ]
+
+ proxy-response:
+ status: 200
+ headers:
+ fields:
+ - [ X-If-Group-Result, { value: "alpha", as: equal } ]
+
+# Test 45: GROUP inside if - alpha branch without sub headers (falls to else)
+- transactions:
+ - client-request:
+ method: "GET"
+ version: "1.1"
+ url: /from_15/logic/if-group
+ headers:
+ fields:
+ - [ Host, www.example.com ]
+ - [ X-Branch, "alpha" ]
+ - [ uuid, 51 ]
+
+ server-response:
+ status: 200
+ reason: OK
+ headers:
+ fields:
+ - [ Connection, close ]
+
+ proxy-response:
+ status: 200
+ headers:
+ fields:
+ - [ X-If-Group-Result, { value: "other", as: equal } ]
+
+# Test 46: GROUP inside if - beta branch
+- transactions:
+ - client-request:
+ method: "GET"
+ version: "1.1"
+ url: /from_15/logic/if-group
+ headers:
+ fields:
+ - [ Host, www.example.com ]
+ - [ X-Branch, "beta" ]
+ - [ uuid, 52 ]
+
+ server-response:
+ status: 200
+ reason: OK
+ headers:
+ fields:
+ - [ Connection, close ]
+
+ proxy-response:
+ status: 200
+ headers:
+ fields:
+ - [ X-If-Group-Result, { value: "beta", as: equal } ]
+
+# Test 47: Nested if with GROUP OR - outer yes, case-A present
+- transactions:
+ - client-request:
+ method: "GET"
+ version: "1.1"
+ url: /from_15/logic/nested-if-group
+ headers:
+ fields:
+ - [ Host, www.example.com ]
+ - [ X-Outer, "yes" ]
+ - [ X-Case-A, "yes" ]
+ - [ uuid, 53 ]
+
+ server-response:
+ status: 200
+ reason: OK
+ headers:
+ fields:
+ - [ Connection, close ]
+
+ proxy-response:
+ status: 200
+ headers:
+ fields:
+ - [ X-Nested-If-Result, { value: "inner-match", as: equal } ]
+
+# Test 48: Nested if with GROUP OR - outer yes, neither case header
+- transactions:
+ - client-request:
+ method: "GET"
+ version: "1.1"
+ url: /from_15/logic/nested-if-group
+ headers:
+ fields:
+ - [ Host, www.example.com ]
+ - [ X-Outer, "yes" ]
+ - [ uuid, 54 ]
+
+ server-response:
+ status: 200
+ reason: OK
+ headers:
+ fields:
+ - [ Connection, close ]
+
+ proxy-response:
+ status: 200
+ headers:
+ fields:
+ - [ X-Nested-If-Result, { value: "inner-miss", as: equal } ]
+
+# Test 49: Nested if with GROUP OR - outer miss
+- transactions:
+ - client-request:
+ method: "GET"
+ version: "1.1"
+ url: /from_15/logic/nested-if-group
+ headers:
+ fields:
+ - [ Host, www.example.com ]
+ - [ X-Outer, "no" ]
+ - [ uuid, 55 ]
+
+ server-response:
+ status: 200
+ reason: OK
+ headers:
+ fields:
+ - [ Connection, close ]
+
+ proxy-response:
+ status: 200
+ headers:
+ fields:
+ - [ X-Nested-If-Result, { value: "outer-miss", as: equal } ]
+
+# Test 50: Complex (A OR B) AND (C OR D) - P+R present (should match)
+- transactions:
+ - client-request:
+ method: "GET"
+ version: "1.1"
+ url: /from_15/logic/complex-and-or
+ headers:
+ fields:
+ - [ Host, www.example.com ]
+ - [ X-P, "yes" ]
+ - [ X-R, "yes" ]
+ - [ uuid, 56 ]
+
+ server-response:
+ status: 200
+ reason: OK
+ headers:
+ fields:
+ - [ Connection, close ]
+
+ proxy-response:
+ status: 200
+ headers:
+ fields:
+ - [ X-Complex-Result, { value: "matched", as: equal } ]
+
+# Test 51: Complex (A OR B) AND (C OR D) - Q+S present (should match)
+- transactions:
+ - client-request:
+ method: "GET"
+ version: "1.1"
+ url: /from_15/logic/complex-and-or
+ headers:
+ fields:
+ - [ Host, www.example.com ]
+ - [ X-Q, "yes" ]
+ - [ X-S, "yes" ]
+ - [ uuid, 57 ]
+
+ server-response:
+ status: 200
+ reason: OK
+ headers:
+ fields:
+ - [ Connection, close ]
+
+ proxy-response:
+ status: 200
+ headers:
+ fields:
+ - [ X-Complex-Result, { value: "matched", as: equal } ]
+
+# Test 52: Complex (A OR B) AND (C OR D) - only P present (first group
+# matches but second doesn't, should not match)
+- transactions:
+ - client-request:
+ method: "GET"
+ version: "1.1"
+ url: /from_15/logic/complex-and-or
+ headers:
+ fields:
+ - [ Host, www.example.com ]
+ - [ X-P, "yes" ]
+ - [ uuid, 58 ]
+
+ server-response:
+ status: 200
+ reason: OK
+ headers:
+ fields:
+ - [ Connection, close ]
+
+ proxy-response:
+ status: 200
+ headers:
+ fields:
+ - [ X-Complex-Result, { as: absent } ]
+
+# Test 53: IP:CLIENT in GROUP with [OR] - IP matches (client is 127.0.0.1)
+- transactions:
+ - client-request:
+ method: "GET"
+ version: "1.1"
+ url: /from_15/logic/ip-group-or
+ headers:
+ fields:
+ - [ Host, www.example.com ]
+ - [ uuid, 59 ]
+
+ server-response:
+ status: 200
+ reason: OK
+ headers:
+ fields:
+ - [ Connection, close ]
+
+ proxy-response:
+ status: 200
+ headers:
+ fields:
+ - [ X-Ip-Group-Or-Result, { value: "matched", as: equal } ]
+
+# Test 54: IP:CLIENT in GROUP with [OR] - header X-Outer matches too
+- transactions:
+ - client-request:
+ method: "GET"
+ version: "1.1"
+ url: /from_15/logic/ip-group-or
+ headers:
+ fields:
+ - [ Host, www.example.com ]
+ - [ X-Outer, "true" ]
+ - [ uuid, 60 ]
+
+ server-response:
+ status: 200
+ reason: OK
+ headers:
+ fields:
+ - [ Connection, close ]
+
+ proxy-response:
+ status: 200
+ headers:
+ fields:
+ - [ X-Ip-Group-Or-Result, { value: "matched", as: equal } ]
+
+# Test 55: GROUP as first condition after hook - X-First-A present, should
match
+- transactions:
+ - client-request:
+ method: "GET"
+ version: "1.1"
+ url: /from_15/logic/group-first
+ headers:
+ fields:
+ - [ Host, www.example.com ]
+ - [ X-First-A, "yes" ]
+ - [ uuid, 61 ]
+
+ server-response:
+ status: 200
+ reason: OK
+ headers:
+ fields:
+ - [ Connection, close ]
+
+ proxy-response:
+ status: 200
+ headers:
+ fields:
+ - [ X-Group-First-Result, { value: "matched", as: equal } ]
+
+# Test 56: GROUP as first condition after hook - no X-First-A, should not match
+- transactions:
+ - client-request:
+ method: "GET"
+ version: "1.1"
+ url: /from_15/logic/group-first
+ headers:
+ fields:
+ - [ Host, www.example.com ]
+ - [ uuid, 62 ]
+
+ server-response:
+ status: 200
+ reason: OK
+ headers:
+ fields:
+ - [ Connection, close ]
+
+ proxy-response:
+ status: 200
+ headers:
+ fields:
+ - [ X-Group-First-Result, { as: absent } ]
diff --git
a/tests/gold_tests/pluginTest/header_rewrite/rules/complex_logics.conf
b/tests/gold_tests/pluginTest/header_rewrite/rules/complex_logics.conf
new file mode 100644
index 0000000000..b4f4b92518
--- /dev/null
+++ b/tests/gold_tests/pluginTest/header_rewrite/rules/complex_logics.conf
@@ -0,0 +1,158 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# All rules use SEND_RESPONSE_HDR_HOOK so set-header operates on the
+# response that the client sees. CLIENT-HEADER and CLIENT-URL:PATH
+# conditions always refer to the original client request regardless of hook.
+
+# ---- Test: GROUP with [OR] on GROUP:END ----
+# hrw4u: if (inbound.req.X-Group-A) || inbound.req.X-Group-B
+cond %{SEND_RESPONSE_HDR_HOOK} [AND]
+cond %{CLIENT-URL:PATH} /\/logic\/group-or$/ [AND]
+cond %{GROUP}
+ cond %{CLIENT-HEADER:X-Group-A} ="" [NOT]
+cond %{GROUP:END} [OR]
+cond %{CLIENT-HEADER:X-Group-B} ="" [NOT]
+ set-header X-Group-Or-Result "matched"
+
+# ---- Test: GROUP with [AND] on GROUP:END (explicit) ----
+# hrw4u: if (inbound.req.X-And-A) && inbound.req.X-And-B
+cond %{SEND_RESPONSE_HDR_HOOK} [AND]
+cond %{CLIENT-URL:PATH} /\/logic\/group-and$/ [AND]
+cond %{GROUP}
+ cond %{CLIENT-HEADER:X-And-A} ="" [NOT]
+cond %{GROUP:END} [AND]
+cond %{CLIENT-HEADER:X-And-B} ="" [NOT]
+ set-header X-Group-And-Result "matched"
+
+# ---- Test: GROUP with [NOT] on GROUP:END ----
+# hrw4u: if !(inbound.req.X-Not-A && inbound.req.X-Not-B)
+cond %{SEND_RESPONSE_HDR_HOOK} [AND]
+cond %{CLIENT-URL:PATH} /\/logic\/group-not$/ [AND]
+cond %{GROUP}
+ cond %{CLIENT-HEADER:X-Not-A} ="" [NOT]
+ cond %{CLIENT-HEADER:X-Not-B} ="" [NOT]
+cond %{GROUP:END} [NOT]
+ set-header X-Group-Not-Result "matched"
+
+# ---- Test: Nested GROUPs with mixed OR/AND ----
+# hrw4u: if inbound.req.X-Outer-A && (inbound.req.X-Inner-A ||
inbound.req.X-Inner-B)
+cond %{SEND_RESPONSE_HDR_HOOK} [AND]
+cond %{CLIENT-URL:PATH} /\/logic\/nested-group$/ [AND]
+cond %{CLIENT-HEADER:X-Outer-A} ="" [NOT]
+cond %{GROUP}
+ cond %{CLIENT-HEADER:X-Inner-A} ="" [NOT,OR]
+ cond %{CLIENT-HEADER:X-Inner-B} ="" [NOT]
+cond %{GROUP:END}
+ set-header X-Nested-Group-Result "matched"
+
+# ---- Test: Two GROUPs with OR between them ----
+# hrw4u: if (inbound.req.X-Left-A && inbound.req.X-Left-B) ||
(inbound.req.X-Right-A && inbound.req.X-Right-B)
+cond %{SEND_RESPONSE_HDR_HOOK} [AND]
+cond %{CLIENT-URL:PATH} /\/logic\/two-groups$/ [AND]
+cond %{GROUP}
+ cond %{CLIENT-HEADER:X-Left-A} ="" [NOT]
+ cond %{CLIENT-HEADER:X-Left-B} ="" [NOT]
+cond %{GROUP:END} [OR]
+cond %{GROUP}
+ cond %{CLIENT-HEADER:X-Right-A} ="" [NOT]
+ cond %{CLIENT-HEADER:X-Right-B} ="" [NOT]
+cond %{GROUP:END}
+ set-header X-Two-Groups-Result "matched"
+
+# ---- Test: GROUP inside if/elif/else ----
+# hrw4u:
+# if inbound.req.X-Branch == "alpha" && (inbound.req.X-Sub-A ||
inbound.req.X-Sub-B)
+# -> "alpha"
+# elif inbound.req.X-Branch == "beta"
+# -> "beta"
+# else
+# -> "other"
+cond %{SEND_RESPONSE_HDR_HOOK} [AND]
+cond %{CLIENT-URL:PATH} /\/logic\/if-group$/ [AND]
+ if
+ cond %{CLIENT-HEADER:X-Branch} ="alpha"
+ cond %{GROUP}
+ cond %{CLIENT-HEADER:X-Sub-A} ="" [NOT,OR]
+ cond %{CLIENT-HEADER:X-Sub-B} ="" [NOT]
+ cond %{GROUP:END}
+ set-header X-If-Group-Result "alpha"
+ elif
+ cond %{CLIENT-HEADER:X-Branch} ="beta"
+ set-header X-If-Group-Result "beta"
+ else
+ set-header X-If-Group-Result "other"
+ endif
+
+# ---- Test: GROUP with OR inside nested if ----
+# hrw4u:
+# if inbound.req.X-Outer == "yes"
+# if (inbound.req.X-Case-A || inbound.req.X-Case-B)
+# -> "inner-match"
+# else
+# -> "inner-miss"
+# else
+# -> "outer-miss"
+cond %{SEND_RESPONSE_HDR_HOOK} [AND]
+cond %{CLIENT-URL:PATH} /\/logic\/nested-if-group$/ [AND]
+ if
+ cond %{CLIENT-HEADER:X-Outer} ="yes"
+ if
+ cond %{GROUP}
+ cond %{CLIENT-HEADER:X-Case-A} ="" [NOT,OR]
+ cond %{CLIENT-HEADER:X-Case-B} ="" [NOT]
+ cond %{GROUP:END}
+ set-header X-Nested-If-Result "inner-match"
+ else
+ set-header X-Nested-If-Result "inner-miss"
+ endif
+ else
+ set-header X-Nested-If-Result "outer-miss"
+ endif
+
+# ---- Test: Complex expression: (A OR B) AND (C OR D) ----
+# hrw4u: if (inbound.req.X-P || inbound.req.X-Q) && (inbound.req.X-R ||
inbound.req.X-S)
+cond %{SEND_RESPONSE_HDR_HOOK} [AND]
+cond %{CLIENT-URL:PATH} /\/logic\/complex-and-or$/ [AND]
+cond %{GROUP}
+ cond %{CLIENT-HEADER:X-P} ="" [NOT,OR]
+ cond %{CLIENT-HEADER:X-Q} ="" [NOT]
+cond %{GROUP:END} [AND]
+cond %{GROUP}
+ cond %{CLIENT-HEADER:X-R} ="" [NOT,OR]
+ cond %{CLIENT-HEADER:X-S} ="" [NOT]
+cond %{GROUP:END}
+ set-header X-Complex-Result "matched"
+
+# ---- Test: IP:CLIENT in GROUP with [OR]
+# hrw4u: if (inbound.ip in {127.0.0.0/8}) || inbound.req.X-Outer == "true"
+cond %{SEND_RESPONSE_HDR_HOOK} [AND]
+cond %{CLIENT-URL:PATH} /\/logic\/ip-group-or$/ [AND]
+cond %{GROUP}
+ cond %{IP:CLIENT} {127.0.0.0/8}
+cond %{GROUP:END} [OR]
+cond %{CLIENT-HEADER:X-Outer} ="true"
+ set-header X-Ip-Group-Or-Result "matched"
+
+# ---- Test: GROUP as first condition after hook ----
+# hrw4u: if (inbound.url.path ~ /\/logic\/group-first$/) &&
inbound.req.X-First-A
+cond %{SEND_RESPONSE_HDR_HOOK} [AND]
+cond %{GROUP}
+ cond %{CLIENT-URL:PATH} /\/logic\/group-first$/
+cond %{GROUP:END} [AND]
+cond %{CLIENT-HEADER:X-First-A} ="" [NOT]
+ set-header X-Group-First-Result "matched"