Args count reduction in expansions coding
authorJeremy Harris <jgh146exb@wizmail.org>
Sun, 19 Jun 2022 20:17:17 +0000 (21:17 +0100)
committerJeremy Harris <jgh146exb@wizmail.org>
Sun, 19 Jun 2022 20:17:17 +0000 (21:17 +0100)
src/src/expand.c

index 4d7dc721920d6d88a554dbab8e2e0eb7be6e42ac..5147c51e7827c5d6ac77886bf3b70bb088452b73 100644 (file)
 
 #include "exim.h"
 
+typedef unsigned esi_flags;
+#define ESI_NOFLAGS            0
+#define ESI_BRACE_ENDS         BIT(0)  /* expansion should stop at } */
+#define ESI_HONOR_DOLLAR       BIT(1)  /* $ is meaningfull */
+#define ESI_SKIPPING           BIT(2)  /* value will not be needed */
+
 /* Recursively called function */
 
-static uschar *expand_string_internal(const uschar *, BOOL, const uschar **, BOOL, BOOL, BOOL *, BOOL *);
+static uschar *expand_string_internal(const uschar *, esi_flags, const uschar **, BOOL *, BOOL *);
 static int_eximarith_t expanded_string_integer(const uschar *, BOOL);
 
 #ifdef STAND_ALONE
@@ -2094,7 +2100,8 @@ Arguments:
   n          maximum number of substrings
   m          minimum required
   sptr       points to current string pointer
-  skipping   the skipping flag
+  flags
+   skipping   the skipping flag
   check_end  if TRUE, check for final '}'
   name       name of item, for error message
   resetok    if not NULL, pointer to flag - write FALSE if unsafe to reset
@@ -2102,15 +2109,16 @@ Arguments:
   textonly_p if not NULL, pointer to bitmask of which subs were text-only
             (did not change when expended)
 
-Returns:     0 OK; string pointer updated
+Returns:     -1 OK; string pointer updated, but in "skipping" mode
+            0 OK; string pointer updated
              1 curly bracketing error (too few arguments)
              2 too many arguments (only if check_end is set); message set
              3 other error (expansion failure)
 */
 
 static int
-read_subs(uschar **sub, int n, int m, const uschar **sptr, BOOL skipping,
-  BOOL check_end, uschar *name, BOOL *resetok, unsigned * textonly_p)
+read_subs(uschar ** sub, int n, int m, const uschar ** sptr, esi_flags flags,
+  BOOL check_end, uschar * name, BOOL * resetok, unsigned * textonly_p)
 {
 const uschar * s = *sptr;
 unsigned textonly_l = 0;
@@ -2130,13 +2138,14 @@ for (int i = 0; i < n; i++)
     sub[i] = NULL;
     break;
     }
-  if (!(sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, resetok,
-                                               textonly_p ? &textonly : NULL)))
+  if (!(sub[i] = expand_string_internal(s+1,
+         ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | flags & ESI_SKIPPING, &s, resetok,
+         textonly_p ? &textonly : NULL)))
     return 3;
   if (*s++ != '}') return 1;
   if (textonly_p && textonly) textonly_l |= BIT(i);
   Uskip_whitespace(&s);
-  }
+  }                                            /*{*/
 if (check_end && *s++ != '}')
   {
   if (s[-1] == '{')
@@ -2151,7 +2160,7 @@ if (check_end && *s++ != '}')
 
 if (textonly_p) *textonly_p = textonly_l;
 *sptr = s;
-return 0;
+return flags & ESI_SKIPPING ? -1 : 0;
 }
 
 
@@ -2619,7 +2628,9 @@ switch(cond_type = identify_operator(&s, &opname))
 
    {
     BOOL textonly;
-    sub[0] = expand_string_internal(s+1, TRUE, &s, yield == NULL, TRUE, resetok, &textonly);
+    sub[0] = expand_string_internal(s+1,
+      ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | (yield ? ESI_NOFLAGS : ESI_SKIPPING),
+      &s, resetok, &textonly);
     if (!sub[0]) return NULL;
     if (textonly) sub_textonly |= BIT(0);
    }
@@ -2719,8 +2730,8 @@ switch(cond_type = identify_operator(&s, &opname))
     Uskip_whitespace(&s);
     if (*s++ != '{') goto COND_FAILED_CURLY_START;     /*}*/
 
