From 0ce9abe687c08503facdd9f4f94dfa27ada83da9 Mon Sep 17 00:00:00 2001 From: Philip Hazel Date: Tue, 6 Feb 2007 10:00:24 +0000 Subject: [PATCH] Add forany/forall (Marcus Holmgren's patch, basically). --- doc/doc-txt/ChangeLog | 4 +- doc/doc-txt/NewStuff | 34 +++++++++++++- src/ACKNOWLEDGMENTS | 5 +- src/src/expand.c | 69 ++++++++++++++++++++++++++- src/src/globals.c | 3 +- src/src/globals.h | 3 +- test/scripts/0000-Basic/0002 | 91 ++++++++++++++++++++++++++++-------- test/stdout/0002 | 35 ++++++++++++++ 8 files changed, 218 insertions(+), 26 deletions(-) diff --git a/doc/doc-txt/ChangeLog b/doc/doc-txt/ChangeLog index 6179dec3e..f9485b632 100644 --- a/doc/doc-txt/ChangeLog +++ b/doc/doc-txt/ChangeLog @@ -1,4 +1,4 @@ -$Cambridge: exim/doc/doc-txt/ChangeLog,v 1.468 2007/02/05 12:35:46 ph10 Exp $ +$Cambridge: exim/doc/doc-txt/ChangeLog,v 1.469 2007/02/06 10:00:24 ph10 Exp $ Change log file for Exim from version 4.21 ------------------------------------------- @@ -73,6 +73,8 @@ PH/14 Added log_selector = +pid. PH/15 Flush SMTP output before delaying, unless control=no_delay_flush is set. +PH/16 Add ${if forany and ${if forall. + Exim version 4.66 ----------------- diff --git a/doc/doc-txt/NewStuff b/doc/doc-txt/NewStuff index ae6c48991..ff72f6fa6 100644 --- a/doc/doc-txt/NewStuff +++ b/doc/doc-txt/NewStuff @@ -1,4 +1,4 @@ -$Cambridge: exim/doc/doc-txt/NewStuff,v 1.135 2007/02/05 12:35:46 ph10 Exp $ +$Cambridge: exim/doc/doc-txt/NewStuff,v 1.136 2007/02/06 10:00:24 ph10 Exp $ New Features in Exim -------------------- @@ -236,6 +236,38 @@ Version 4.67 a delay in an ACL. This behaviour can be disabled by obeying control = no_delay_flush at some earlier point. +12. There are two new expansion conditions that iterate over a list. They are + called forany and forall, and they are used like this: + + ${if forany{}{}{}{}} + ${if forall{}{}{}{}} + + The first argument is expanded, and the result is treated as a list. By + default, the list separator is a colon, but it can be changed by the normal + method. The second argument is interpreted as a condition that is to be + applied to each item in the list in turn. During the interpretation of the + condition, the current list item is placed in a variable called $item. + + - For forany, interpretation stops if the condition is true for any item, + and the yes-string is then expanded. If the condition is false for all + items in the list, the no-string is expanded. + + - For forall, interpration stops if the condition is false for any item, + and the no-string is then expanded. If the condition is true for all + items in the list, the yes-string is expanded. + + Note that negation of forany means that the condition must be false for all + items for the overall condition to succeed, and negation of forall means + that the condition must be false for at least one item. + + In this example, the list separator is changed to a comma: + + ${if forany{<, $recipients}{match{$item}{^user3@}}{yes}{no}} + + Outside a forany/forall condition, the value of $item is an empty string. + Its value is saved and restored while forany/forall is being processed, to + enable these expansion items to be nested. + Version 4.66 ------------ diff --git a/src/ACKNOWLEDGMENTS b/src/ACKNOWLEDGMENTS index 1a39046c3..d9238db38 100644 --- a/src/ACKNOWLEDGMENTS +++ b/src/ACKNOWLEDGMENTS @@ -1,4 +1,4 @@ -$Cambridge: exim/src/ACKNOWLEDGMENTS,v 1.71 2007/01/31 16:52:12 ph10 Exp $ +$Cambridge: exim/src/ACKNOWLEDGMENTS,v 1.72 2007/02/06 10:00:24 ph10 Exp $ EXIM ACKNOWLEDGEMENTS @@ -20,7 +20,7 @@ relatively small patches. Philip Hazel Lists created: 20 November 2002 -Last updated: 31 January 2007 +Last updated: 06 February 2007 THE OLD LIST @@ -168,6 +168,7 @@ Magnus Holmgren Patch for filter_prepend_home Patch for "h" flag in Domain Keys Patch for $sending_ip_address/$sending_port Patch for ${rfc2047d: + ... and several more Lots of other maintenance support Kjetil Torgrim Homme Patch for require_files problem on NFS file systems Tom Hughes Suggested patch for $n bug in pipe command from filter diff --git a/src/src/expand.c b/src/src/expand.c index b2674dd42..1409437d4 100644 --- a/src/src/expand.c +++ b/src/src/expand.c @@ -1,4 +1,4 @@ -/* $Cambridge: exim/src/src/expand.c,v 1.79 2007/01/31 11:30:08 ph10 Exp $ */ +/* $Cambridge: exim/src/src/expand.c,v 1.80 2007/02/06 10:00:24 ph10 Exp $ */ /************************************************* * Exim - an Internet mail transport agent * @@ -240,6 +240,8 @@ static uschar *cond_table[] = { US"eqi", US"exists", US"first_delivery", + US"forall", + US"forany", US"ge", US"gei", US"gt", @@ -279,6 +281,8 @@ enum { ECOND_STR_EQI, ECOND_EXISTS, ECOND_FIRST_DELIVERY, + ECOND_FORALL, + ECOND_FORANY, ECOND_STR_GE, ECOND_STR_GEI, ECOND_STR_GT, @@ -420,6 +424,7 @@ static var_entry var_table[] = { { "inode", vtype_ino, &deliver_inode }, { "interface_address", vtype_stringptr, &interface_address }, { "interface_port", vtype_int, &interface_port }, + { "item", vtype_stringptr, &iterate_item }, #ifdef LOOKUP_LDAP { "ldap_dn", vtype_stringptr, &eldap_dn }, #endif @@ -2349,6 +2354,68 @@ switch(cond_type) return ++s; + /* forall/forany: iterates a condition with different values */ + + case ECOND_FORALL: + case ECOND_FORANY: + { + int sep = 0; + uschar *iterate_item_save = iterate_item; + + while (isspace(*s)) s++; + if (*s++ != '{') goto COND_FAILED_CURLY_START; + + sub[0] = expand_string_internal(s, TRUE, &s, (yield == NULL)); + if (sub[0] == NULL) return NULL; + if (*s++ != '}') goto COND_FAILED_CURLY_END; + + while (isspace(*s)) s++; + if (*s++ != '{') goto COND_FAILED_CURLY_START; + + sub[1] = s; + + /* Call eval_condition once, with result discarded (as if scanning a + "false" part). This allows us to find the end of the condition, because if + the list it empty, we won't actually evaluate the condition for real. */ + + s = eval_condition(sub[1], NULL); + if (s == NULL) + { + expand_string_message = string_sprintf("%s inside \"%s\" condition", + expand_string_message, name); + return NULL; + } + while (isspace(*s)) s++; + + if (*s++ != '}') + { + expand_string_message = string_sprintf("missing } at end of condition " + "inside \"%s\"", name); + return NULL; + } + + if (yield != NULL) *yield = !testfor; + while ((iterate_item = string_nextinlist(&sub[0], &sep, NULL, 0)) != NULL) + { + DEBUG(D_expand) debug_printf("%s: $item = \"%s\"\n", name, iterate_item); + if (eval_condition(sub[1], &tempcond) == NULL) + { + expand_string_message = string_sprintf("%s inside \"%s\" condition", + expand_string_message, name); + return NULL; + } + DEBUG(D_expand) debug_printf("%s: condition evaluated to %s\n", name, + tempcond? "true":"false"); + + if (yield != NULL) *yield = (tempcond == testfor); + if (tempcond == (cond_type == ECOND_FORANY)) break; + } + + iterate_item = iterate_item_save; + return s; + } + + /* Unknown condition */ default: diff --git a/src/src/globals.c b/src/src/globals.c index b3bbd7faf..4d790ee9e 100644 --- a/src/src/globals.c +++ b/src/src/globals.c @@ -1,4 +1,4 @@ -/* $Cambridge: exim/src/src/globals.c,v 1.68 2007/02/05 12:35:46 ph10 Exp $ */ +/* $Cambridge: exim/src/src/globals.c,v 1.69 2007/02/06 10:00:24 ph10 Exp $ */ /************************************************* * Exim - an Internet mail transport agent * @@ -630,6 +630,7 @@ uschar *ignore_fromline_hosts = NULL; uschar *interface_address = NULL; int interface_port = -1; BOOL is_inetd = FALSE; +uschar *iterate_item = NULL; int journal_fd = -1; diff --git a/src/src/globals.h b/src/src/globals.h index 570e4c87b..77662b376 100644 --- a/src/src/globals.h +++ b/src/src/globals.h @@ -1,4 +1,4 @@ -/* $Cambridge: exim/src/src/globals.h,v 1.48 2007/02/05 12:35:46 ph10 Exp $ */ +/* $Cambridge: exim/src/src/globals.h,v 1.49 2007/02/06 10:00:24 ph10 Exp $ */ /************************************************* * Exim - an Internet mail transport agent * @@ -385,6 +385,7 @@ extern int ignore_bounce_errors_after; /* Keep them for this time. */ extern BOOL ignore_fromline_local; /* Local SMTP ignore fromline */ extern uschar *ignore_fromline_hosts; /* Hosts permitted to send "From " */ extern BOOL is_inetd; /* True for inetd calls */ +extern uschar *iterate_item; /* Item from iterate list */ extern int journal_fd; /* Fd for journal file */ diff --git a/test/scripts/0000-Basic/0002 b/test/scripts/0000-Basic/0002 index f32b605b0..5e0126524 100644 --- a/test/scripts/0000-Basic/0002 +++ b/test/scripts/0000-Basic/0002 @@ -69,17 +69,17 @@ eval10: ${eval10:077} eval10: ${eval10:08} eval10: ${eval10:0x1234} eval: ${eval:2+42%5} -eval: ${eval:0xc&5} -eval: ${eval:0xc & 5 } -eval: ${eval:0x0c|5} -eval: ${eval:0xc^5} -eval: ${eval:0xc>>1} -eval: ${eval:0xc >> 2} -eval: ${eval:0xc >> 4 } -eval: ${eval:0xc<<1} -eval: ${eval:~255&0x1234} -eval: ${eval:~ 255&0x1234} -eval: ${eval: -(~255&0x1234)} +eval: ${eval:0xc&5} +eval: ${eval:0xc & 5 } +eval: ${eval:0x0c|5} +eval: ${eval:0xc^5} +eval: ${eval:0xc>>1} +eval: ${eval:0xc >> 2} +eval: ${eval:0xc >> 4 } +eval: ${eval:0xc<<1} +eval: ${eval:~255&0x1234} +eval: ${eval:~ 255&0x1234} +eval: ${eval: -(~255&0x1234)} expand: \$primary_hostname ${expand:\$primary_hostname} hash: ${hash_3:monty} ${hash_5:monty} ${hash_4_62:monty python} @@ -100,15 +100,15 @@ while doing a reasonable check. base62: ${if or {\ {eq {${base62:12345}}{0003D7}}\ {eq {${base62:12345}}{0009IX}}\ - }{OK}{NOT OK}} + }{OK}{NOT OK}} base62d: ${if or {\ {eq {${base62d:0003D7}}{12345}}\ {eq {${base62d:0009IX}}{12345}}\ - }{OK}{NOT OK}} + }{OK}{NOT OK}} base62d: ${if or {\ {eq {${base62d:3D7}}{12345}}\ {eq {${base62d:9IX}}{12345}}\ - }{OK}{NOT OK}} + }{OK}{NOT OK}} base62 error: ${base62:12345x} base62d error:${base62d:0003D7.} @@ -304,19 +304,19 @@ match_domain: ${if match_domain{xyz}{+dlist}{yes}{no}} ${if match{x@zz.aa.bb}{^(.*)} \ { \ - >$1< \ + >$1< \ ${if match_domain{${domain:$1}}{+dlist}{[$1]}} \ >$1< \ } \ - { CAN'T HAPPEN}} + { CAN'T HAPPEN}} ${if match{x@xxxabc}{^(.*)} \ { \ - >$1< \ + >$1< \ ${if match_domain{${domain:$1}}{^\Nxxx(.*)\N}{[$1]}} \ >$1< \ } \ - { CAN'T HAPPEN}} + { CAN'T HAPPEN}} match_address: ${if match_address{x@y.z}{p@q:*@y.z}{yes}{no}} match_address: ${if match_address{x@y.z}{p@q:x@*.z}{yes}{no}} @@ -569,7 +569,7 @@ ${prvs{userx@test.ex}{secret}{rhubarb}} ${prvs{userx@test.ex}{secret}{}} # Correct checks; can't put explicit addresses in the tests, because they -# will change over time. +# will change over time. ${prvscheck{${prvs{userx@test.ex}{secret}}}{secret}} result=$prvscheck_result @@ -613,6 +613,59 @@ ${if or {{eq {}{}}{yes}{no}} ${substr_1_:12345} ${substr__3:12345} +# Iterations: forany and forall + +${if forany{a:b:c}{eq{$item}{a}}{yes}{no}} +${if forany{a:b:c}{eq{$item}{b}}{yes}{no}} +${if forany{a:b:c}{eq{$item}{c}}{yes}{no}} +${if forany {a:b:c} {eq {$item} {z}} {yes} {no}} +${if !forany{a:b:c}{eq{$item}{z}}{yes}{no}} +${if !forany{a:b:c}{eq{$item}{a}}{yes}{no}} +${if forany{}{eq{$item}{a}}{yes}{no}} +${if !forany{}{eq{$item}{a}}{yes}{no}} +${if forany{<, $primary_hostname,foo,bar}{eq{$item}{$primary_hostname}}{yes}{no}} + +${if forany{}{yes}{no}} +${if forany{a:b:c}{gt{$item}{a}{yes}{no}} + +${if forall{a:b:c}{match{$item}{^[a-c]\$}}{yes}{no}} +${if forall{q:b:c}{match{$item}{^[a-c]\$}}{yes}{no}} +${if forall{a:b:z}{match{$item}{^[a-c]\$}}{yes}{no}} +${if forall{}{match{$item}{^[a-c]\$}}{yes}{no}} + +${if !forall{a:b:c}{match{$item}{^[a-c]\$}}{yes}{no}} +${if !forall{q:b:c}{match{$item}{^[a-c]\$}}{yes}{no}} +${if !forall{a:b:z}{match{$item}{^[a-c]\$}}{yes}{no}} +${if !forall{}{match{$item}{^[a-c]\$}}{yes}{no}} + +# Expect yes +${if forany{a:b:c}\ + {\ + eq\ + {$item: ${if forall{x:y:z}{match{$item}{^[x-z]\$}}{true}{false}}}\ + {$item: true}\ + }\ +{outer=yes}{outer=no}} item='$item' (unset) + +# Expect no +${if forany{a:b:c}\ + {\ + eq\ + {$item: ${if !forall{x:y:z}{match{$item}{^[x-z]\$}}{true}{false}}}\ + {$item: true}\ + }\ +{outer=yes}{outer=no}} + +# Error inside nest - check message is helpful +${if forany{a:b:c}\ + {\ + eq\ + {$item: ${if forall{x:y:z}{match{$item}{^[x-z]\$}{true}{false}}}\ + {$item: true}\ + }\ +{outer=yes}{outer=no}} + + # Miscellaneous (for bug fixes, etc) ${if ={1}{1} {true}{${if ={1}{1} {true}{${if ={1}{1}{true}fail}}}}} diff --git a/test/stdout/0002 b/test/stdout/0002 index 623e5769b..b1f29c447 100644 --- a/test/stdout/0002 +++ b/test/stdout/0002 @@ -590,6 +590,41 @@ xyz > Failed: non-digit after underscore in "substr_1_" > Failed: non-digit after underscore in "substr__3" > +> # Iterations: forany and forall +> +> yes +> yes +> yes +> no +> yes +> no +> no +> yes +> yes +> +> Failed: unknown condition "yes" inside "forany" condition +> Failed: missing } at end of condition inside "forany" +> +> yes +> no +> no +> no +> +> no +> yes +> yes +> yes +> +> # Expect yes +> outer=yes item='' (unset) +> +> # Expect no +> outer=no +> +> # Error inside nest - check message is helpful +> Failed: missing } at end of condition inside "forall" inside "forany" condition +> +> > # Miscellaneous (for bug fixes, etc) > > true -- 2.30.2