like some other languages.
The unfortunate outcome is that this makes a lot of things much
more complex visually. But it's a bit more consistent with the
rest of the xlat functions. And, for the v4 way of "less magic
and fewer special cases".
For now, %{1} etc. is still functional
and enable %regex.match() in non-PCRE builds
if (DHCP-Client-Identifier && \
"%{string:DHCP-Client-Identifier}" =~ /^RAS([0-9])-site([A-Z])$/) {
update reply {
- DHCP-Boot-Filename := "rasboot-%{1}-%{2}.kpxe"
+ DHCP-Boot-Filename := "rasboot-%regex.match(1)-%regex.match(2).kpxe"
}
}
----
```
NAS-IP-Address
```
- control.NAS-IP-Address := "%{1}"
+ control.NAS-IP-Address := "%regex.match(1)"
```
Acct-Session-Id
```
- control.Acct-Session-Id := "%{2}"
+ control.Acct-Session-Id := "%regex.match(2)"
subrequest CoA-Request {
```
```
if (!MAC-Address) {
if (Ethernet-Frame =~ /0x.{12}(..)(..)(..)(..)(..)(..).*/) {
- request.MAC-Address = "%{1}:%{2}:%{3}:%{4}:%{5}:%{6}"
+ request.MAC-Address = "%regex.match(1):%regex.match(2):%regex.match(3):%regex.match(4):%regex.match(5):%regex.match(6)"
}
else {
request.MAC-Address = Cookie
When the `=~` or `!~` operators are used, then parentheses in the regular
expression will sub capture groups, which contain part of the subject string.
-The special expansion `%{0}` expands to the portion of the subject that
+The special expansion `%regex.match(0)` expands to the portion of the subject that
matched. The expansions +
-`%{1}`..`%{32}` expand to the contents of any subcapture groups.
+`%regex.match(1)`..`%regex.match(32)` expand to the contents of any subcapture groups.
When using libpcre[2], named capture groups may also be accessed using the
built-in expansion +
----
if (User-Name =~ /^(.*)@example\.com$/) {
reply += {
- Reply-Message = "Hello %{1}"
+ Reply-Message = "Hello %regex.match(1)"
}
}
----
== Miscellaneous Expansions
-=== %{0}+..+%{32}
+=== %regex.match(0)+..+%regex.match(32)
-`%{0}` expands to the portion of the subject that matched the last regular
-expression evaluated. `%{1}`..`%{32}` expand to the contents of any capture
+`%regex.match(0)` expands to the portion of the subject that matched the last regular
+expression evaluated. `%regex.match(1)`..`%regex.match(32)` expand to the contents of any capture
groups in the pattern.
Every time a regular expression is evaluated, whether it matches or not,
return 0;
}
-#if defined(HAVE_REGEX_PCRE) || defined(HAVE_REGEX_PCRE2)
static xlat_arg_parser_t const xlat_func_regex_args[] = {
{ .variadic = XLAT_ARG_VARIADIC_EMPTY_KEEP, .type = FR_TYPE_VOID },
XLAT_ARG_PARSER_TERMINATOR
}
default:
+#if defined(HAVE_REGEX_PCRE) || defined(HAVE_REGEX_PCRE2)
{
fr_value_box_t *vb;
return XLAT_ACTION_DONE;
}
+#else
+ RDEBUG("Named regex captures are not supported (they require libpcre2)");
+ return XLAT_ACTION_FAIL;
+#endif
}
}
-#endif
static xlat_arg_parser_t const xlat_func_sha_arg[] = {
{ .concat = true, .type = FR_TYPE_OCTETS },
XLAT_REGISTER_PURE("md5", xlat_func_md5, FR_TYPE_OCTETS, xlat_func_md5_arg);
XLAT_NEW("hash.md4");
-#if defined(HAVE_REGEX_PCRE) || defined(HAVE_REGEX_PCRE2)
if (unlikely((xlat = xlat_func_register(xlat_ctx, "regex.match", xlat_func_regex, FR_TYPE_STRING)) == NULL)) return -1;
xlat_func_args_set(xlat, xlat_func_regex_args);
xlat_func_flags_set(xlat, XLAT_FUNC_FLAG_INTERNAL);
xlat_func_args_set(xlat, xlat_func_regex_args);
xlat_func_flags_set(xlat, XLAT_FUNC_FLAG_INTERNAL);
XLAT_NEW("regex.match");
-#endif
{
static xlat_arg_parser_t const xlat_regex_safe_args[] = {
}
if (User-Name =~ /^(.*)@test\.example\.com$/) {
- Stripped-User-Name := "%{1}"
+ Stripped-User-Name := "%regex.match(1)"
control.Password.Cleartext := "bob"
}
foreach thing (control.User-Name) {
if (test_string =~ /([A-Z0-9\-]*)_%{thing}/) {
- test_string := %{1}
+ test_string := %regex.match(1)
success
break
}
foreach value (Vendor-Specific.Cisco.AVPair) {
if (value =~ /^%{cisco_prefix}=(.*)$/i) {
reply += {
- Called-Station-Id = %{1}
+ Called-Station-Id = %regex.match(1)
}
}
}
foreach value (Vendor-Specific.Cisco.AVPair) {
if (value =~ /^stupid=(.*)$/i) {
reply += {
- Called-Station-Id = %{1}
+ Called-Station-Id = %regex.match(1)
}
}
}
if (User-Name) {
foreach thing (control.Filter-Id[*]) {
- if (thing =~ /(.*)/) {
- control.Calling-Station-Id := %{1}
- }
+ control.Calling-Station-Id := thing
}
}
control -= Calling-Station-Id[*]
foreach thing (control.Filter-Id) {
- if (thing =~ /(.*)/) {
- control.Calling-Station-Id := %{1}
- }
+ control.Calling-Station-Id := thing
}
if (!(control.Calling-Station-Id == 'ssid=GHIJKL')) {
#
# Verify non-empty capture groups evaluate to true
#
-# Note that "%{1}" evaluates to an empty string!
+# Note that "%regex.match(1)" evaluates to an empty string!
#
-if !%{1} {
+if !%regex.match(1) {
test_fail
}
#
# Verify empty capture groups evaluate to false
#
-if %{2} {
+if %regex.match(2) {
test_fail
}
#
# Check for stale capture group values
#
-if %{1} {
+if %regex.match(1) {
test_fail
}
# Check assignment of regex null-match
#
if (control.Password.Cleartext =~ /hell(o)(.*)/) {
- control.Filter-Id := "%{2}"
+ control.Filter-Id := "%regex.match(2)"
}
if (!control.Filter-Id) {
# Matching on attribute ref with capture groups
if (User-Name =~ /^([0-9])_([0-9])?_([0-9]*)_([0-9]+)_([^_])_(6)_([7-8])%{test_string}/) {
# Test all the capture groups
- reply.User-Name := "%{7}_%{6}_%{5}_%{4}_%{3}_%{2}_%{1}_%{0}"
+ reply.User-Name := "%regex.match(7)_%regex.match(6)_%regex.match(5)_%regex.match(4)_%regex.match(3)_%regex.match(2)_%regex.match(1)_%regex.match(0)"
}
else {
test_fail
# Checking capture groups are cleared out correctly
if (User-Name =~ /^([0-9])_%{test_string}/) {
- if (!("%{0}%{1}%{2}%{3}%{4}%{5}%{6}%{7}" == '1_1')) {
+ if (!("%regex.match(0)%regex.match(1)%regex.match(2)%regex.match(3)%regex.match(4)%regex.match(5)%regex.match(6)%regex.match(7)" == '1_1')) {
test_fail
}
}
# Checking capture groups are cleared out correctly when there are no matches
if (User-Name =~ /^.%{test_string}/) {
- if (!("%{0}%{1}%{2}%{3}%{4}%{5}%{6}%{7}" == '1')) {
+ if (!("%regex.match(0)%regex.match(1)%regex.match(2)%regex.match(3)%regex.match(4)%regex.match(5)%regex.match(6)%regex.match(7)" == '1')) {
test_fail
}
}
# Checking full capture group range
if ('a_b_c_d_e_f_g_h_i_j_k_l_m_n_o_p_q_r_s_t_u_v_w_x_y_z_A_B_C_D_E_F' =~ /^(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)$/) {
- if (!("%{0}%{1}%{2}%{3}%{4}%{5}%{6}%{7}%{8}%{9}%{10}%{11}%{12}%{13}%{14}%{15}%{16}%{17}%{18}%{19}%{20}%{21}%{22}%{23}%{24}%{25}%{26}%{27}%{28}%{29}%{30}%{31}%{32}" == 'a_b_c_d_e_f_g_h_i_j_k_l_m_n_o_p_q_r_s_t_u_v_w_x_y_z_A_B_C_D_E_FabcdefghijklmnopqrstuvwxyzABCDEF')) {
+ if (!("%regex.match(0)%regex.match(1)%regex.match(2)%regex.match(3)%regex.match(4)%regex.match(5)%regex.match(6)%regex.match(7)%regex.match(8)%regex.match(9)%regex.match(10)%regex.match(11)%regex.match(12)%regex.match(13)%regex.match(14)%regex.match(15)%regex.match(16)%regex.match(17)%regex.match(18)%regex.match(19)%regex.match(20)%regex.match(21)%regex.match(22)%regex.match(23)%regex.match(24)%regex.match(25)%regex.match(26)%regex.match(27)%regex.match(28)%regex.match(29)%regex.match(30)%regex.match(31)%regex.match(32)" == 'a_b_c_d_e_f_g_h_i_j_k_l_m_n_o_p_q_r_s_t_u_v_w_x_y_z_A_B_C_D_E_FabcdefghijklmnopqrstuvwxyzABCDEF')) {
test_fail
}
}
# Checking full capture group overrun
if ('a_b_c_d_e_f_g_h_i_j_k_l_m_n_o_p_q_r_s_t_u_v_w_x_y_z_A_B_C_D_E_F_G' =~ /^(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)_(.)$/) {
- if (!("%{0}%{1}%{2}%{3}%{4}%{5}%{6}%{7}%{8}%{9}%{10}%{11}%{12}%{13}%{14}%{15}%{16}%{17}%{18}%{19}%{20}%{21}%{22}%{23}%{24}%{25}%{26}%{27}%{28}%{29}%{30}%{31}%{32}" == 'a_b_c_d_e_f_g_h_i_j_k_l_m_n_o_p_q_r_s_t_u_v_w_x_y_z_A_B_C_D_E_F_GabcdefghijklmnopqrstuvwxyzABCDEF')) {
+ if (!("%regex.match(0)%regex.match(1)%regex.match(2)%regex.match(3)%regex.match(4)%regex.match(5)%regex.match(6)%regex.match(7)%regex.match(8)%regex.match(9)%regex.match(10)%regex.match(11)%regex.match(12)%regex.match(13)%regex.match(14)%regex.match(15)%regex.match(16)%regex.match(17)%regex.match(18)%regex.match(19)%regex.match(20)%regex.match(21)%regex.match(22)%regex.match(23)%regex.match(24)%regex.match(25)%regex.match(26)%regex.match(27)%regex.match(28)%regex.match(29)%regex.match(30)%regex.match(31)%regex.match(32)" == 'a_b_c_d_e_f_g_h_i_j_k_l_m_n_o_p_q_r_s_t_u_v_w_x_y_z_A_B_C_D_E_F_GabcdefghijklmnopqrstuvwxyzABCDEF')) {
test_fail
}
}
# Matching on attribute ref with capture groups
if (User-Name =~ /^([0-9])_([0-9])?_([0-9]*)_([0-9]+)_([^_])_(6)_([7-8])/) {
# Test all the capture groups
- reply.User-Name := "%{7}_%{6}_%{5}_%{4}_%{3}_%{2}_%{1}_%{0}"
+ reply.User-Name := "%regex.match(7)_%regex.match(6)_%regex.match(5)_%regex.match(4)_%regex.match(3)_%regex.match(2)_%regex.match(1)_%regex.match(0)"
}
else {
test_fail
# Checking capture groups are cleared out correctly
if (User-Name =~ /^([0-9])_/) {
- if (!("%{0}%{1}%{2}%{3}%{4}%{5}%{6}%{7}" == '1_1')) {
+ if (!("%regex.match(0)%regex.match(1)%regex.match(2)%regex.match(3)%regex.match(4)%regex.match(5)%regex.match(6)%regex.match(7)" == '1_1')) {
test_fail
}
}
# Checking capture groups are cleared out correctly when there are no matches
if (User-Name =~ /^./) {
- if (!("%{0}%{1}%{2}%{3}%{4}%{5}%{6}%{7}" == '1')) {
+ if (!("%regex.match(0)%regex.match(1)%regex.match(2)%regex.match(3)%regex.match(4)%regex.match(5)%regex.match(6)%regex.match(7)" == '1')) {
test_fail
}
}
# uncompiled - ref - named capture groups
if (User-Name =~ /^(?<one>[0-9])_(?<two>[0-9])?_(?<three>[0-9]*)_(?<four>[0-9]+)_(?<five>[^_])_(?<six>6)_(?<seven>[7-8])%{dummy_string}/) {
- result_string := "%regex.match('seven')_%regex.match('six')_%regex.match('five')_%regex.match('four')_%regex.match('three')_%regex.match('two')_%regex.match('one')_%{0}"
+ result_string := "%regex.match('seven')_%regex.match('six')_%regex.match('five')_%regex.match('four')_%regex.match('three')_%regex.match('two')_%regex.match('one')_%regex.match(0)"
if (!(result_string == '7_6_5_4_3_2_1_1_2_3_4_5_6_7')) {
test_fail
}
# Checking capture groups are cleared out correctly
if (User-Name =~ /^(?<one>[0-9])_%{dummy_string}/) {
- result_string := "%{0}%regex.match('one')%regex.match('two')%regex.match('three')%regex.match('four')%regex.match('five')%regex.match('six')%regex.match('seven')"
+ result_string := "%regex.match(0)%regex.match('one')%regex.match('two')%regex.match('three')%regex.match('four')%regex.match('five')%regex.match('six')%regex.match('seven')"
if (!(result_string == '1_1')) {
test_fail
}
# Checking capture groups are cleared out correctly when there are no matches
if (User-Name =~ /^.%{dummy_string}/) {
- result_string := "%{0}%regex.match('one')%regex.match('two')%regex.match('three')%regex.match('four')%regex.match('five')%regex.match('six')%regex.match('seven')"
+ result_string := "%regex.match(0)%regex.match('one')%regex.match('two')%regex.match('three')%regex.match('four')%regex.match('five')%regex.match('six')%regex.match('seven')"
if (!(result_string == '1')) {
test_fail
}
# compiled - ref - named capture groups
if (User-Name =~ /^(?<one>[0-9])_(?<two>[0-9])?_(?<three>[0-9]*)_(?<four>[0-9]+)_(?<five>[^_])_(?<six>6)_(?<seven>[7-8])/) {
- result_string := "%regex.match('seven')_%regex.match('six')_%regex.match('five')_%regex.match('four')_%regex.match('three')_%regex.match('two')_%regex.match('one')_%{0}"
+ result_string := "%regex.match('seven')_%regex.match('six')_%regex.match('five')_%regex.match('four')_%regex.match('three')_%regex.match('two')_%regex.match('one')_%regex.match(0)"
if (!(result_string == '7_6_5_4_3_2_1_1_2_3_4_5_6_7')) {
test_fail
}
# compiled - xlat - named capture groups
if ('1_2_3_4_5_6_7' =~ /^(?<one>[0-9])_(?<two>[0-9])?_(?<three>[0-9]*)_(?<four>[0-9]+)_(?<five>[^_])_(?<six>6)_(?<seven>[7-8])/) {
- result_string := "%regex.match('seven')_%regex.match('six')_%regex.match('five')_%regex.match('four')_%regex.match('three')_%regex.match('two')_%regex.match('one')_%{0}"
+ result_string := "%regex.match('seven')_%regex.match('six')_%regex.match('five')_%regex.match('four')_%regex.match('three')_%regex.match('two')_%regex.match('one')_%regex.match(0)"
if (!(result_string == '7_6_5_4_3_2_1_1_2_3_4_5_6_7')) {
test_fail
}
# compiled - ref - named capture groups (numeric indexes)
if (User-Name =~ /^(?<one>[0-9])_(?<two>[0-9])?_(?<three>[0-9]*)_(?<four>[0-9]+)_(?<five>[^_])_(?<six>6)_(?<seven>[7-8])/) {
- result_string := "%{7}_%{6}_%{5}_%{4}_%{3}_%{2}_%{1}_%{0}"
+ result_string := "%regex.match(7)_%regex.match(6)_%regex.match(5)_%regex.match(4)_%regex.match(3)_%regex.match(2)_%regex.match(1)_%regex.match(0)"
if (!(result_string == '7_6_5_4_3_2_1_1_2_3_4_5_6_7')) {
test_fail
}
if (Vendor-Specific.Cisco.AVPair[1] =~ /bar=(.*)/) {
- if (!("%{1}" == 'baz')) {
+ if (!("%regex.match(1)" == 'baz')) {
test_fail
}
}
}
if (Vendor-Specific.Cisco.AVPair[*] =~ /bar=(.*)/) {
- if (!("%{1}" == 'baz')) {
+ if (!("%regex.match(1)" == 'baz')) {
test_fail
}
}
test_fail
}
-result_string := "%{1}"
+result_string := "%regex.match(1)"
if (!(%length(%{result_string}) == 8166)) {
test_fail
"%{control.User-Name || control.Password.Cleartext}"
if (control.Password.Cleartext =~ /(h)(e)(l)(l)(o)/) {
- "%{0}"
- "%{1}"
- "%{2}"
- "%{3}"
- "%{4}"
- "%{5}"
- "%{6}"
+ "%regex.match(0)"
+ "%regex.match(1)"
+ "%regex.match(2)"
+ "%regex.match(3)"
+ "%regex.match(4)"
+ "%regex.match(5)"
+ "%regex.match(6)"
}
success
# so we can check the output
#
if (&LDAP-Sync.Entry-DN =~ /(CN=.+:)[a-f0-9-]+(,CN=Deleted Objects,DC=example,DC=com)/) {
- request.LDAP-Sync.Entry-DN := "%{1}oldid%{2}"
+ request.LDAP-Sync.Entry-DN := "%regex.match(1)oldid%regex.match(2)"
}
linelog
}
#
if (User-Name =~ /^[0-9](.*)/) {
request += {
- User-Name = %{1}
+ User-Name = %regex.match(1)
EAP-Type = ::AKA
}
}
result_string := %3gpp_temporary_id.decrypt(%{control.User-Name}, %{test_string}, 'false')
if ("%{User-Name}" =~ /^0(.*)/) {
- if (!result_string || (result_string == '') || (%{result_string} != "%{1}")) {
+ if (!result_string || (result_string == '') || (%{result_string} != "%regex.match(1)")) {
test_fail
}
}
date_str = %date('now')
now = %date(%{date_str})
if (date_str =~ /([0-9]{4}-[0-9]{2}-[0-9]{2}T)[0-9]{2}:[0-9]{2}:[0-9]{2}Z/) {
- date_str := "%{1}" + '00:00:00Z'
+ date_str := "%regex.match(1)" + '00:00:00Z'
}
start = %date(%{date_str})
xlat_expr 5 + ()
match ERROR offset 6: Empty expressions are invalid
-xlat_expr %{3}
-match %{3}
+xlat_expr %regex.match(3)
+match %regex.match(3)
#
# These next two are arguably wrong.
match 25
#
-# Not the same as a regex %{3}, but we can't tell the difference here.
+# Not the same as a regex %{1}, but we can't tell the difference here.
#
xlat_purify %{1 + 2}
match 3