summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/doc-docbook/spec.xfpt38
-rw-r--r--doc/doc-txt/ChangeLog2
-rw-r--r--src/src/expand.c113
-rw-r--r--test/scripts/0000-Basic/00027
-rw-r--r--test/stdout/00027
5 files changed, 166 insertions, 1 deletions
diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt
index 17bdcc943..f29c38722 100644
--- a/doc/doc-docbook/spec.xfpt
+++ b/doc/doc-docbook/spec.xfpt
@@ -9181,6 +9181,44 @@ of <&'string2'&>, whichever is the shorter. Do not confuse &%length%& with
&%strlen%&, which gives the length of a string.
+.vitem "&*${listextract{*&<&'number'&>&*}&&&
+ {*&<&'string1'&>&*}{*&<&'string2'&>&*}{*&<&'string3'&>&*}}*&"
+.cindex "expansion" "extracting list elements by number"
+.cindex "&%listextract%&" "extract list elements by number"
+.cindex "list" "extracting elements by number"
+The <&'number'&> argument must consist entirely of decimal digits,
+apart from an optional leading minus,
+and leading and trailing white space (which is ignored).
+
+After expansion, <&'string1'&> is interpreted as a list, colon-separated by
+default, but the separator can be changed in the usual way.
+
+The first field of the list is numbered one.
+If the number is negative, the fields are
+counted from the end of the list, with the rightmost one numbered -1.
+The numbered element of the list is extracted and placed in &$value$&,
+then <&'string2'&> is expanded as the result.
+
+If the modulus of the
+number is zero or greater than the number of fields in the string,
+the result is the expansion of <&'string3'&>.
+
+For example:
+.code
+${listextract{2}{x:42:99}}
+.endd
+yields &"42"&, and
+.code
+${listextract{-3}{<, x,42,99,& Mailer,,/bin/bash}{result: $value}}
+.endd
+yields &"result: 99"&.
+
+If {<&'string3'&>} is omitted, an empty string is used for string3.
+If {<&'string2'&>} is also omitted, the value that was
+extracted is used.
+You can use &`fail`& instead of {<&'string3'&>} as in a string extract.
+
+
.vitem "&*${lookup{*&<&'key'&>&*}&~*&<&'search&~type'&>&*&~&&&
{*&<&'file'&>&*}&~{*&<&'string1'&>&*}&~{*&<&'string2'&>&*}}*&"
This is the first of one of two different types of lookup item, which are both
diff --git a/doc/doc-txt/ChangeLog b/doc/doc-txt/ChangeLog
index 12369e434..e2b33cf91 100644
--- a/doc/doc-txt/ChangeLog
+++ b/doc/doc-txt/ChangeLog
@@ -27,6 +27,8 @@ TL/02 Experimental Proxy Protocol support: allows a proxied SMTP connection
to extract and use the src ip:port in logging and expansions as if it
were a direct connection from the outside internet.
+JH/02 Add ${listextract {number}{list}{success}{fail}}.
+
Exim version 4.82
-----------------
diff --git a/src/src/expand.c b/src/src/expand.c
index bf1f82c6f..325b05178 100644
--- a/src/src/expand.c
+++ b/src/src/expand.c
@@ -110,6 +110,7 @@ static uschar *item_table[] = {
US"hmac",
US"if",
US"length",
+ US"listextract",
US"lookup",
US"map",
US"nhash",
@@ -133,6 +134,7 @@ enum {
EITEM_HMAC,
EITEM_IF,
EITEM_LENGTH,
+ EITEM_LISTEXTRACT,
EITEM_LOOKUP,
EITEM_MAP,
EITEM_NHASH,
@@ -1131,6 +1133,22 @@ return fieldtext;
}
+static uschar *
+expand_getlistele (int field, uschar *list)
+{
+uschar * tlist= list;
+int sep= 0;
+uschar dummy;
+
+if(field<0)
+{
+ for(field++; string_nextinlist(&tlist, &sep, &dummy, 1); ) field++;
+ sep= 0;
+}
+if(field==0) return NULL;
+while(--field>0 && (string_nextinlist(&list, &sep, &dummy, 1))) ;
+return string_nextinlist(&list, &sep, NULL, 0);
+}
/*************************************************
* Extract a substring from a string *
@@ -5149,7 +5167,7 @@ while (*s != 0)
for (i = 0; i < j; i++)
{
while (isspace(*s)) s++;
- if (*s == '{')
+ if (*s == '{') /*}*/
{
sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok);
if (sub[i] == NULL) goto EXPAND_FAILED; /*{*/
@@ -5230,6 +5248,99 @@ while (*s != 0)
continue;
}
+ /* return the Nth item from a list */
+
+ case EITEM_LISTEXTRACT:
+ {
+ int i;
+ int field_number = 1;
+ uschar *save_lookup_value = lookup_value;
+ uschar *sub[2];
+ int save_expand_nmax =
+ save_expand_strings(save_expand_nstring, save_expand_nlength);
+
+ /* Read the field & list arguments */
+
+ for (i = 0; i < 2; i++)
+ {
+ while (isspace(*s)) s++;
+ if (*s != '{') /*}*/
+ goto EXPAND_FAILED_CURLY;
+
+ sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok);
+ if (!sub[i]) goto EXPAND_FAILED; /*{*/
+ if (*s++ != '}') goto EXPAND_FAILED_CURLY;
+
+ /* After removal of leading and trailing white space, the first
+ argument must be numeric and nonempty. */
+
+ if (i == 0)
+ {
+ int len;
+ int x = 0;
+ uschar *p = sub[0];
+
+ while (isspace(*p)) p++;
+ sub[0] = p;
+
+ len = Ustrlen(p);
+ while (len > 0 && isspace(p[len-1])) len--;
+ p[len] = 0;
+
+ if (!*p && !skipping)
+ {
+ expand_string_message = US"first argument of \"listextract\" must "
+ "not be empty";
+ goto EXPAND_FAILED;
+ }
+
+ if (*p == '-')
+ {
+ field_number = -1;
+ p++;
+ }
+ while (*p && isdigit(*p)) x = x * 10 + *p++ - '0';
+ if (*p)
+ {
+ expand_string_message = US"first argument of \"listextract\" must "
+ "be numeric";
+ goto EXPAND_FAILED;
+ }
+ field_number *= x;
+ }
+ }
+
+ /* Extract the numbered element into $value. If
+ skipping, just pretend the extraction failed. */
+
+ lookup_value = skipping? NULL : expand_getlistele(field_number, sub[1]);
+
+ /* If no string follows, $value gets substituted; otherwise there can
+ be yes/no strings, as for lookup or if. */
+
+ switch(process_yesno(
+ skipping, /* were previously skipping */
+ lookup_value != NULL, /* success/failure indicator */
+ save_lookup_value, /* value to reset for string2 */
+ &s, /* input pointer */
+ &yield, /* output pointer */
+ &size, /* output size */
+ &ptr, /* output current point */
+ US"extract", /* condition type */
+ &resetok))
+ {
+ case 1: goto EXPAND_FAILED; /* when all is well, the */
+ case 2: goto EXPAND_FAILED_CURLY; /* returned value is 0 */
+ }
+
+ /* All done - restore numerical variables. */
+
+ restore_expand_strings(save_expand_nmax, save_expand_nstring,
+ save_expand_nlength);
+
+ continue;
+ }
+
/* Handle list operations */
case EITEM_FILTER:
diff --git a/test/scripts/0000-Basic/0002 b/test/scripts/0000-Basic/0002
index b924a0934..367d558b3 100644
--- a/test/scripts/0000-Basic/0002
+++ b/test/scripts/0000-Basic/0002
@@ -75,6 +75,13 @@ listcount: ${listcount:}
listcount: ${listcount:<;a;b;c}
listcount: ${listcount:${listnamed:dlist}}
+listextract: ${listextract{ 2}{a:b:c:d}}
+listextract: ${listextract{-2}{<,a,b,c,d}{X${value}X}}
+listextract: ${listextract{ 5}{a:b:c:d}}
+listextract: ${listextract{-5}{a:b:c:d}}
+listextract: ${listextract{ 5}{a:b:c:d}{}{fail}}
+listextract: ${listextract{ 5}{a:b:c:d}{}fail}
+
# 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 1cf6a5b84..e6270977b 100644
--- a/test/stdout/0002
+++ b/test/stdout/0002
@@ -64,6 +64,13 @@
> listcount: 3
> listcount: 2
>
+> listextract: b
+> listextract: XcX
+> listextract:
+> listextract:
+> listextract: fail
+> Failed: "extract" failed and "fail" requested
+>
> # Tests with iscntrl() and illegal separators
>
> map: 'a'