Skip to content

Commit 3a65cfe

Browse files
committed
add and document regex.escape()
which always escapes the input
1 parent e7c5ac0 commit 3a65cfe

File tree

3 files changed

+75
-9
lines changed

3 files changed

+75
-9
lines changed

doc/antora/modules/reference/pages/unlang/condition/regex.adoc

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,23 @@ in the values produced by the dynamic expansion, so that they are treated as
146146
literal characters in the regular expression.
147147

148148
To allow non-literals in dynamic elements of regular expressions, wrap any literal
149-
values or expansions in `%regex.safe(...)`.
149+
values or expansions in `%regex.escape(...)`.
150+
151+
.Using regex.escape to perform escaping
152+
====
153+
[source,unlang]
154+
----
155+
local domain
156+
157+
domain = "example.com"
158+
if (Stripped-User-Name =~ /domain$/) {
159+
...
160+
}
161+
----
162+
====
163+
164+
165+
If you want to _prevent_ escaping you can use `%regex.safe()` to indicate that the text is safe for use as-is, in regular expressions.
150166

151167
.Using regex.safe to prevent escaping
152168
====

src/lib/unlang/xlat_builtin.c

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ void xlat_debug_attr_list(request_t *request, fr_pair_list_t const *list)
229229
}
230230
}
231231

232-
/** Common function to move boxes form input list to output list
232+
/** Common function to move boxes from input list to output list
233233
*
234234
* This can be used to implement safe_for functions, as the xlat framework
235235
* can be used for concatenation, casting, and marking up output boxes as
@@ -2737,6 +2737,28 @@ static xlat_action_t xlat_func_range(TALLOC_CTX *ctx, fr_dcursor_t *out,
27372737
return XLAT_ACTION_DONE;
27382738
}
27392739

2740+
static int CC_HINT(nonnull(2,3)) regex_xlat_escape(UNUSED request_t *request, fr_value_box_t *vb, UNUSED void *uctx)
2741+
{
2742+
ssize_t slen;
2743+
fr_sbuff_t *out = NULL;
2744+
fr_value_box_entry_t entry;
2745+
2746+
FR_SBUFF_TALLOC_THREAD_LOCAL(&out, 256, 4096);
2747+
2748+
slen = fr_value_box_print(out, vb, &regex_escape_rules);
2749+
if (slen < 0) return -1;
2750+
2751+
entry = vb->entry;
2752+
2753+
fr_value_box_clear(vb);
2754+
fr_value_box_init(vb, FR_TYPE_STRING, NULL, false);
2755+
(void) fr_value_box_bstrndup(vb, vb, NULL, fr_sbuff_start(out), fr_sbuff_used(out), false);
2756+
2757+
vb->entry = entry;
2758+
2759+
return 0;
2760+
}
2761+
27402762
#if defined(HAVE_REGEX_PCRE) || defined(HAVE_REGEX_PCRE2)
27412763
static xlat_arg_parser_t const xlat_func_regex_args[] = {
27422764
{ .variadic = XLAT_ARG_VARIADIC_EMPTY_KEEP, .type = FR_TYPE_VOID },
@@ -4281,11 +4303,24 @@ do { \
42814303
XLAT_ARG_PARSER_TERMINATOR
42824304
};
42834305

4306+
static xlat_arg_parser_t const xlat_regex_escape_args[] = {
4307+
{ .type = FR_TYPE_STRING,
4308+
.func = regex_xlat_escape, .safe_for = FR_REGEX_SAFE_FOR, .always_escape = true,
4309+
.variadic = true, .concat = true },
4310+
XLAT_ARG_PARSER_TERMINATOR
4311+
};
4312+
42844313
if (unlikely((xlat = xlat_func_register(xlat_ctx, "regex.safe",
42854314
xlat_transparent, FR_TYPE_STRING)) == NULL)) return -1;
42864315
xlat_func_flags_set(xlat, XLAT_FUNC_FLAG_INTERNAL);
42874316
xlat_func_args_set(xlat, xlat_regex_safe_args);
42884317
xlat_func_safe_for_set(xlat, FR_REGEX_SAFE_FOR);
4318+
4319+
if (unlikely((xlat = xlat_func_register(xlat_ctx, "regex.escape",
4320+
xlat_transparent, FR_TYPE_STRING)) == NULL)) return -1;
4321+
xlat_func_flags_set(xlat, XLAT_FUNC_FLAG_INTERNAL);
4322+
xlat_func_args_set(xlat, xlat_regex_escape_args);
4323+
xlat_func_safe_for_set(xlat, FR_REGEX_SAFE_FOR);
42894324
}
42904325

42914326
XLAT_REGISTER_PURE("sha1", xlat_func_sha1, FR_TYPE_OCTETS, xlat_func_sha_arg);

src/tests/keywords/regex-escape

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,40 @@
11
#
22
# PRE: if
33
#
4-
string test_string1
5-
string test_string2
4+
string domain
5+
string escaped
6+
string from_user
67

78
#
89
# Strings which are expanded in a regex have regex special
910
# characters escaped. Because the input strings are unsafe.
1011
#
11-
test_string1 := %taint("example.com")
12-
test_string2 := "exampleXcom"
12+
domain := "example.com"
13+
escaped := %regex.escape(domain)
14+
from_user := "exampleXcom"
1315

14-
if ("exampleXcom" =~ /%{test_string1}/) {
16+
if (domain == escaped) {
1517
test_fail
1618
}
1719

18-
if ("exampleXcom" !~ /%regex.safe(%{test_string1})/) {
20+
#
21+
# We require an explicit '.' in the from_user srring.
22+
#
23+
if ("exampleXcom" =~ /%{escaped}/) {
1924
test_fail
2025
}
2126

22-
if (test_string2 =~ /%{test_string1}/) {
27+
#
28+
# interpret the '.' as a regex match, but marking the text as safe.
29+
#
30+
if ("exampleXcom" !~ /%regex.safe(domain)/) {
31+
test_fail
32+
}
33+
34+
#
35+
# The text from the user should match, too.
36+
#
37+
if (from_user =~ /%{escaped}/) {
2338
test_fail
2439
}
2540

0 commit comments

Comments
 (0)