summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhilip Hazel <ph10@hermes.cam.ac.uk>2007-02-06 10:00:24 +0000
committerPhilip Hazel <ph10@hermes.cam.ac.uk>2007-02-06 10:00:24 +0000
commit0ce9abe687c08503facdd9f4f94dfa27ada83da9 (patch)
treef5fa00b073bf46c891bdd26ecce2d8ae08449418
parent047bdd8ce4bf9cc9fd22fb22a2ebaf190d492343 (diff)
Add forany/forall (Marcus Holmgren's patch, basically).
-rw-r--r--doc/doc-txt/ChangeLog4
-rw-r--r--doc/doc-txt/NewStuff34
-rw-r--r--src/ACKNOWLEDGMENTS5
-rw-r--r--src/src/expand.c69
-rw-r--r--src/src/globals.c3
-rw-r--r--src/src/globals.h3
-rw-r--r--test/scripts/0000-Basic/000291
-rw-r--r--test/stdout/000235
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{<a list>}{<a condition>}{<yes-string>}{<no-string>}}
+ ${if forall{<a list>}{<a condition>}{<yes-string>}{<no-string>}}
+
+ 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