Add ${list:name} and ${nlist:string} expansion operators.
authorJeremy Harris <jgh146exb@wizmail.org>
Sun, 10 Jun 2012 16:53:01 +0000 (17:53 +0100)
committerJeremy Harris <jgh146exb@wizmail.org>
Sun, 10 Jun 2012 16:53:01 +0000 (17:53 +0100)
doc/doc-docbook/spec.xfpt
doc/doc-txt/ChangeLog
doc/doc-txt/NewStuff
src/src/expand.c
test/confs/0002
test/scripts/0000-Basic/0002
test/stdout/0002

index ece47a084e877dcce49b1afb8596854da0a932fa..7c0a400d29ec142cc17de0dbe0c491ca33cb9cf0 100644 (file)
@@ -9734,6 +9734,25 @@ extracted from it. If the string does not parse successfully, the result is
 empty.
 
 
 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"
 .vitem &*${mask:*&<&'IP&~address'&>&*/*&<&'bit&~count'&>&*}*&
 .cindex "masked IP address"
 .cindex "IP address" "masking"
index 66fb1ca322f4fe27110aa28a947b5d4236671b71..af608098546905e87521681027c32bca9c08d325 100644 (file)
@@ -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.
 
 
 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
 -----------------
 
 Exim version 4.80
 -----------------
index aae58c631db548f7b05499c75e97e394b9ef2d64..64c1c14b7478644f4378b8d833cc14afac2a5eaa 100644 (file)
@@ -70,7 +70,7 @@ Version 4.81
     system not your own.
 
     The Recieved-by: header on items delivered by cutthrough is generated
     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 "=>".
     
     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.
 
 
     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
 ------------
 
 Version 4.80
 ------------
index 05361a3ef68668052e31c7ee0b7ad2e4969bfa3e..2a9e6b4fc3e567e586e62aa9bb676cba91b86b8b 100644 (file)
@@ -182,10 +182,12 @@ static uschar *op_table_main[] = {
   US"l",
   US"lc",
   US"length",
   US"l",
   US"lc",
   US"length",
+  US"list",
   US"mask",
   US"md5",
   US"nh",
   US"nhash",
   US"mask",
   US"md5",
   US"nh",
   US"nhash",
+  US"nlist",
   US"quote",
   US"randint",
   US"rfc2047",
   US"quote",
   US"randint",
   US"rfc2047",
@@ -215,10 +217,12 @@ enum {
   EOP_L,
   EOP_LC,
   EOP_LENGTH,
   EOP_L,
   EOP_LC,
   EOP_LENGTH,
+  EOP_LIST,
   EOP_MASK,
   EOP_MD5,
   EOP_NH,
   EOP_NHASH,
   EOP_MASK,
   EOP_MD5,
   EOP_NH,
   EOP_NHASH,
+  EOP_NLIST,
   EOP_QUOTE,
   EOP_RANDINT,
   EOP_RFC2047,
   EOP_QUOTE,
   EOP_RANDINT,
   EOP_RFC2047,
@@ -5470,6 +5474,107 @@ while (*s != 0)
         continue;
         }
 
         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. */
 
       /* 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. */
 
index af680500c5b9f66c3ea257ed77e43ab215921180..6983fd87f18385aaad761e9948f19625798ec95c 100644 (file)
@@ -15,6 +15,8 @@ gecos_name = CALLER_NAME
 # ----- Main settings -----
 
 domainlist dlist = *.aa.bb : ^\Nxxx(.*)
 # ----- 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
 
 hostlist   hlist = V4NET.11.12.13 : iplsearch;DIR/aux-fixed/0002.iplsearch
 headers_charset = iso-8859-8
 
index 3b485ee97c8511c0d1e048b332002ed073434429..41e0d00641244440b19043c59423e434af70beb5 100644 (file)
@@ -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}}}}
 
 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'}}
 # Tests with iscntrl() and illegal separators
 
 map: ${map{<\n a\n\nb\nc}{'$item'}}
index 2acfb63ea34868a54bb097b34642dd83edc4a12b..a7c87622988b3b796755d4cb093f024d9ab7f718 100644 (file)
 > reduce: 6
 > reduce: 9
 > 
 > 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'
 > # Tests with iscntrl() and illegal separators
 > 
 > map: 'a'