From aa26e1378803587c24924ad0055318959d597802 Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Sun, 27 Oct 2013 15:18:44 +0000 Subject: [PATCH] Add ${listextract {n}{list}...} --- doc/doc-docbook/spec.xfpt | 38 ++++++++++++ doc/doc-txt/ChangeLog | 2 + src/src/expand.c | 113 ++++++++++++++++++++++++++++++++++- test/scripts/0000-Basic/0002 | 7 +++ test/stdout/0002 | 7 +++ 5 files changed, 166 insertions(+), 1 deletion(-) 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' -- 2.30.2