-    switch(read_subs(sub, nelem(sub), 1,
-      &s, yield == NULL, TRUE, name, resetok, NULL))
+    switch(read_subs(sub, nelem(sub), 1, &s,
+       yield ? ESI_NOFLAGS : ESI_SKIPPING, TRUE, name, resetok, NULL))
       {
       case 1: expand_string_message = US"too few arguments or bracketing "
         "error for acl";
@@ -2771,8 +2782,8 @@ switch(cond_type = identify_operator(&s, &opname))
     uschar *sub[4];
     Uskip_whitespace(&s);
     if (*s++ != '{') goto COND_FAILED_CURLY_START;     /* }-for-text-editors */
-    switch(read_subs(sub, nelem(sub), 2, &s, yield == NULL, TRUE, name,
-                   resetok, NULL))
+    switch(read_subs(sub, nelem(sub), 2, &s,
+       yield ? ESI_NOFLAGS : ESI_SKIPPING, TRUE, name, resetok, NULL))
       {
       case 1: expand_string_message = US"too few arguments or bracketing "
        "error for saslauthd";
@@ -2845,9 +2856,11 @@ switch(cond_type = identify_operator(&s, &opname))
     created using match_address{}{} and friends, where the second param
     includes information from untrustworthy sources. */
     /*XXX is this moot given taint-tracking? */
-    BOOL honour_dollar = TRUE;
-    if ((i > 0) && !sub2_honour_dollar)
-      honour_dollar = FALSE;
+
+    esi_flags flags = ESI_BRACE_ENDS;
+
+    if (!(i > 0 && !sub2_honour_dollar)) flags |= ESI_HONOR_DOLLAR;
+    if (!yield) flags |= ESI_SKIPPING;
 
     if (Uskip_whitespace(&s) != '{')
       {
@@ -2856,8 +2869,7 @@ switch(cond_type = identify_operator(&s, &opname))
         "after \"%s\"", opname);
       return NULL;
       }
-    if (!(sub[i] = expand_string_internal(s+1, TRUE, &s, yield == NULL,
-        honour_dollar, resetok, &textonly)))
+    if (!(sub[i] = expand_string_internal(s+1, flags, &s, resetok, &textonly)))
       return NULL;
     if (textonly) sub_textonly |= BIT(i);
     DEBUG(D_expand) if (i == 1 && !sub2_honour_dollar && Ustrchr(sub[1], '$'))
@@ -3261,7 +3273,9 @@ switch(cond_type = identify_operator(&s, &opname))
 
     Uskip_whitespace(&s);
     if (*s++ != '{') goto COND_FAILED_CURLY_START;     /* }-for-text-editors */
-    if (!(sub[0] = expand_string_internal(s, TRUE, &s, yield == NULL, TRUE, resetok, NULL)))
+    if (!(sub[0] = expand_string_internal(s,
+      ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | (yield ? ESI_NOFLAGS : ESI_SKIPPING),
+      &s, resetok, NULL)))
       return NULL;
     /* {-for-text-editors */
     if (*s++ != '}') goto COND_FAILED_CURLY_END;
@@ -3349,7 +3363,8 @@ switch(cond_type = identify_operator(&s, &opname))
 
     if (Uskip_whitespace(&s) != '{') goto COND_FAILED_CURLY_START;     /* }-for-text-editors */
     ourname = cond_type == ECOND_BOOL_LAX ? US"bool_lax" : US"bool";
-    switch(read_subs(sub_arg, 1, 1, &s, yield == NULL, FALSE, ourname, resetok, NULL))
+    switch(read_subs(sub_arg, 1, 1, &s,
+           yield ? ESI_NOFLAGS : ESI_SKIPPING, FALSE, ourname, resetok, NULL))
       {
       case 1: expand_string_message = string_sprintf(
                   "too few arguments or bracketing error for %s",
@@ -3417,7 +3432,8 @@ switch(cond_type = identify_operator(&s, &opname))
     uschar cksum[4];
     BOOL boolvalue = FALSE;
 
-    switch(read_subs(sub, 2, 2, CUSS &s, yield == NULL, FALSE, name, resetok, NULL))
+    switch(read_subs(sub, 2, 2, CUSS &s,
+           yield ? ESI_NOFLAGS : ESI_SKIPPING, FALSE, name, resetok, NULL))
       {
       case 1: expand_string_message = US"too few arguments or bracketing "
        "error for inbound_srs";
@@ -3616,7 +3632,8 @@ expanded, to check their syntax, but "skipping" is set when the result is not
 needed - this avoids unnecessary nested lookups.
 
 Arguments:
-  skipping       TRUE if we were skipping when this item was reached
+  flags
+   skipping       TRUE if we were skipping when this item was reached
   yes            TRUE if the first string is to be used, else use the second
   save_lookup    a value to put back into lookup_value before the 2nd expansion
   sptr           points to the input string pointer
@@ -3632,7 +3649,7 @@ Returns:         0 OK; lookup_value has been reset to save_lookup
 */
 
 static int
-process_yesno(BOOL skipping, BOOL yes, uschar *save_lookup, const uschar **sptr,
+process_yesno(esi_flags flags, BOOL yes, uschar *save_lookup, const uschar **sptr,
   gstring ** yieldptr, uschar *type, BOOL *resetok)
 {
 int rc = 0;
@@ -3640,6 +3657,8 @@ const uschar *s = *sptr;    /* Local value */
 uschar *sub1, *sub2;
 const uschar * errwhere;
 
+flags &= ESI_SKIPPING;         /* Ignore all buf the skipping flag */
+
 /* If there are no following strings, we substitute the contents of $value for
 lookups and for extractions in the success case. For the ${if item, the string
 "true" is substituted. In the fail case, nothing is substituted for all three
@@ -3649,12 +3668,12 @@ if (skip_whitespace(&s) == '}')
   {
   if (type[0] == 'i')
     {
-    if (yes && !skipping)
+    if (yes && !(flags & ESI_SKIPPING))
       *yieldptr = string_catn(*yieldptr, US"true", 4);
     }
   else
     {
-    if (yes && lookup_value && !skipping)
+    if (yes && lookup_value && !(flags & ESI_SKIPPING))
       *yieldptr = string_cat(*yieldptr, lookup_value);
     lookup_value = save_lookup;
     }
@@ -3666,7 +3685,7 @@ if (skip_whitespace(&s) == '}')
 
 if (*s++ != '{')
   {
-  errwhere = US"'yes' part did not start with '{'";
+  errwhere = US"'yes' part did not start with '{'";            /*}}*/
   goto FAILED_CURLY;
   }
 
@@ -3674,9 +3693,12 @@ if (*s++ != '{')
 want this string. Set skipping in the call in the fail case (this will always
 be the case if we were already skipping). */
 
-sub1 = expand_string_internal(s, TRUE, &s, !yes, TRUE, resetok, NULL);
+sub1 = expand_string_internal(s,
+  ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | (yes ? ESI_NOFLAGS : ESI_SKIPPING),
+  &s, resetok, NULL);
 if (sub1 == NULL && (yes || !f.expand_string_forcedfail)) goto FAILED;
 f.expand_string_forcedfail = FALSE;
+                                                               /*{{*/
 if (*s++ != '}')
   {
   errwhere = US"'yes' part did not end with '}'";
@@ -3701,14 +3723,16 @@ time, forced failures are noticed only if we want the second string. We must
 set skipping in the nested call if we don't want this string, or if we were
 already skipping. */
 
-if (skip_whitespace(&s) == '{')
+if (skip_whitespace(&s) == '{')                                        /*}*/
   {
-  sub2 = expand_string_internal(s+1, TRUE, &s, yes || skipping, TRUE, resetok, NULL);
-  if (sub2 == NULL && (!yes || !f.expand_string_forcedfail)) goto FAILED;
-  f.expand_string_forcedfail = FALSE;
+  esi_flags s_flags = ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | flags;
+  if (yes) s_flags |= ESI_SKIPPING;
+  sub2 = expand_string_internal(s+1, s_flags, &s, resetok, NULL);
+  if (!sub2 && (!yes || !f.expand_string_forcedfail)) goto FAILED;
+  f.expand_string_forcedfail = FALSE;                          /*{*/
   if (*s++ != '}')
     {
-    errwhere = US"'no' part did not start with '{'";
+    errwhere = US"'no' part did not start with '{'";           /*}*/
     goto FAILED_CURLY;
     }
 
@@ -3717,7 +3741,7 @@ if (skip_whitespace(&s) == '{')
   if (!yes)
     *yieldptr = string_cat(*yieldptr, sub2);
   }
-
+                                                               /*{{*/
 /* If there is no second string, but the word "fail" is present when the use of
 the second string is wanted, set a flag indicating it was a forced failure
 rather than a syntactic error. Swallow the terminating } in case this is nested
@@ -3730,9 +3754,9 @@ else if (*s != '}')
   s = US read_name(name, sizeof(name), s, US"_");
   if (Ustrcmp(name, "fail") == 0)
     {
-    if (!yes && !skipping)
+    if (!yes && !(flags & ESI_SKIPPING))
       {
-      Uskip_whitespace(&s);
+      Uskip_whitespace(&s);                                    /*{{*/
       if (*s++ != '}')
         {
        errwhere = US"did not close with '}' after forcedfail";
@@ -3754,7 +3778,7 @@ else if (*s != '}')
 
 /* All we have to do now is to check on the final closing brace. */
 
-skip_whitespace(&s);
+skip_whitespace(&s);                                           /*{{*/
 if (*s++ != '}')
   {
   errwhere = US"did not close with '}'";
@@ -4433,13 +4457,14 @@ string expansion becoming too powerful.
 
 Arguments:
   string         the string to be expanded
-  ket_ends       true if expansion is to stop at }
+  flags
+   brace_ends     expansion is to stop at }
+   honour_dollar  TRUE if $ is to be expanded,
+                  FALSE if it's just another character
+   skipping       TRUE for recursive calls when the value isn't actually going
+                  to be used (to allow for optimisation)
   left           if not NULL, a pointer to the first character after the
-                 expansion is placed here (typically used with ket_ends)
-  skipping       TRUE for recursive calls when the value isn't actually going
-                 to be used (to allow for optimisation)
-  honour_dollar  TRUE if $ is to be expanded,
-                 FALSE if it's just another character
+                 expansion is placed here (typically used with brace_ends)
   resetok_p     if not NULL, pointer to flag - write FALSE if unsafe to reset
                 the store.
   textonly_p    if not NULL, pointer to flag - write bool for only-met-text
@@ -4451,8 +4476,8 @@ Returns:         NULL if expansion fails:
 */
 
 static uschar *
-expand_string_internal(const uschar *string, BOOL ket_ends, const uschar **left,
-  BOOL skipping, BOOL honour_dollar, BOOL *resetok_p, BOOL * textonly_p)
+expand_string_internal(const uschar * string, esi_flags flags, const uschar ** left,
+  BOOL *resetok_p, BOOL * textonly_p)
 {
 rmark reset_point = store_mark();
 gstring * yield = string_get(Ustrlen(string) + 64);
@@ -4483,11 +4508,11 @@ while (*s)
     DEBUG(D_noutf8)
       debug_printf_indent("%c%s: %s\n",
        first ? '/' : '|',
-       skipping ? "---scanning" : "considering", s);
+       flags & ESI_SKIPPING ? "---scanning" : "considering", s);
     else
       debug_printf_indent("%s%s: %s\n",
        first ? UTF8_DOWN_RIGHT : UTF8_VERT_RIGHT,
-       skipping
+       flags & ESI_SKIPPING
        ? UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ "scanning"
        : "considering",
        s);
@@ -4513,7 +4538,7 @@ while (*s)
       for (s = t; *s ; s++) if (*s == '\\' && s[1] == 'N') break;
 
       DEBUG(D_expand)
-       debug_expansion_interim(US"protected", t, (int)(s - t), skipping);
+       debug_expansion_interim(US"protected", t, (int)(s - t), !!(flags & ESI_SKIPPING));
       yield = string_catn(yield, t, s - t);
       if (*s) s += 2;
       }
@@ -4536,15 +4561,15 @@ while (*s)
   /* Anything other than $ is just copied verbatim, unless we are
   looking for a terminating } character. */
 
-  if (ket_ends && *s == '}') break;
+  if (flags & ESI_BRACE_ENDS && *s == '}') break;
 
-  if (*s != '$' || !honour_dollar)
+  if (*s != '$' || !(flags & ESI_HONOR_DOLLAR))
     {
     int i = 1;                                                         /*{*/
     for (const uschar * t = s+1;
        *t && *t != '$' && *t != '}' && *t != '\\'; t++) i++;
 
-    DEBUG(D_expand) debug_expansion_interim(US"text", s, i, skipping);
+    DEBUG(D_expand) debug_expansion_interim(US"text", s, i, !!(flags & ESI_SKIPPING));
 
     yield = string_catn(yield, s, i);
     s += i;
@@ -4612,7 +4637,7 @@ while (*s)
 
     /* Variable */
 
-    else if (!(value = find_variable(name, FALSE, skipping, &newsize)))
+    else if (!(value = find_variable(name, FALSE, !!(flags & ESI_SKIPPING), &newsize)))
       {
       expand_string_message =
        string_sprintf("unknown variable name \"%s\"", name);
@@ -4712,14 +4737,13 @@ while (*s)
       uschar * user_msg;
       int rc;
 
-      switch(read_subs(sub, nelem(sub), 1, &s, skipping, TRUE, name,
-                     &resetok, NULL))
+      switch(read_subs(sub, nelem(sub), 1, &s, flags, TRUE, name, &resetok, NULL))
         {
+       case -1: continue;              /* skipping */
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
         case 3: goto EXPAND_FAILED;
         }
-      if (skipping) continue;
 
       resetok = FALSE;
       switch(rc = eval_acl(sub, nelem(sub), &user_msg))
@@ -4748,13 +4772,13 @@ while (*s)
       {
       uschar * sub_arg[1];
 
-      switch(read_subs(sub_arg, nelem(sub_arg), 1, &s, skipping, TRUE, name,
-                     &resetok, NULL))
+      switch(read_subs(sub_arg, nelem(sub_arg), 1, &s, flags, TRUE, name, &resetok, NULL))
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
         case 3: goto EXPAND_FAILED;
         }
+      /*XXX no skipping-optimisation? */
 
       yield = string_append(yield, 3,
                        US"Authentication-Results: ", sub_arg[0], US"; none");
@@ -4792,14 +4816,14 @@ while (*s)
       uschar * save_lookup_value = lookup_value;
 
       Uskip_whitespace(&s);
-      if (!(next_s = eval_condition(s, &resetok, skipping ? NULL : &cond)))
+      if (!(next_s = eval_condition(s, &resetok, flags & ESI_SKIPPING ? NULL : &cond)))
        goto EXPAND_FAILED;  /* message already set */
 
       DEBUG(D_expand)
        {
-       debug_expansion_interim(US"condition", s, (int)(next_s - s), skipping);
+       debug_expansion_interim(US"condition", s, (int)(next_s - s), !!(flags & ESI_SKIPPING));
        debug_expansion_interim(US"result",
-         cond ? US"true" : US"false", cond ? 4 : 5, skipping);
+         cond ? US"true" : US"false", cond ? 4 : 5, !!(flags & ESI_SKIPPING));
        }
 
       s = next_s;
@@ -4808,12 +4832,12 @@ while (*s)
       function that is also used by ${lookup} and ${extract} and ${run}. */
 
       switch(process_yesno(
-               skipping,                     /* were previously skipping */
-               cond,                         /* success/failure indicator */
-               lookup_value,                 /* value to reset for string2 */
-               &s,                           /* input pointer */
-               &yield,                       /* output pointer */
-               US"if",                       /* condition type */
+               flags,                  /* were previously skipping */
+               cond,                   /* success/failure indicator */
+               lookup_value,                   /* value to reset for string2 */
+               &s,                     /* input pointer */
+               &yield,                 /* output pointer */
+               US"if",                 /* condition type */
               &resetok))
         {
         case 1: goto EXPAND_FAILED;          /* when all is well, the */
@@ -4835,13 +4859,13 @@ while (*s)
       uschar *sub_arg[3];
       uschar *encoded;
 
-      switch(read_subs(sub_arg, nelem(sub_arg), 1, &s, skipping, TRUE, name,
-                     &resetok, NULL))
+      switch(read_subs(sub_arg, nelem(sub_arg), 1, &s, flags, TRUE, name, &resetok, NULL))
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
         case 3: goto EXPAND_FAILED;
         }
+      /*XXX no skipping-optimisation? */
 
       if (!sub_arg[1])                 /* One argument */
        {
@@ -4857,7 +4881,7 @@ while (*s)
        goto EXPAND_FAILED;
        }
 
-      if (skipping) continue;
+      if (flags & ESI_SKIPPING) continue;
 
       if (!(encoded = imap_utf7_encode(sub_arg[0], headers_charset,
                          sub_arg[1][0], sub_arg[2], &expand_string_message)))
@@ -4896,7 +4920,8 @@ while (*s)
 
       if (Uskip_whitespace(&s) == '{')                                 /*}*/
         {
-        key = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok, NULL);
+        key = expand_string_internal(s+1,
+               ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | flags, &s, &resetok, NULL);
         if (!key) goto EXPAND_FAILED;                  /*{{*/
         if (*s++ != '}')
          {
@@ -4966,7 +4991,8 @@ while (*s)
        expand_string_message = US"missing '{' for lookup file-or-query arg";
        goto EXPAND_FAILED_CURLY;                                               /*}}*/
        }
-      if (!(filename = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok, NULL)))
+      if (!(filename = expand_string_internal(s+1,
+               ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | flags, &s, &resetok, NULL)))
        goto EXPAND_FAILED;
                                                                                /*{{*/
       if (*s++ != '}')
@@ -4997,7 +5023,7 @@ while (*s)
       since new variables will have been set. Note that at the end of this
       "lookup" section, the old numeric variables are restored. */
 
-      if (skipping)
+      if (flags & ESI_SKIPPING)
         lookup_value = NULL;
       else
         {
@@ -5023,12 +5049,12 @@ while (*s)
       function that is also used by ${if} and ${extract}. */
 
       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 */
-               US"lookup",                   /* condition type */
+               flags,                  /* were previously skipping */
+               lookup_value != NULL,   /* success/failure indicator */
+               save_lookup_value,      /* value to reset for string2 */
+               &s,                     /* input pointer */
+               &yield,                 /* output pointer */
+               US"lookup",             /* condition type */
               &resetok))
         {
         case 1: goto EXPAND_FAILED;          /* when all is well, the */
@@ -5041,7 +5067,7 @@ while (*s)
       restore_expand_strings(save_expand_nmax, save_expand_nstring,
         save_expand_nlength);
 
-      if (skipping) continue;
+      if (flags & ESI_SKIPPING) continue;
       break;
       }
 
@@ -5069,18 +5095,15 @@ while (*s)
         goto EXPAND_FAILED;
         }
 
-      switch(read_subs(sub_arg, EXIM_PERL_MAX_ARGS + 1, 1, &s, skipping, TRUE,
+      switch(read_subs(sub_arg, EXIM_PERL_MAX_ARGS + 1, 1, &s, flags, TRUE,
            name, &resetok, NULL))
         {
+       case -1: continue;      /* If skipping, we don't actually do anything */
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
         case 3: goto EXPAND_FAILED;
         }
 
-      /* If skipping, we don't actually do anything */
-
-      if (skipping) continue;
-
       /* Start the interpreter if necessary */
 
       if (!opt_perl_started)
@@ -5140,16 +5163,14 @@ while (*s)
       {
       uschar * sub_arg[3], * p, * domain;
 
-      switch(read_subs(sub_arg, 3, 2, &s, skipping, TRUE, name, &resetok, NULL))
+      switch(read_subs(sub_arg, 3, 2, &s, flags, TRUE, name, &resetok, NULL))
         {
+       case -1: continue;      /* If skipping, we don't actually do anything */
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
         case 3: goto EXPAND_FAILED;
         }
 
-      /* If skipping, we don't actually do anything */
-      if (skipping) continue;
-
       /* sub_arg[0] is the address */
       if (  !(domain = Ustrrchr(sub_arg[0],'@'))
         || domain == sub_arg[0] || Ustrlen(domain) == 1)
@@ -5198,22 +5219,12 @@ while (*s)
       gstring * g;
       const pcre2_code * re;
 
-      /* TF: Ugliness: We want to expand parameter 1 first, then set
-         up expansion variables that are used in the expansion of
-         parameter 2. So we clone the string for the first
-         expansion, where we only expand parameter 1.
-
-         PH: Actually, that isn't necessary. The read_subs() function is
-         designed to work this way for the ${if and ${lookup expansions. I've
-         tidied the code.
-      */                                                               /*}}*/
-
       /* Reset expansion variables */
       prvscheck_result = NULL;
       prvscheck_address = NULL;
       prvscheck_keynum = NULL;
 
-      switch(read_subs(sub_arg, 1, 1, &s, skipping, FALSE, name, &resetok, NULL))
+      switch(read_subs(sub_arg, 1, 1, &s, flags, FALSE, name, &resetok, NULL))
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
@@ -5249,7 +5260,7 @@ while (*s)
         prvscheck_keynum = string_copy(key_num);
 
         /* Now expand the second argument */
-        switch(read_subs(sub_arg, 1, 1, &s, skipping, FALSE, name, &resetok, NULL))
+        switch(read_subs(sub_arg, 1, 1, &s, flags, FALSE, name, &resetok, NULL))
           {
           case 1: goto EXPAND_FAILED_CURLY;
           case 2:
@@ -5302,7 +5313,7 @@ while (*s)
         /* Now expand the final argument. We leave this till now so that
         it can include $prvscheck_result. */
 
-        switch(read_subs(sub_arg, 1, 0, &s, skipping, TRUE, name, &resetok, NULL))
+        switch(read_subs(sub_arg, 1, 0, &s, flags, TRUE, name, &resetok, NULL))
           {
           case 1: goto EXPAND_FAILED_CURLY;
           case 2:
@@ -5323,14 +5334,14 @@ while (*s)
            We need to make sure all subs are expanded first, so as to skip over
            the entire item. */
 
-        switch(read_subs(sub_arg, 2, 1, &s, skipping, TRUE, name, &resetok, NULL))
+        switch(read_subs(sub_arg, 2, 1, &s, flags, TRUE, name, &resetok, NULL))
           {
           case 1: goto EXPAND_FAILED_CURLY;
           case 2:
           case 3: goto EXPAND_FAILED;
           }
 
-      if (skipping) continue;
+      if (flags & ESI_SKIPPING) continue;
       break;
       }
 
@@ -5347,7 +5358,7 @@ while (*s)
         goto EXPAND_FAILED;
         }
 
-      switch(read_subs(sub_arg, 2, 1, &s, skipping, TRUE, name, &resetok, NULL))
+      switch(read_subs(sub_arg, 2, 1, &s, flags, TRUE, name, &resetok, NULL))
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
@@ -5356,7 +5367,7 @@ while (*s)
 
       /* If skipping, we don't actually do anything */
 
-      if (skipping) continue;
+      if (flags & ESI_SKIPPING) continue;
 
       /* Open the file and read it */
 
@@ -5388,7 +5399,7 @@ while (*s)
       /* Read up to 4 arguments, but don't do the end of item check afterwards,
       because there may be a string for expansion on failure. */
 
-      switch(read_subs(sub_arg, 4, 2, &s, skipping, FALSE, name, &resetok, NULL))
+      switch(read_subs(sub_arg, 4, 2, &s, flags, FALSE, name, &resetok, NULL))
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:                             /* Won't occur: no end check */
@@ -5398,7 +5409,7 @@ while (*s)
       /* If skipping, we don't actually do anything. Otherwise, arrange to
       connect to either an IP or a Unix socket. */
 
-      if (!skipping)
+      if (!(flags & ESI_SKIPPING))
         {
        int stype = search_findtype(US"readsock", 8);
        gstring * g = NULL;
@@ -5476,7 +5487,8 @@ while (*s)
 
       if (*s == '{')                                                   /*}*/
         {
-        if (!expand_string_internal(s+1, TRUE, &s, TRUE, TRUE, &resetok, NULL))
+        if (!expand_string_internal(s+1,
+         ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | ESI_SKIPPING, &s, &resetok, NULL))
           goto EXPAND_FAILED;                                          /*{*/
         if (*s++ != '}')
          {                                                             /*{*/
@@ -5492,7 +5504,7 @@ while (*s)
        expand_string_message = US"missing '}' closing readsocket";
        goto EXPAND_FAILED_CURLY;
        }
-      if (skipping) continue;
+      if (flags & ESI_SKIPPING) continue;
       break;
 
       /* Come here on failure to create socket, connect socket, write to the
@@ -5502,7 +5514,8 @@ while (*s)
     SOCK_FAIL:
       if (*s != '{') goto EXPAND_FAILED;                               /*}*/
       DEBUG(D_any) debug_printf("%s\n", expand_string_message);
-      if (!(arg = expand_string_internal(s+1, TRUE, &s, FALSE, TRUE, &resetok, NULL)))
+      if (!(arg = expand_string_internal(s+1,
+                   ESI_BRACE_ENDS | ESI_HONOR_DOLLAR, &s, &resetok, NULL)))
         goto EXPAND_FAILED;
       yield = string_cat(yield, arg);                                  /*{*/
       if (*s++ != '}')
@@ -5555,12 +5568,13 @@ while (*s)
       if (late_expand)         /* this is the default case */
        {                                               /*{*/
        int n = Ustrcspn(s, "}");
-       arg = skipping ? NULL : string_copyn(s, n);
+       arg = flags & ESI_SKIPPING ? NULL : string_copyn(s, n);
        s += n;
        }
       else
        {
-       if (!(arg = expand_string_internal(s, TRUE, &s, skipping, TRUE, &resetok, NULL)))
+       if (!(arg = expand_string_internal(s,
+               ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | flags, &s, &resetok, NULL)))
          goto EXPAND_FAILED;
        Uskip_whitespace(&s);
        }
@@ -5571,7 +5585,7 @@ while (*s)
        goto EXPAND_FAILED_CURLY;
        }
 
-      if (skipping)   /* Just pretend it worked when we're skipping */
+      if (flags & ESI_SKIPPING)   /* Just pretend it worked when we're skipping */
        {
         runrc = 0;
        lookup_value = NULL;
@@ -5645,19 +5659,19 @@ while (*s)
       /* Process the yes/no strings; $value may be useful in both cases */
 
       switch(process_yesno(
-               skipping,                     /* were previously skipping */
-               runrc == 0,                   /* success/failure indicator */
-               lookup_value,                 /* value to reset for string2 */
-               &s,                           /* input pointer */
-               &yield,                       /* output pointer */
-               US"run",                      /* condition type */
+               flags,                  /* were previously skipping */
+               runrc == 0,             /* success/failure indicator */
+               lookup_value,           /* value to reset for string2 */
+               &s,                     /* input pointer */
+               &yield,                 /* output pointer */
+               US"run",                        /* condition type */
               &resetok))
         {
         case 1: goto EXPAND_FAILED;          /* when all is well, the */
         case 2: goto EXPAND_FAILED_CURLY;    /* returned value is 0 */
         }
 
-      if (skipping) continue;
+      if (flags & ESI_SKIPPING) continue;
       break;
       }
 
@@ -5669,8 +5683,9 @@ while (*s)
       int o2m;
       uschar * sub[3];
 
-      switch(read_subs(sub, 3, 3, &s, skipping, TRUE, name, &resetok, NULL))
+      switch(read_subs(sub, 3, 3, &s, flags, TRUE, name, &resetok, NULL))
         {
+       case -1: continue;      /* skipping */
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
         case 3: goto EXPAND_FAILED;
@@ -5689,7 +5704,6 @@ while (*s)
           }
         }
 
-      if (skipping) continue;
       break;
       }
 
@@ -5710,9 +5724,10 @@ while (*s)
       Ensure that sub[2] is set in the ${length } case. */
 
       sub[2] = NULL;
-      switch(read_subs(sub, (item_type == EITEM_LENGTH)? 2:3, 2, &s, skipping,
+      switch(read_subs(sub, item_type == EITEM_LENGTH ? 2:3, 2, &s, flags,
              TRUE, name, &resetok, NULL))
         {
+       case -1: continue;      /* skipping */
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
         case 3: goto EXPAND_FAILED;
@@ -5753,7 +5768,6 @@ while (*s)
       if (!ret)
        goto EXPAND_FAILED;
       yield = string_catn(yield, ret, len);
-      if (skipping) continue;
       break;
       }
 
@@ -5786,15 +5800,14 @@ while (*s)
       uschar innerkey[MAX_HASHBLOCKLEN];
       uschar outerkey[MAX_HASHBLOCKLEN];
 
-      switch (read_subs(sub, 3, 3, &s, skipping, TRUE, name, &resetok, NULL))
+      switch (read_subs(sub, 3, 3, &s, flags, TRUE, name, &resetok, NULL))
         {
+       case -1: continue;      /* skipping */
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
         case 3: goto EXPAND_FAILED;
         }
 
-      if (skipping) continue;
-
       if (Ustrcmp(sub[0], "md5") == 0)
        {
        type = HMAC_MD5;
@@ -5881,13 +5894,13 @@ while (*s)
         save_expand_strings(save_expand_nstring, save_expand_nlength);
       unsigned sub_textonly = 0;
 
-      switch(read_subs(sub, 3, 3, &s, skipping, TRUE, name, &resetok, &sub_textonly))
+      switch(read_subs(sub, 3, 3, &s, flags, TRUE, name, &resetok, &sub_textonly))
         {
+       case -1: continue;      /* skipping */
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
         case 3: goto EXPAND_FAILED;
         }
-      if (skipping) continue;
 
       /* Compile the regular expression */
 
@@ -6011,11 +6024,12 @@ while (*s)
       available (eg. $item) hence cannot decide on numeric vs. keyed.
       Read a maximum of 5 arguments (including the yes/no) */
 
-      if (skipping)
+      if (flags & ESI_SKIPPING)
        {
         for (int j = 5; j > 0 && *s == '{'; j--)                       /*'}'*/
          {
-          if (!expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok, NULL))
+          if (!expand_string_internal(s+1,
+               ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | flags, &s, &resetok, NULL))
            goto EXPAND_FAILED;                                 /*'{'*/
           if (*s++ != '}')
            {
@@ -6042,7 +6056,8 @@ while (*s)
         {
        if (Uskip_whitespace(&s) == '{')                                /*'}'*/
           {
-          if (!(sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok, NULL)))
+          if (!(sub[i] = expand_string_internal(s+1,
+               ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | flags, &s, &resetok, NULL)))
            goto EXPAND_FAILED;                                         /*'{'*/
           if (*s++ != '}')
            {
@@ -6101,7 +6116,7 @@ while (*s)
       /* Extract either the numbered or the keyed substring into $value. If
       skipping, just pretend the extraction failed. */
 
-      if (skipping)
+      if (flags & ESI_SKIPPING)
        lookup_value = NULL;
       else switch (fmt)
        {
@@ -6195,12 +6210,12 @@ while (*s)
       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 */
-               US"extract",                  /* condition type */
+               flags,                  /* were previously skipping */
+               lookup_value != NULL,   /* success/failure indicator */
+               save_lookup_value,      /* value to reset for string2 */
+               &s,                     /* input pointer */
+               &yield,                 /* output pointer */
+               US"extract",            /* condition type */
               &resetok))
         {
         case 1: goto EXPAND_FAILED;          /* when all is well, the */
@@ -6212,7 +6227,7 @@ while (*s)
       restore_expand_strings(save_expand_nmax, save_expand_nstring,
         save_expand_nlength);
 
-      if (skipping) continue;
+      if (flags & ESI_SKIPPING) continue;
       break;
       }
 
@@ -6236,7 +6251,8 @@ while (*s)
          goto EXPAND_FAILED_CURLY;
          }
 
-       sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok, NULL);
+       sub[i] = expand_string_internal(s+1,
+             ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | flags, &s, &resetok, NULL);
        if (!sub[i])     goto EXPAND_FAILED;                            /*{{*/
        if (*s++ != '}')
          {
@@ -6261,7 +6277,7 @@ while (*s)
          while (len > 0 && isspace(p[len-1])) len--;
          p[len] = 0;
 
-         if (!*p && !skipping)
+         if (!*p && !(flags & ESI_SKIPPING))
            {
            expand_string_message = US"first argument of \"listextract\" must "
              "not be empty";
@@ -6287,18 +6303,18 @@ while (*s)
       /* Extract the numbered element into $value. If
       skipping, just pretend the extraction failed. */
 
-      lookup_value = skipping ? NULL : expand_getlistele(field_number, sub[1]);
+      lookup_value = flags & ESI_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 */
-               US"listextract",              /* condition type */
+               flags,                          /* were previously skipping */
+               lookup_value != NULL,           /* success/failure indicator */
+               save_lookup_value,              /* value to reset for string2 */
+               &s,                             /* input pointer */
+               &yield,                         /* output pointer */
+               US"listextract",                        /* condition type */
               &resetok))
         {
         case 1: goto EXPAND_FAILED;          /* when all is well, the */
@@ -6310,15 +6326,16 @@ while (*s)
       restore_expand_strings(save_expand_nmax, save_expand_nstring,
         save_expand_nlength);
 
-      if (skipping) continue;
+      if (flags & ESI_SKIPPING) continue;
       break;
       }
 
     case EITEM_LISTQUOTE:
       {
       uschar * sub[2];
-      switch(read_subs(sub, 2, 2, &s, skipping, TRUE, name, &resetok, NULL))
+      switch(read_subs(sub, 2, 2, &s, flags, TRUE, name, &resetok, NULL))
         {
+       case -1: continue;      /* skipping */
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
         case 3: goto EXPAND_FAILED;
@@ -6329,7 +6346,6 @@ while (*s)
        yield = string_catn(yield, sub[1], 1);
        }
       else yield = string_catn(yield, US" ", 1);
-      if (skipping) continue;
       break;
       }
 
@@ -6346,7 +6362,8 @@ while (*s)
        expand_string_message = US"missing '{' for field arg of certextract";
        goto EXPAND_FAILED_CURLY;                                       /*}*/
        }
-      sub[0] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok, NULL);
+      sub[0] = expand_string_internal(s+1,
+               ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | flags, &s, &resetok, NULL);
       if (!sub[0])     goto EXPAND_FAILED;                             /*{{*/
       if (*s++ != '}')
         {
@@ -6378,7 +6395,8 @@ while (*s)
          "be a certificate variable";
        goto EXPAND_FAILED;
        }
-      sub[1] = expand_string_internal(s+1, TRUE, &s, skipping, FALSE, &resetok, NULL);
+      sub[1] = expand_string_internal(s+1,
+               ESI_BRACE_ENDS | flags & ESI_SKIPPING, &s, &resetok, NULL);
       if (!sub[1])     goto EXPAND_FAILED;                             /*{{*/
       if (*s++ != '}')
         {
@@ -6386,7 +6404,7 @@ while (*s)
        goto EXPAND_FAILED_CURLY;
        }
 
-      if (skipping)
+      if (flags & ESI_SKIPPING)
        lookup_value = NULL;
       else
        {
@@ -6394,12 +6412,12 @@ while (*s)
        if (*expand_string_message) goto EXPAND_FAILED;
        }
       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 */
-               US"certextract",              /* condition type */
+               flags,                          /* were previously skipping */
+               lookup_value != NULL,           /* success/failure indicator */
+               save_lookup_value,              /* value to reset for string2 */
+               &s,                             /* input pointer */
+               &yield,                         /* output pointer */
+               US"certextract",                        /* condition type */
               &resetok))
         {
         case 1: goto EXPAND_FAILED;          /* when all is well, the */
@@ -6408,7 +6426,7 @@ while (*s)
 
       restore_expand_strings(save_expand_nmax, save_expand_nstring,
         save_expand_nlength);
-      if (skipping) continue;
+      if (flags & ESI_SKIPPING) continue;
       break;
       }
 #endif /*DISABLE_TLS*/
@@ -6433,7 +6451,8 @@ while (*s)
        goto EXPAND_FAILED_CURLY;                                       /*}*/
        }
 
-      if (!(list = expand_string_internal(s, TRUE, &s, skipping, TRUE, &resetok, NULL)))
+      if (!(list = expand_string_internal(s,
+             ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | flags, &s, &resetok, NULL)))
        goto EXPAND_FAILED;                                             /*{{*/
       if (*s++ != '}')
         {
@@ -6451,7 +6470,8 @@ while (*s)
          expand_string_message = US"missing '{' for second arg of reduce";
          goto EXPAND_FAILED_CURLY;                                     /*}*/
          }
-        t = expand_string_internal(s, TRUE, &s, skipping, TRUE, &resetok, NULL);
+        t = expand_string_internal(s,
+             ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | flags, &s, &resetok, NULL);
         if (!t) goto EXPAND_FAILED;
         lookup_value = t;                                              /*{{*/
         if (*s++ != '}')
@@ -6478,7 +6498,8 @@ while (*s)
       the normal internal expansion function. */
 
       if (item_type != EITEM_FILTER)
-        temp = expand_string_internal(s, TRUE, &s, TRUE, TRUE, &resetok, NULL);
+        temp = expand_string_internal(s,
+         ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | ESI_SKIPPING, &s, &resetok, NULL);
       else
         if ((temp = eval_condition(expr, &resetok, NULL))) s = temp;
 
@@ -6509,7 +6530,7 @@ while (*s)
       /* If we are skipping, we can now just move on to the next item. When
       processing for real, we perform the iteration. */
 
-      if (skipping) continue;
+      if (flags & ESI_SKIPPING) continue;
       while ((iterate_item = string_nextinlist(&list, &sep, NULL, 0)))
         {
         *outsep = (uschar)sep;      /* Separator as a string */
@@ -6540,7 +6561,8 @@ while (*s)
 
         else
           {
-         uschar * t = expand_string_internal(expr, TRUE, NULL, skipping, TRUE, &resetok, NULL);
+         uschar * t = expand_string_internal(expr,
+           ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | flags, NULL, &resetok, NULL);
           temp = t;
           if (!temp)
             {
@@ -6612,7 +6634,7 @@ while (*s)
       /* Restore preserved $item */
 
       iterate_item = save_iterate_item;
-      if (skipping) continue;
+      if (flags & ESI_SKIPPING) continue;
       break;
       }
 
@@ -6631,7 +6653,8 @@ while (*s)
        goto EXPAND_FAILED_CURLY;                                       /*}*/
        }
 
-      srclist = expand_string_internal(s, TRUE, &s, skipping, TRUE, &resetok, NULL);
+      srclist = expand_string_internal(s,
+             ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | flags, &s, &resetok, NULL);
       if (!srclist) goto EXPAND_FAILED;                                        /*{{*/
       if (*s++ != '}')
         {
@@ -6646,7 +6669,8 @@ while (*s)
        goto EXPAND_FAILED_CURLY;                                       /*}*/
        }
 
-      cmp = expand_string_internal(s, TRUE, &s, skipping, FALSE, &resetok, NULL);
+      cmp = expand_string_internal(s,
+             ESI_BRACE_ENDS | flags & ESI_SKIPPING, &s, &resetok, NULL);
       if (!cmp) goto EXPAND_FAILED;                                    /*{{*/
       if (*s++ != '}')
         {
@@ -6681,7 +6705,8 @@ while (*s)
        }
 
       xtract = s;
-      if (!(tmp = expand_string_internal(s, TRUE, &s, TRUE, TRUE, &resetok, NULL)))
+      if (!(tmp = expand_string_internal(s,
+       ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | ESI_SKIPPING, &s, &resetok, NULL)))
        goto EXPAND_FAILED;
       xtract = string_copyn(xtract, s - xtract);
                                                                        /*{{*/
@@ -6697,7 +6722,7 @@ while (*s)
         goto EXPAND_FAILED;
         }
 
-      if (skipping) continue;
+      if (flags & ESI_SKIPPING) continue;
 
       while ((srcitem = string_nextinlist(&srclist, &sep, NULL, 0)))
        {
@@ -6708,8 +6733,8 @@ while (*s)
 
        /* extract field for comparisons */
        iterate_item = srcitem;
-       if (  !(srcfield = expand_string_internal(xtract, FALSE, NULL, FALSE,
-                                         TRUE, &resetok, NULL))
+       if (  !(srcfield = expand_string_internal(xtract,
+                                 ESI_HONOR_DOLLAR, NULL, &resetok, NULL))
           || !*srcfield)
          {
          expand_string_message = string_sprintf(
@@ -6814,18 +6839,15 @@ while (*s)
         goto EXPAND_FAILED;
         }
 
-      switch(read_subs(argv, EXPAND_DLFUNC_MAX_ARGS + 2, 2, &s, skipping,
+      switch(read_subs(argv, EXPAND_DLFUNC_MAX_ARGS + 2, 2, &s, flags,
            TRUE, name, &resetok, NULL))
         {
+       case -1: continue;      /* skipping */
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
         case 3: goto EXPAND_FAILED;
         }
 
-      /* If skipping, we don't actually do anything */
-
-      if (skipping) continue;
-
       /* Look up the dynamically loaded object handle in the tree. If it isn't
       found, dlopen() the file and put the handle in the tree for next time. */
 
@@ -6891,7 +6913,8 @@ while (*s)
       if (Uskip_whitespace(&s) != '{')                                 /*}*/
        goto EXPAND_FAILED;
 
-      key = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok, NULL);
+      key = expand_string_internal(s+1,
+             ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | flags, &s, &resetok, NULL);
       if (!key) goto EXPAND_FAILED;                                    /*{{*/
       if (*s++ != '}')
         {
@@ -6902,18 +6925,18 @@ while (*s)
       lookup_value = US getenv(CS key);
 
       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 */
-               US"env",                      /* condition type */
+               flags,                          /* were previously skipping */
+               lookup_value != NULL,           /* success/failure indicator */
+               save_lookup_value,              /* value to reset for string2 */
+               &s,                             /* input pointer */
+               &yield,                         /* output pointer */
+               US"env",                                /* condition type */
               &resetok))
         {
         case 1: goto EXPAND_FAILED;          /* when all is well, the */
         case 2: goto EXPAND_FAILED_CURLY;    /* returned value is 0 */
         }
-      if (skipping) continue;
+      if (flags & ESI_SKIPPING) continue;
       break;
       }
 
@@ -6926,8 +6949,9 @@ while (*s)
       gstring * g = NULL;
       BOOL quoted = FALSE;
 
-      switch (read_subs(sub, 3, 3, CUSS &s, skipping, TRUE, name, &resetok, NULL))
+      switch (read_subs(sub, 3, 3, CUSS &s, flags, TRUE, name, &resetok, NULL))
         {
+       case -1: continue;      /* skipping */
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
         case 3: goto EXPAND_FAILED;
@@ -6996,7 +7020,6 @@ while (*s)
       yield = string_catn(yield, US"@", 1);
       yield = string_cat(yield, sub[2]);
 
-      if (skipping) continue;
       break;
       }
 #endif /*SUPPORT_SRS*/
@@ -7009,7 +7032,7 @@ while (*s)
   DEBUG(D_expand)
     if (yield && (start > 0 || *s))    /* only if not the sole expansion of the line */
       debug_expansion_interim(US"item-res",
-                             yield->s + start, yield->ptr - start, skipping);
+                             yield->s + start, yield->ptr - start, !!(flags & ESI_SKIPPING));
   continue;
 
 NOT_ITEM: ;
@@ -7055,8 +7078,8 @@ NOT_ITEM: ;
        if (s[1] == '$')
          {
          const uschar * s1 = s;
-         sub = expand_string_internal(s+2, TRUE, &s1, skipping,
-                 FALSE, &resetok, NULL);
+         sub = expand_string_internal(s+2,
+             ESI_BRACE_ENDS | flags & ESI_SKIPPING, &s1, &resetok, NULL);
          if (!sub)       goto EXPAND_FAILED;           /*{*/
          if (*s1 != '}')
            {                                           /*{*/
@@ -7074,7 +7097,8 @@ NOT_ITEM: ;
         /*FALLTHROUGH*/
 #endif
       default:
-       sub = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok, NULL);
+       sub = expand_string_internal(s+1,
+               ESI_BRACE_ENDS | ESI_HONOR_DOLLAR | flags, &s, &resetok, NULL);
        if (!sub) goto EXPAND_FAILED;
        s++;
        break;
@@ -7086,7 +7110,7 @@ NOT_ITEM: ;
     for the existence of $sender_host_address before trying to mask it. For
     other operations, doing them may not fail, but it is a waste of time. */
 
-    if (skipping && c >= 0) continue;
+    if (flags & ESI_SKIPPING && c >= 0) continue;
 
     /* Otherwise, switch on the operator type.  After handling go back
     to the main loop top. */
@@ -7171,7 +7195,8 @@ NOT_ITEM: ;
 
       case EOP_EXPAND:
        {
-       uschar *expanded = expand_string_internal(sub, FALSE, NULL, skipping, TRUE, &resetok, NULL);
+       uschar *expanded = expand_string_internal(sub,
+               ESI_HONOR_DOLLAR | flags & ESI_SKIPPING, NULL, &resetok, NULL);
        if (!expanded)
          {
          expand_string_message =
@@ -8162,7 +8187,7 @@ NOT_ITEM: ;
          debug_printf_indent("|-----op-res: %.*s\n", i, s);
          if (tainted)
            {
-           debug_printf_indent("%s     \\__", skipping ? "|     " : "      ");
+           debug_printf_indent("%s     \\__", flags & ESI_SKIPPING ? "|     " : "      ");
            debug_print_taint(yield->s);
            }
          }
@@ -8174,7 +8199,7 @@ NOT_ITEM: ;
          if (tainted)
            {
            debug_printf_indent("%s",
-             skipping
+             flags & ESI_SKIPPING
              ? UTF8_VERT "             " : "           " UTF8_UP_RIGHT UTF8_HORIZ UTF8_HORIZ);
            debug_print_taint(yield->s);
            }
@@ -8208,7 +8233,7 @@ NOT_ITEM: ;
       reset_point = store_mark();
       g = store_get(sizeof(gstring), GET_UNTAINTED);   /* alloc _before_ calling find_variable() */
       }
-    if (!(value = find_variable(name, FALSE, skipping, &newsize)))
+    if (!(value = find_variable(name, FALSE, !!(flags & ESI_SKIPPING), &newsize)))
       {
       expand_string_message =
         string_sprintf("unknown variable in \"${%s}\"", name);
@@ -8236,10 +8261,10 @@ NOT_ITEM: ;
   goto EXPAND_FAILED;
   }
 
-/* If we hit the end of the string when ket_ends is set, there is a missing
+/* If we hit the end of the string when brace_ends is set, there is a missing
 terminating brace. */
 
-if (ket_ends && !*s)
+if (flags & ESI_BRACE_ENDS && !*s)
   {                                                    /*{{*/
   expand_string_message = malformed_header
     ? US"missing } at end of string - could be header name not terminated by colon"
@@ -8270,13 +8295,13 @@ DEBUG(D_expand)
     {
     debug_printf_indent("|--expanding: %.*s\n", (int)(s - string), string);
     debug_printf_indent("%sresult: %s\n",
-      skipping ? "|-----" : "\\_____", yield->s);
+      flags & ESI_SKIPPING ? "|-----" : "\\_____", yield->s);
     if (tainted)
       {
-      debug_printf_indent("%s     \\__", skipping ? "|     " : "      ");
+      debug_printf_indent("%s     \\__", flags & ESI_SKIPPING ? "|     " : "      ");
       debug_print_taint(yield->s);
       }
-    if (skipping)
+    if (flags & ESI_SKIPPING)
       debug_printf_indent("\\___skipping: result is not used\n");
     }
   else
@@ -8286,16 +8311,16 @@ DEBUG(D_expand)
       (int)(s - string), string);
     debug_printf_indent("%s" UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ
       "result: %s\n",
-      skipping ? UTF8_VERT_RIGHT : UTF8_UP_RIGHT,
+      flags & ESI_SKIPPING ? UTF8_VERT_RIGHT : UTF8_UP_RIGHT,
       yield->s);
     if (tainted)
       {
       debug_printf_indent("%s",
-       skipping
+       flags & ESI_SKIPPING
        ? UTF8_VERT "             " : "           " UTF8_UP_RIGHT UTF8_HORIZ UTF8_HORIZ);
       debug_print_taint(yield->s);
       }
-    if (skipping)
+    if (flags & ESI_SKIPPING)
       debug_printf_indent(UTF8_UP_RIGHT UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ
        "skipping: result is not used\n");
     }
@@ -8372,7 +8397,7 @@ if (Ustrpbrk(string, "$\\") != NULL)
   f.search_find_defer = FALSE;
   malformed_header = FALSE;
   store_pool = POOL_MAIN;
-    s = expand_string_internal(string, FALSE, NULL, FALSE, TRUE, NULL, textonly_p);
+    s = expand_string_internal(string, ESI_HONOR_DOLLAR, NULL, NULL, textonly_p);
   store_pool = old_pool;
   return s;
   }