From 042eb971b8c1af6d257f207d453b30afeed529b8 Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Sun, 10 Jun 2012 17:53:01 +0100 Subject: [PATCH] Add ${list:name} and ${nlist:string} expansion operators. --- doc/doc-docbook/spec.xfpt | 19 +++++++ doc/doc-txt/ChangeLog | 2 + doc/doc-txt/NewStuff | 4 +- src/src/expand.c | 105 +++++++++++++++++++++++++++++++++++ test/confs/0002 | 2 + test/scripts/0000-Basic/0002 | 15 +++++ test/stdout/0002 | 15 +++++ 7 files changed, 161 insertions(+), 1 deletion(-) diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt index ece47a084..7c0a400d2 100644 --- a/doc/doc-docbook/spec.xfpt +++ b/doc/doc-docbook/spec.xfpt @@ -9734,6 +9734,25 @@ extracted from it. If the string does not parse successfully, the result is empty. +.vitem &*${list:*&<&'name'&>&*}*&&~and&~&*${list_*&<&'type'&>&*name'&>&*}*& +.cindex "expansion" "named list" +.cindex "&%list%& expansion item" +The name is interpreted as a named list and the content of the list is returned, +expanding any referenced lists, re-quoting as needed for colon-separation. +If the optional type if given it must be one of "a", "d", "h" or "l" +and selects address-, domain-, host- or localpart- lists to search among respectively. +Otherwise all types are searched in an undefined order and the first +matching list is returned. + + +.vitem &*${nlist:*&<&'string'&>&*}*& +.cindex "expansion" "list item count" +.cindex "list" "item count" +.cindex "list" "count of items" +.cindex "&%nlist%& expansion item" +The string is interpreted as a list and the number of items is returned. + + .vitem &*${mask:*&<&'IP&~address'&>&*/*&<&'bit&~count'&>&*}*& .cindex "masked IP address" .cindex "IP address" "masking" diff --git a/doc/doc-txt/ChangeLog b/doc/doc-txt/ChangeLog index 66fb1ca32..af6080985 100644 --- a/doc/doc-txt/ChangeLog +++ b/doc/doc-txt/ChangeLog @@ -39,6 +39,8 @@ JH/02 Support "G" suffix to numbers in ${if comparisons. PP/08 Handle smtp transport tls_sni option forced-fail for OpenSSL. +JH/03 Add expansion operators ${list:name} and ${nlist:string} + Exim version 4.80 ----------------- diff --git a/doc/doc-txt/NewStuff b/doc/doc-txt/NewStuff index aae58c631..64c1c14b7 100644 --- a/doc/doc-txt/NewStuff +++ b/doc/doc-txt/NewStuff @@ -70,7 +70,7 @@ Version 4.81 system not your own. The Recieved-by: header on items delivered by cutthrough is generated - early in of reception rather than at the end; this will affect any timestamp + early in reception rather than at the end; this will affect any timestamp included. The log line showing delivery is recorded before that showing reception; it uses a new ">>" tag instead of "=>". @@ -84,6 +84,8 @@ Version 4.81 Not yet supported: IGNOREQUOTA, SIZE, PIPELINING, AUTH. + 8. New expansion operators ${list:name} to get the content of a named list + and ${nlist:string} to count the items in a list. Version 4.80 ------------ diff --git a/src/src/expand.c b/src/src/expand.c index 05361a3ef..2a9e6b4fc 100644 --- a/src/src/expand.c +++ b/src/src/expand.c @@ -182,10 +182,12 @@ static uschar *op_table_main[] = { US"l", US"lc", US"length", + US"list", US"mask", US"md5", US"nh", US"nhash", + US"nlist", US"quote", US"randint", US"rfc2047", @@ -215,10 +217,12 @@ enum { EOP_L, EOP_LC, EOP_LENGTH, + EOP_LIST, EOP_MASK, EOP_MD5, EOP_NH, EOP_NHASH, + EOP_NLIST, EOP_QUOTE, EOP_RANDINT, EOP_RFC2047, @@ -5470,6 +5474,107 @@ while (*s != 0) continue; } + /* expand a named list given the name */ + /* handles nested named lists but will confuse the separators in the result */ + + case EOP_LIST: + { + tree_node *t = NULL; + uschar * list; + int sep = 0; + uschar * item; + uschar * suffix = ""; + BOOL needsep = FALSE; + uschar buffer[256]; + + if (*sub == '+') sub++; + if (arg == NULL) /* no-argument version */ + { + if (!(t = tree_search(addresslist_anchor, sub)) && + !(t = tree_search(domainlist_anchor, sub)) && + !(t = tree_search(hostlist_anchor, sub))) + t = tree_search(localpartlist_anchor, sub); + } + else switch(*arg) /* specific list-type version */ + { + case 'a': t = tree_search(addresslist_anchor, sub); suffix = "_a"; break; + case 'd': t = tree_search(domainlist_anchor, sub); suffix = "_d"; break; + case 'h': t = tree_search(hostlist_anchor, sub); suffix = "_h"; break; + case 'l': t = tree_search(localpartlist_anchor, sub); suffix = "_l"; break; + default: + expand_string_message = string_sprintf("bad suffix on \"list\" operator"); + goto EXPAND_FAILED; + } + + if(!t) + { + expand_string_message = string_sprintf("\"%s\" is not a %snamed list", + sub, !arg?"" + : *arg=='a'?"address " + : *arg=='d'?"domain " + : *arg=='h'?"host " + : *arg=='l'?"localpart " + : 0); + goto EXPAND_FAILED; + } + + if (skipping) continue; + list = ((namedlist_block *)(t->data.ptr))->string; + + while ((item = string_nextinlist(&list, &sep, buffer, sizeof(buffer))) != NULL) + { + uschar * buf = US" : "; + if (needsep) + yield = string_cat(yield, &size, &ptr, buf, 3); + else + needsep = TRUE; + + if (*item == '+') /* list item is itself a named list */ + { + uschar * sub = string_sprintf("${list%s:%s}", suffix, item); + item = expand_string_internal(sub, FALSE, NULL, FALSE, TRUE); + } + else if (sep != ':') /* item from non-colon-sep list, re-quote for colon list-separator */ + { + char * cp; + char tok[3]; + tok[0] = sep; tok[1] = ':'; tok[2] = 0; + while ((cp= strpbrk((const char *)item, tok))) + { + yield = string_cat(yield, &size, &ptr, item, cp-(char *)item); + if (*cp++ == ':') /* colon in a non-colon-sep list item, needs doubling */ + { + yield = string_cat(yield, &size, &ptr, US"::", 2); + item = cp; + } + else /* sep in item; should already be doubled; emit once */ + { + yield = string_cat(yield, &size, &ptr, (uschar *)tok, 1); + if (*cp == sep) cp++; + item = cp; + } + } + } + yield = string_cat(yield, &size, &ptr, item, Ustrlen(item)); + } + continue; + } + + /* count the number of list elements */ + + case EOP_NLIST: + { + int cnt = 0; + int sep = 0; + uschar * cp; + uschar buffer[256]; + + while (string_nextinlist(&sub, &sep, buffer, sizeof(buffer)) != NULL) cnt++; + cp = string_sprintf("%d", cnt); + yield = string_cat(yield, &size, &ptr, cp, Ustrlen(cp)); + continue; + } + /* mask applies a mask to an IP address; for example the result of ${mask:131.111.10.206/28} is 131.111.10.192/28. */ diff --git a/test/confs/0002 b/test/confs/0002 index af680500c..6983fd87f 100644 --- a/test/confs/0002 +++ b/test/confs/0002 @@ -15,6 +15,8 @@ gecos_name = CALLER_NAME # ----- Main settings ----- domainlist dlist = *.aa.bb : ^\Nxxx(.*) +domainlist elist = +dlist : ;; +domainlist flist = <; a ; b;;c ; +elist ; 2001:630:212:8:204::b664 ; hostlist hlist = V4NET.11.12.13 : iplsearch;DIR/aux-fixed/0002.iplsearch headers_charset = iso-8859-8 diff --git a/test/scripts/0000-Basic/0002 b/test/scripts/0000-Basic/0002 index 3b485ee97..41e0d0064 100644 --- a/test/scripts/0000-Basic/0002 +++ b/test/scripts/0000-Basic/0002 @@ -60,6 +60,21 @@ reduce: ${reduce{a:b:c}{+}{$value$item}} reduce: ${reduce {<, 1,2,3}{0}{${eval:$value+$item}}} reduce: ${reduce {3:0:9:4:6}{0}{${if >{$item}{$value}{$item}{$value}}}} +list: ${list:dlist} +list: ${list:+dlist} +list: ${list:hlist} +list: ${list:elist} +list: ${list:flist} +list: ${list:nolist} +list: ${list_d:dlist} +list: ${list_d:hlist} +list: ${list_z:dlist} + +nlist: ${nlist:a:b:c} +nlist: ${nlist:} +nlist: ${nlist:<;a;b;c} +nlist: ${nlist:${list:dlist}} + # Tests with iscntrl() and illegal separators map: ${map{<\n a\n\nb\nc}{'$item'}} diff --git a/test/stdout/0002 b/test/stdout/0002 index 2acfb63ea..a7c876229 100644 --- a/test/stdout/0002 +++ b/test/stdout/0002 @@ -49,6 +49,21 @@ > reduce: 6 > reduce: 9 > +> list: *.aa.bb : ^\Nxxx(.*) +> list: *.aa.bb : ^\Nxxx(.*) +> list: V4NET.11.12.13 : iplsearch;TESTSUITE/aux-fixed/0002.iplsearch +> list: *.aa.bb : ^\Nxxx(.*) : ;; +> list: a : b;c : *.aa.bb : ^\Nxxx(.*) : ;; : 2001::630::212::8::204::::b664 +> Failed: "nolist" is not a named list +> list: *.aa.bb : ^\Nxxx(.*) +> Failed: "hlist" is not a domain named list +> Failed: bad suffix on "list" operator +> +> nlist: 3 +> nlist: 0 +> nlist: 3 +> nlist: 2 +> > # Tests with iscntrl() and illegal separators > > map: 'a' -- 2.25.1