Propagate null gstring through string_catn()
[exim.git] / src / src / expand.c
index 810fc1cde0dc9881f273c19ffe7502194ea54d07..6478920f859b9482e5a45cae7675ce0fdf1a434c 100644 (file)
@@ -4398,6 +4398,36 @@ return yield;
 
 
 
+/************************************************/
+static void
+debug_expansion_interim(const uschar * what, const uschar * value, int nchar,
+  BOOL skipping)
+{
+DEBUG(D_noutf8)
+  debug_printf_indent("|");
+else
+  debug_printf_indent(UTF8_VERT_RIGHT);
+
+for (int fill = 11 - Ustrlen(what); fill > 0; fill--)
+  DEBUG(D_noutf8)
+    debug_printf("-");
+  else
+    debug_printf(UTF8_HORIZ);
+
+debug_printf("%s: %.*s\n", what, nchar, value);
+if (is_tainted(value))
+  {
+  DEBUG(D_noutf8)
+    debug_printf_indent("%s     \\__", skipping ? "|     " : "      ");
+  else
+    debug_printf_indent("%s",
+      skipping
+      ? UTF8_VERT "             " : "           " UTF8_UP_RIGHT UTF8_HORIZ UTF8_HORIZ);
+  debug_printf("(tainted)\n");
+  }
+}
+
+
 /*************************************************
 *                 Expand string                  *
 *************************************************/
@@ -4522,16 +4552,12 @@ while (*s)
       {
       const uschar * t = s + 2;
       for (s = t; *s ; s++) if (*s == '\\' && s[1] == 'N') break;
+
       DEBUG(D_expand)
-       DEBUG(D_noutf8)
-         debug_printf_indent("|--protected: %.*s\n", (int)(s - t), t);
-       else
-         debug_printf_indent(UTF8_VERT_RIGHT UTF8_HORIZ UTF8_HORIZ
-           "protected: %.*s\n", (int)(s - t), t);
+       debug_expansion_interim(US"protected", t, (int)(s - t), skipping);
       yield = string_catn(yield, t, s - t);
-      if (*s != 0) s += 2;
+      if (*s) s += 2;
       }
-
     else
       {
       uschar ch[1];
@@ -4544,30 +4570,22 @@ while (*s)
       s++;
       yield = string_catn(yield, ch, 1);
       }
-
     continue;
     }
 
-  /*{*/
+                                                                       /*{{*/
   /* Anything other than $ is just copied verbatim, unless we are
   looking for a terminating } character. */
 
-  /*{*/
   if (ket_ends && *s == '}') break;
 
   if (*s != '$' || !honour_dollar)
     {
     int i = 1;                                                         /*{*/
-    for(const uschar * t = s+1; *t && *t != '$' && *t != '}' && *t != '\\'; t++)
-      i++;
+    for (const uschar * t = s+1;
+       *t && *t != '$' && *t != '}' && *t != '\\'; t++) i++;
 
-    DEBUG(D_expand)
-      DEBUG(D_noutf8)
-       debug_printf_indent("|-------text: %.*s\n", i, s);
-      else
-       debug_printf_indent(UTF8_VERT_RIGHT
-         UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ
-         "text: %.*s\n", i, s);
+    DEBUG(D_expand) debug_expansion_interim(US"text", s, i, skipping);
 
     yield = string_catn(yield, s, i);
     s += i;
@@ -4582,11 +4600,10 @@ while (*s)
   "$header_". A non-existent header yields a NULL value; nothing is
   inserted. */ /*}*/
 
-  if (isalpha((*(++s))))
+  if (isalpha(*++s))
     {
     const uschar * value;
-    int len;
-    int newsize = 0;
+    int newsize = 0, len;
     gstring * g = NULL;
     uschar * t;
 
@@ -4627,7 +4644,7 @@ while (*s)
       But there is no error here - nothing gets inserted. */
 
       if (!value)
-        {              /*{*/
+        {                                                              /*{*/
         if (Ustrchr(name, '}')) malformed_header = TRUE;
         continue;
         }
@@ -4685,12 +4702,12 @@ while (*s)
   /* After { there can be various things, but they all start with
   an initial word, except for a number for a string match variable. */ /*}*/
 
-  if (isdigit((*(++s))))
+  if (isdigit(*++s))
     {
     int n;
-    s = read_cnumber(&n, s);           /*{*/
+    s = read_cnumber(&n, s);                                           /*{{*/
     if (*s++ != '}')
-      {                                        /*{*/
+      {
       expand_string_message = US"} expected after number";
       goto EXPAND_FAILED;
       }
@@ -4712,6 +4729,11 @@ while (*s)
   s = read_name(name, sizeof(name), s, US"_-");
   item_type = chop_match(name, item_table, nelem(item_table));
 
+  /* Switch on item type.  All nondefault choices should "continue* when
+  skipping, but "break" otherwise so we get debug output for the item
+  expansion. */
+  {
+  int start = gstring_length(yield);
   switch(item_type)
     {
     /* Call an ACL from an expansion.  We feed data in via $acl_arg1 - $acl_arg9.
@@ -4726,8 +4748,8 @@ while (*s)
     case EITEM_ACL:
       /* ${acl {name} {arg1}{arg2}...} */
       {
-      uschar *sub[10]; /* name + arg1-arg9 (which must match number of acl_arg[]) */
-      uschar *user_msg;
+      uschar * sub[10];        /* name + arg1-arg9 (which must match number of acl_arg[]) */
+      uschar * user_msg;
       int rc;
 
       switch(read_subs(sub, nelem(sub), 1, &s, skipping, TRUE, name,
@@ -4748,7 +4770,7 @@ while (*s)
            debug_printf_indent("acl expansion yield: %s\n", user_msg);
          if (user_msg)
             yield = string_cat(yield, user_msg);
-         continue;
+         break;
 
        case DEFER:
           f.expand_string_forcedfail = TRUE;
@@ -4758,12 +4780,13 @@ while (*s)
            rc_names[rc], sub[0]);
          goto EXPAND_FAILED;
        }
+      break;
       }
 
     case EITEM_AUTHRESULTS:
       /* ${authresults {mysystemname}} */
       {
-      uschar *sub_arg[1];
+      uschar * sub_arg[1];
 
       switch(read_subs(sub_arg, nelem(sub_arg), 1, &s, skipping, TRUE, name,
                      &resetok))
@@ -4792,7 +4815,7 @@ while (*s)
 #ifdef EXPERIMENTAL_ARC
       yield = authres_arc(yield);
 #endif
-      continue;
+      break;
       }
 
     /* Handle conditionals - preserve the values of the numerical expansion
@@ -4812,21 +4835,11 @@ while (*s)
        goto EXPAND_FAILED;  /* message already set */
 
       DEBUG(D_expand)
-       DEBUG(D_noutf8)
-         {
-         debug_printf_indent("|--condition: %.*s\n", (int)(next_s - s), s);
-         debug_printf_indent("|-----result: %s\n", cond ? "true" : "false");
-         }
-       else
-         {
-         debug_printf_indent(UTF8_VERT_RIGHT UTF8_HORIZ UTF8_HORIZ
-           "condition: %.*s\n",
-           (int)(next_s - s), s);
-         debug_printf_indent(UTF8_VERT_RIGHT UTF8_HORIZ UTF8_HORIZ
-           UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ
-           "result: %s\n",
-           cond ? "true" : "false");
-         }
+       {
+       debug_expansion_interim(US"condition", s, (int)(next_s - s), skipping);
+       debug_expansion_interim(US"result",
+         cond ? US"true" : US"false", cond ? 4 : 5, skipping);
+       }
 
       s = next_s;
 
@@ -4851,7 +4864,7 @@ while (*s)
 
       restore_expand_strings(save_expand_nmax, save_expand_nstring,
         save_expand_nlength);
-      continue;
+      break;
       }
 
 #ifdef SUPPORT_I18N
@@ -4882,14 +4895,13 @@ while (*s)
        goto EXPAND_FAILED;
        }
 
-      if (!skipping)
-       {
-       if (!(encoded = imap_utf7_encode(sub_arg[0], headers_charset,
-                           sub_arg[1][0], sub_arg[2], &expand_string_message)))
-         goto EXPAND_FAILED;
-       yield = string_cat(yield, encoded);
-       }
-      continue;
+      if (skipping) continue;
+
+      if (!(encoded = imap_utf7_encode(sub_arg[0], headers_charset,
+                         sub_arg[1][0], sub_arg[2], &expand_string_message)))
+       goto EXPAND_FAILED;
+      yield = string_cat(yield, encoded);
+      break;
       }
 #endif
 
@@ -4905,9 +4917,9 @@ while (*s)
       int stype, partial, affixlen, starflags;
       int expand_setup = 0;
       int nameptr = 0;
-      uschar *key, *filename;
+      uschar * key, * filename;
       const uschar * affix, * opts;
-      uschar *save_lookup_value = lookup_value;
+      uschar * save_lookup_value = lookup_value;
       int save_expand_nmax =
         save_expand_strings(save_expand_nstring, save_expand_nlength);
 
@@ -4975,15 +4987,12 @@ while (*s)
           goto EXPAND_FAILED;
           }
         }
-      else
-        {
-        if (key)
-          {
-          expand_string_message = string_sprintf("a single key was given for "
-            "lookup type \"%s\", which is not a single-key lookup type", name);
-          goto EXPAND_FAILED;
-          }
-        }
+      else if (key)
+       {
+       expand_string_message = string_sprintf("a single key was given for "
+         "lookup type \"%s\", which is not a single-key lookup type", name);
+       goto EXPAND_FAILED;
+       }
 
       /* Get the next string in brackets and expand it. It is the file name for
       single-key+file lookups, and the whole query otherwise. In the case of
@@ -5069,7 +5078,9 @@ while (*s)
 
       restore_expand_strings(save_expand_nmax, save_expand_nstring,
         save_expand_nlength);
-      continue;
+
+      if (skipping) continue;
+      break;
       }
 
     /* If Perl support is configured, handle calling embedded perl subroutines,
@@ -5077,20 +5088,20 @@ while (*s)
     or ${perl{sub}{arg1}{arg2}} or up to a maximum of EXIM_PERL_MAX_ARGS
     arguments (defined below). */
 
-  #define EXIM_PERL_MAX_ARGS 8
+#define EXIM_PERL_MAX_ARGS 8
 
     case EITEM_PERL:
-  #ifndef EXIM_PERL
-    expand_string_message = US"\"${perl\" encountered, but this facility "     /*}*/
-      "is not included in this binary";
-    goto EXPAND_FAILED;
+#ifndef EXIM_PERL
+      expand_string_message = US"\"${perl\" encountered, but this facility "   /*}*/
+       "is not included in this binary";
+      goto EXPAND_FAILED;
 
-  #else   /* EXIM_PERL */
+#else   /* EXIM_PERL */
       {
-      uschar *sub_arg[EXIM_PERL_MAX_ARGS + 2];
-      gstring *new_yield;
+      uschar * sub_arg[EXIM_PERL_MAX_ARGS + 2];
+      gstring * new_yield;
 
-      if ((expand_forbid & RDO_PERL) != 0)
+      if (expand_forbid & RDO_PERL)
         {
         expand_string_message = US"Perl calls are not permitted";
         goto EXPAND_FAILED;
@@ -5112,7 +5123,7 @@ while (*s)
 
       if (!opt_perl_started)
         {
-        uschar *initerror;
+        uschar * initerror;
         if (!opt_perl_startup)
           {
           expand_string_message = US"A setting of perl_startup is needed when "
@@ -5156,17 +5167,16 @@ while (*s)
 
       f.expand_string_forcedfail = FALSE;
       yield = new_yield;
-      continue;
+      break;
       }
-  #endif /* EXIM_PERL */
+#endif /* EXIM_PERL */
 
     /* Transform email address to "prvs" scheme to use
        as BATV-signed return path */
 
     case EITEM_PRVS:
       {
-      uschar *sub_arg[3];
-      uschar *p,*domain;
+      uschar * sub_arg[3], * p, * domain;
 
       switch(read_subs(sub_arg, 3, 2, &s, skipping, TRUE, name, &resetok))
         {
@@ -5215,17 +5225,16 @@ while (*s)
       yield = string_catn(yield, US"@", 1);
       yield = string_cat (yield, domain);
 
-      continue;
+      break;
       }
 
     /* Check a prvs-encoded address for validity */
 
     case EITEM_PRVSCHECK:
       {
-      uschar *sub_arg[3];
+      uschar * sub_arg[3], * p;
       gstring * g;
-      const pcre2_code *re;
-      uschar *p;
+      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
@@ -5254,11 +5263,11 @@ while (*s)
 
       if (regex_match_and_setup(re,sub_arg[0],0,-1))
         {
-        uschar *local_part = string_copyn(expand_nstring[4],expand_nlength[4]);
-        uschar *key_num = string_copyn(expand_nstring[1],expand_nlength[1]);
-        uschar *daystamp = string_copyn(expand_nstring[2],expand_nlength[2]);
-        uschar *hash = string_copyn(expand_nstring[3],expand_nlength[3]);
-        uschar *domain = string_copyn(expand_nstring[5],expand_nlength[5]);
+        uschar * local_part = string_copyn(expand_nstring[4],expand_nlength[4]);
+        uschar * key_num = string_copyn(expand_nstring[1],expand_nlength[1]);
+        uschar * daystamp = string_copyn(expand_nstring[2],expand_nlength[2]);
+        uschar * hash = string_copyn(expand_nstring[3],expand_nlength[3]);
+        uschar * domain = string_copyn(expand_nstring[5],expand_nlength[5]);
 
         DEBUG(D_expand) debug_printf_indent("prvscheck localpart: %s\n", local_part);
         DEBUG(D_expand) debug_printf_indent("prvscheck key number: %s\n", key_num);
@@ -5356,15 +5365,16 @@ while (*s)
           case 3: goto EXPAND_FAILED;
           }
 
-      continue;
+      if (skipping) continue;
+      break;
       }
 
     /* Handle "readfile" to insert an entire file */
 
     case EITEM_READFILE:
       {
-      FILE *f;
-      uschar *sub_arg[2];
+      FILE * f;
+      uschar * sub_arg[2];
 
       if ((expand_forbid & RDO_READFILE) != 0)
         {
@@ -5393,7 +5403,7 @@ while (*s)
 
       yield = cat_file(f, yield, sub_arg[1]);
       (void)fclose(f);
-      continue;
+      break;
       }
 
     /* Handle "readsocket" to insert data from a socket, either
@@ -5517,7 +5527,8 @@ while (*s)
        expand_string_message = US"missing '}' closing readsocket";
        goto EXPAND_FAILED_CURLY;
        }
-      continue;
+      if (skipping) continue;
+      break;
 
       /* Come here on failure to create socket, connect socket, write to the
       socket, or timeout on reading. If another substring follows, expand and
@@ -5542,9 +5553,9 @@ while (*s)
 
     case EITEM_RUN:
       {
-      FILE *f;
-      uschar *arg;
-      const uschar **argv;
+      FILE * f;
+      uschar * arg;
+      const uschar ** argv;
       pid_t pid;
       int fd_in, fd_out;
 
@@ -5651,7 +5662,8 @@ while (*s)
         case 2: goto EXPAND_FAILED_CURLY;    /* returned value is 0 */
         }
 
-      continue;
+      if (skipping) continue;
+      break;
       }
 
     /* Handle character translation for "tr" */
@@ -5660,7 +5672,7 @@ while (*s)
       {
       int oldptr = gstring_length(yield);
       int o2m;
-      uschar *sub[3];
+      uschar * sub[3];
 
       switch(read_subs(sub, 3, 3, &s, skipping, TRUE, name, &resetok))
         {
@@ -5682,7 +5694,8 @@ while (*s)
           }
         }
 
-      continue;
+      if (skipping) continue;
+      break;
       }
 
     /* Handle "hash", "length", "nhash", and "substr" when they are given with
@@ -5696,7 +5709,7 @@ while (*s)
       int len;
       uschar *ret;
       int val[2] = { 0, -1 };
-      uschar *sub[3];
+      uschar * sub[3];
 
       /* "length" takes only 2 arguments whereas the others take 2 or 3.
       Ensure that sub[2] is set in the ${length } case. */
@@ -5745,7 +5758,8 @@ while (*s)
       if (!ret)
        goto EXPAND_FAILED;
       yield = string_catn(yield, ret, len);
-      continue;
+      if (skipping) continue;
+      break;
       }
 
     /* Handle HMAC computation: ${hmac{<algorithm>}{<secret>}{<text>}}
@@ -5760,14 +5774,14 @@ while (*s)
 
     case EITEM_HMAC:
       {
-      uschar *sub[3];
+      uschar * sub[3];
       md5 md5_base;
       hctx sha1_ctx;
-      void *use_base;
+      void * use_base;
       int type;
       int hashlen;      /* Number of octets for the hash algorithm's output */
       int hashblocklen; /* Number of octets the hash algorithm processes */
-      uschar *keyptr, *p;
+      uschar * keyptr, * p;
       unsigned int keylen;
 
       uschar keyhash[MAX_HASHLEN];
@@ -5784,79 +5798,78 @@ while (*s)
         case 3: goto EXPAND_FAILED;
         }
 
-      if (!skipping)
+      if (skipping) continue;
+
+      if (Ustrcmp(sub[0], "md5") == 0)
        {
-       if (Ustrcmp(sub[0], "md5") == 0)
-         {
-         type = HMAC_MD5;
-         use_base = &md5_base;
-         hashlen = 16;
-         hashblocklen = 64;
-         }
-       else if (Ustrcmp(sub[0], "sha1") == 0)
-         {
-         type = HMAC_SHA1;
-         use_base = &sha1_ctx;
-         hashlen = 20;
-         hashblocklen = 64;
-         }
-       else
-         {
-         expand_string_message =
-           string_sprintf("hmac algorithm \"%s\" is not recognised", sub[0]);
-         goto EXPAND_FAILED;
-         }
+       type = HMAC_MD5;
+       use_base = &md5_base;
+       hashlen = 16;
+       hashblocklen = 64;
+       }
+      else if (Ustrcmp(sub[0], "sha1") == 0)
+       {
+       type = HMAC_SHA1;
+       use_base = &sha1_ctx;
+       hashlen = 20;
+       hashblocklen = 64;
+       }
+      else
+       {
+       expand_string_message =
+         string_sprintf("hmac algorithm \"%s\" is not recognised", sub[0]);
+       goto EXPAND_FAILED;
+       }
 
-       keyptr = sub[1];
-       keylen = Ustrlen(keyptr);
+      keyptr = sub[1];
+      keylen = Ustrlen(keyptr);
 
-       /* If the key is longer than the hash block length, then hash the key
-       first */
+      /* If the key is longer than the hash block length, then hash the key
+      first */
 
-       if (keylen > hashblocklen)
-         {
-         chash_start(type, use_base);
-         chash_end(type, use_base, keyptr, keylen, keyhash);
-         keyptr = keyhash;
-         keylen = hashlen;
-         }
+      if (keylen > hashblocklen)
+       {
+       chash_start(type, use_base);
+       chash_end(type, use_base, keyptr, keylen, keyhash);
+       keyptr = keyhash;
+       keylen = hashlen;
+       }
 
-       /* Now make the inner and outer key values */
+      /* Now make the inner and outer key values */
 
-       memset(innerkey, 0x36, hashblocklen);
-       memset(outerkey, 0x5c, hashblocklen);
+      memset(innerkey, 0x36, hashblocklen);
+      memset(outerkey, 0x5c, hashblocklen);
 
-       for (int i = 0; i < keylen; i++)
-         {
-         innerkey[i] ^= keyptr[i];
-         outerkey[i] ^= keyptr[i];
-         }
+      for (int i = 0; i < keylen; i++)
+       {
+       innerkey[i] ^= keyptr[i];
+       outerkey[i] ^= keyptr[i];
+       }
 
-       /* Now do the hashes */
+      /* Now do the hashes */
 
-       chash_start(type, use_base);
-       chash_mid(type, use_base, innerkey);
-       chash_end(type, use_base, sub[2], Ustrlen(sub[2]), innerhash);
+      chash_start(type, use_base);
+      chash_mid(type, use_base, innerkey);
+      chash_end(type, use_base, sub[2], Ustrlen(sub[2]), innerhash);
 
-       chash_start(type, use_base);
-       chash_mid(type, use_base, outerkey);
-       chash_end(type, use_base, innerhash, hashlen, finalhash);
+      chash_start(type, use_base);
+      chash_mid(type, use_base, outerkey);
+      chash_end(type, use_base, innerhash, hashlen, finalhash);
 
-       /* Encode the final hash as a hex string */
+      /* Encode the final hash as a hex string */
 
-       p = finalhash_hex;
-       for (int i = 0; i < hashlen; i++)
-         {
-         *p++ = hex_digits[(finalhash[i] & 0xf0) >> 4];
-         *p++ = hex_digits[finalhash[i] & 0x0f];
-         }
+      p = finalhash_hex;
+      for (int i = 0; i < hashlen; i++)
+       {
+       *p++ = hex_digits[(finalhash[i] & 0xf0) >> 4];
+       *p++ = hex_digits[finalhash[i] & 0x0f];
+       }
 
-       DEBUG(D_any) debug_printf("HMAC[%s](%.*s,%s)=%.*s\n",
-         sub[0], (int)keylen, keyptr, sub[2], hashlen*2, finalhash_hex);
+      DEBUG(D_any) debug_printf("HMAC[%s](%.*s,%s)=%.*s\n",
+       sub[0], (int)keylen, keyptr, sub[2], hashlen*2, finalhash_hex);
 
-       yield = string_catn(yield, finalhash_hex, hashlen*2);
-       }
-      continue;
+      yield = string_catn(yield, finalhash_hex, hashlen*2);
+      break;
       }
 
     /* Handle global substitution for "sg" - like Perl's s/xxx/yyy/g operator.
@@ -5869,8 +5882,7 @@ while (*s)
       PCRE2_SIZE roffset;
       pcre2_match_data * md;
       int err, emptyopt;
-      uschar *subject;
-      uschar *sub[3];
+      uschar * subject, * sub[3];
       int save_expand_nmax =
         save_expand_strings(save_expand_nstring, save_expand_nlength);
 
@@ -5881,6 +5893,7 @@ while (*s)
         case 3: goto EXPAND_FAILED;
         }
 
+      /*XXX no handling of skipping? */
       /* Compile the regular expression */
 
       if (!(re = pcre2_compile((PCRE2_SPTR)sub[1], PCRE2_ZERO_TERMINATED,
@@ -5908,7 +5921,7 @@ while (*s)
        PCRE2_SIZE * ovec = pcre2_get_ovector_pointer(md);
        int n = pcre2_match(re, (PCRE2_SPTR)subject, slen, moffset + moffsetextra,
          PCRE_EOPT | emptyopt, md, pcre_mtc_ctx);
-        uschar *insert;
+        uschar * insert;
 
         /* No match - if we previously set PCRE_NOTEMPTY after a null match, this
         is not necessarily the end. We want to repeat the match from one
@@ -5943,7 +5956,8 @@ while (*s)
 
         /* Copy the characters before the match, plus the expanded insertion. */
 
-        yield = string_catn(yield, subject + moffset, ovec[0] - moffset);
+       yield = string_catn(yield, subject + moffset, ovec[0] - moffset);
+
         if (!(insert = expand_string(sub[2])))
          goto EXPAND_FAILED;
         yield = string_cat(yield, insert);
@@ -5970,7 +5984,8 @@ while (*s)
 
       restore_expand_strings(save_expand_nmax, save_expand_nstring,
         save_expand_nlength);
-      continue;
+      if (skipping) continue;
+      break;
       }
 
     /* Handle keyed and numbered substring extraction. If the first argument
@@ -5980,8 +5995,7 @@ while (*s)
       {
       int field_number = 1;
       BOOL field_number_set = FALSE;
-      uschar *save_lookup_value = lookup_value;
-      uschar *sub[3];
+      uschar * save_lookup_value = lookup_value, * sub[3];
       int save_expand_nmax =
         save_expand_strings(save_expand_nstring, save_expand_nlength);
 
@@ -6007,7 +6021,7 @@ while (*s)
 
       if (skipping)
        {
-        for (int j = 5; j > 0 && *s == '{'; j--)               /*'}'*/
+        for (int j = 5; j > 0 && *s == '{'; j--)                       /*'}'*/
          {
           if (!expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok))
            goto EXPAND_FAILED;                                 /*'{'*/
@@ -6018,13 +6032,13 @@ while (*s)
            }
          Uskip_whitespace(&s);
          }
-       if (  Ustrncmp(s, "fail", 4) == 0                       /*'{'*/
+       if (  Ustrncmp(s, "fail", 4) == 0                               /*'{'*/
           && (s[4] == '}' || s[4] == ' ' || s[4] == '\t' || !s[4])
           )
          {
          s += 4;
          Uskip_whitespace(&s);
-         }                                                     /*'{'*/
+         }                                                             /*'{'*/
        if (*s != '}')
          {
          expand_string_message = US"missing '}' closing extract";
@@ -6034,10 +6048,10 @@ while (*s)
 
       else for (int i = 0, j = 2; i < j; i++) /* Read the proper number of arguments */
         {
-       if (Uskip_whitespace(&s) == '{')                                                /*'}'*/
+       if (Uskip_whitespace(&s) == '{')                                /*'}'*/
           {
           if (!(sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok)))
-           goto EXPAND_FAILED;                                 /*'{'*/
+           goto EXPAND_FAILED;                                         /*'{'*/
           if (*s++ != '}')
            {
            expand_string_message = string_sprintf(
@@ -6054,7 +6068,7 @@ while (*s)
             {
             int len;
             int x = 0;
-            uschar *p = sub[0];
+            uschar * p = sub[0];
 
             Uskip_whitespace(&p);
             sub[0] = p;
@@ -6063,7 +6077,7 @@ while (*s)
             while (len > 0 && isspace(p[len-1])) len--;
             p[len] = 0;
 
-           if (*p == 0)
+           if (!*p)
              {
              expand_string_message = US"first argument of \"extract\" must "
                "not be empty";
@@ -6075,8 +6089,8 @@ while (*s)
              field_number = -1;
              p++;
              }
-           while (*p != 0 && isdigit(*p)) x = x * 10 + *p++ - '0';
-           if (*p == 0)
+           while (*p && isdigit(*p)) x = x * 10 + *p++ - '0';
+           if (!*p)
              {
              field_number *= x;
              if (fmt == extract_basic) j = 3;               /* Need 3 args */
@@ -6206,7 +6220,8 @@ while (*s)
       restore_expand_strings(save_expand_nmax, save_expand_nstring,
         save_expand_nlength);
 
-      continue;
+      if (skipping) continue;
+      break;
       }
 
     /* return the Nth item from a list */
@@ -6214,8 +6229,7 @@ while (*s)
     case EITEM_LISTEXTRACT:
       {
       int field_number = 1;
-      uschar *save_lookup_value = lookup_value;
-      uschar *sub[2];
+      uschar * save_lookup_value = lookup_value, * sub[2];
       int save_expand_nmax =
         save_expand_strings(save_expand_nstring, save_expand_nlength);
 
@@ -6223,15 +6237,15 @@ while (*s)
 
       for (int i = 0; i < 2; i++)
         {
-        if (Uskip_whitespace(&s) != '{')                                       /*'}'*/
+        if (Uskip_whitespace(&s) != '{')                               /*}*/
          {
          expand_string_message = string_sprintf(
-           "missing '{' for arg %d of listextract", i+1);
+           "missing '{' for arg %d of listextract", i+1);              /*}*/
          goto EXPAND_FAILED_CURLY;
          }
 
        sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok);
-       if (!sub[i])     goto EXPAND_FAILED;            /*{*/
+       if (!sub[i])     goto EXPAND_FAILED;                            /*{{*/
        if (*s++ != '}')
          {
          expand_string_message = string_sprintf(
@@ -6304,7 +6318,8 @@ while (*s)
       restore_expand_strings(save_expand_nmax, save_expand_nstring,
         save_expand_nlength);
 
-      continue;
+      if (skipping) continue;
+      break;
       }
 
     case EITEM_LISTQUOTE:
@@ -6322,14 +6337,14 @@ while (*s)
        yield = string_catn(yield, sub[1], 1);
        }
       else yield = string_catn(yield, US" ", 1);
-      continue;
+      if (skipping) continue;
+      break;
       }
 
 #ifndef DISABLE_TLS
     case EITEM_CERTEXTRACT:
       {
-      uschar *save_lookup_value = lookup_value;
-      uschar *sub[2];
+      uschar * save_lookup_value = lookup_value, * sub[2];
       int save_expand_nmax =
         save_expand_strings(save_expand_nstring, save_expand_nlength);
 
@@ -6337,10 +6352,10 @@ while (*s)
       if (Uskip_whitespace(&s) != '{')                                 /*}*/
        {
        expand_string_message = US"missing '{' for field arg of certextract";
-       goto EXPAND_FAILED_CURLY;
+       goto EXPAND_FAILED_CURLY;                                       /*}*/
        }
       sub[0] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok);
-      if (!sub[0])     goto EXPAND_FAILED;             /*{*/
+      if (!sub[0])     goto EXPAND_FAILED;                             /*{{*/
       if (*s++ != '}')
         {
        expand_string_message = US"missing '}' closing field arg of certextract";
@@ -6363,7 +6378,7 @@ while (*s)
       if (Uskip_whitespace(&s) != '{')                                 /*}*/
        {
        expand_string_message = US"missing '{' for cert variable arg of certextract";
-       goto EXPAND_FAILED_CURLY;
+       goto EXPAND_FAILED_CURLY;                                       /*}*/
        }
       if (*++s != '$')
         {
@@ -6372,7 +6387,7 @@ while (*s)
        goto EXPAND_FAILED;
        }
       sub[1] = expand_string_internal(s+1, TRUE, &s, skipping, FALSE, &resetok);
-      if (!sub[1])     goto EXPAND_FAILED;             /*{*/
+      if (!sub[1])     goto EXPAND_FAILED;                             /*{{*/
       if (*s++ != '}')
         {
        expand_string_message = US"missing '}' closing cert variable arg of certextract";
@@ -6401,7 +6416,8 @@ while (*s)
 
       restore_expand_strings(save_expand_nmax, save_expand_nstring,
         save_expand_nlength);
-      continue;
+      if (skipping) continue;
+      break;
       }
 #endif /*DISABLE_TLS*/
 
@@ -6411,27 +6427,24 @@ while (*s)
     case EITEM_MAP:
     case EITEM_REDUCE:
       {
-      int sep = 0;
-      int save_ptr = gstring_length(yield);
+      int sep = 0, save_ptr = gstring_length(yield);
       uschar outsep[2] = { '\0', '\0' };
       const uschar *list, *expr, *temp;
-      uschar *save_iterate_item = iterate_item;
-      uschar *save_lookup_value = lookup_value;
+      uschar * save_iterate_item = iterate_item;
+      uschar * save_lookup_value = lookup_value;
 
       Uskip_whitespace(&s);
-      if (*s++ != '{') /*}*/
+      if (*s++ != '{')                                                 /*}*/
         {
        expand_string_message =
          string_sprintf("missing '{' for first arg of %s", name);
-       goto EXPAND_FAILED_CURLY;
+       goto EXPAND_FAILED_CURLY;                                       /*}*/
        }
 
       if (!(list = expand_string_internal(s, TRUE, &s, skipping, TRUE, &resetok)))
-       goto EXPAND_FAILED;
-      /*{*/
+       goto EXPAND_FAILED;                                             /*{{*/
       if (*s++ != '}')
         {
-       /*{*/
        expand_string_message =
          string_sprintf("missing '}' closing first arg of %s", name);
        goto EXPAND_FAILED_CURLY;
@@ -6441,14 +6454,14 @@ while (*s)
         {
        uschar * t;
         Uskip_whitespace(&s);
-        if (*s++ != '{')
+        if (*s++ != '{')                                               /*}*/
          {
          expand_string_message = US"missing '{' for second arg of reduce";
-         goto EXPAND_FAILED_CURLY;
+         goto EXPAND_FAILED_CURLY;                                     /*}*/
          }
         t = expand_string_internal(s, TRUE, &s, skipping, TRUE, &resetok);
         if (!t) goto EXPAND_FAILED;
-        lookup_value = t;
+        lookup_value = t;                                              /*{{*/
         if (*s++ != '}')
          {
          expand_string_message = US"missing '}' closing second arg of reduce";
@@ -6457,7 +6470,7 @@ while (*s)
         }
 
       Uskip_whitespace(&s);
-      if (*s++ != '{') /*}*/
+      if (*s++ != '{')                                                 /*}*/
         {
        expand_string_message =
          string_sprintf("missing '{' for last arg of %s", name);       /*}*/
@@ -6484,18 +6497,18 @@ while (*s)
         goto EXPAND_FAILED;
         }
 
-      Uskip_whitespace(&s);                            /*{*/
+      Uskip_whitespace(&s);                                            /*{{{*/
       if (*s++ != '}')
-        {                                              /*{*/
+        {
         expand_string_message = string_sprintf("missing } at end of condition "
           "or expression inside \"%s\"; could be an unquoted } in the content",
          name);
         goto EXPAND_FAILED;
         }
 
-      Uskip_whitespace(&s);                            /*{*/
+      Uskip_whitespace(&s);                                            /*{{*/
       if (*s++ != '}')
-        {                                              /*{*/
+        {
         expand_string_message = string_sprintf("missing } at end of \"%s\"",
           name);
         goto EXPAND_FAILED;
@@ -6575,7 +6588,7 @@ while (*s)
           too many; backup and end the loop. Otherwise arrange to double the
           separator. */
 
-          if (temp[seglen] == '\0') { yield->ptr--; break; }
+          if (!temp[seglen]) { yield->ptr--; break; }
           yield = string_catn(yield, outsep, 1);
           temp += seglen + 1;
           }
@@ -6604,28 +6617,27 @@ while (*s)
       /* Restore preserved $item */
 
       iterate_item = save_iterate_item;
-      continue;
+      if (skipping) continue;
+      break;
       }
 
     case EITEM_SORT:
       {
-      int cond_type;
-      int sep = 0;
-      const uschar *srclist, *cmp, *xtract;
+      int sep = 0, cond_type;
+      const uschar * srclist, * cmp, * xtract;
       uschar * opname, * srcitem;
-      const uschar *dstlist = NULL, *dstkeylist = NULL;
-      uschar * tmp;
-      uschar *save_iterate_item = iterate_item;
+      const uschar * dstlist = NULL, * dstkeylist = NULL;
+      uschar * tmp, * save_iterate_item = iterate_item;
 
       Uskip_whitespace(&s);
-      if (*s++ != '{')
+      if (*s++ != '{')                                                 /*}*/
         {
         expand_string_message = US"missing '{' for list arg of sort";
-       goto EXPAND_FAILED_CURLY;
+       goto EXPAND_FAILED_CURLY;                                       /*}*/
        }
 
       srclist = expand_string_internal(s, TRUE, &s, skipping, TRUE, &resetok);
-      if (!srclist) goto EXPAND_FAILED;
+      if (!srclist) goto EXPAND_FAILED;                                        /*{{*/
       if (*s++ != '}')
         {
         expand_string_message = US"missing '}' closing list arg of sort";
@@ -6633,14 +6645,14 @@ while (*s)
        }
 
       Uskip_whitespace(&s);
-      if (*s++ != '{')
+      if (*s++ != '{')                                                 /*}*/
         {
         expand_string_message = US"missing '{' for comparator arg of sort";
-       goto EXPAND_FAILED_CURLY;
+       goto EXPAND_FAILED_CURLY;                                       /*}*/
        }
 
       cmp = expand_string_internal(s, TRUE, &s, skipping, FALSE, &resetok);
-      if (!cmp) goto EXPAND_FAILED;
+      if (!cmp) goto EXPAND_FAILED;                                    /*{{*/
       if (*s++ != '}')
         {
         expand_string_message = US"missing '}' closing comparator arg of sort";
@@ -6667,25 +6679,25 @@ while (*s)
        }
 
       Uskip_whitespace(&s);
-      if (*s++ != '{')
+      if (*s++ != '{')                                                 /*}*/
         {
         expand_string_message = US"missing '{' for extractor arg of sort";
-       goto EXPAND_FAILED_CURLY;
+       goto EXPAND_FAILED_CURLY;                                       /*}*/
        }
 
       xtract = s;
       if (!(tmp = expand_string_internal(s, TRUE, &s, TRUE, TRUE, &resetok)))
        goto EXPAND_FAILED;
       xtract = string_copyn(xtract, s - xtract);
-
+                                                                       /*{{*/
       if (*s++ != '}')
         {
         expand_string_message = US"missing '}' closing extractor arg of sort";
        goto EXPAND_FAILED_CURLY;
        }
-                                                       /*{*/
+                                                                       /*{{*/
       if (*s++ != '}')
-        {                                              /*{*/
+        {
         expand_string_message = US"missing } at end of \"sort\"";
         goto EXPAND_FAILED;
         }
@@ -6695,8 +6707,7 @@ while (*s)
       while ((srcitem = string_nextinlist(&srclist, &sep, NULL, 0)))
        {
        uschar * srcfield, * dstitem;
-       gstring * newlist = NULL;
-       gstring * newkeylist = NULL;
+       gstring * newlist = NULL, * newkeylist = NULL;
 
         DEBUG(D_expand) debug_printf_indent("%s: $item = \"%s\"\n", name, srcitem);
 
@@ -6720,7 +6731,7 @@ while (*s)
 
          /* field for comparison */
          if (!(dstfield = string_nextinlist(&dstkeylist, &sep, NULL, 0)))
-           goto sort_mismatch;
+           goto SORT_MISMATCH;
 
          /* String-comparator names start with a letter; numeric names do not */
 
@@ -6741,7 +6752,7 @@ while (*s)
            while ((dstitem = string_nextinlist(&dstlist, &sep, NULL, 0)))
              {
              if (!(dstfield = string_nextinlist(&dstkeylist, &sep, NULL, 0)))
-               goto sort_mismatch;
+               goto SORT_MISMATCH;
              newlist = string_append_listele(newlist, sep, dstitem);
              newkeylist = string_append_listele(newkeylist, sep, dstfield);
              }
@@ -6772,9 +6783,9 @@ while (*s)
 
       /* Restore preserved $item */
       iterate_item = save_iterate_item;
-      continue;
+      break;
 
-      sort_mismatch:
+      SORT_MISMATCH:
        expand_string_message = US"Internal error in sort (list mismatch)";
        goto EXPAND_FAILED;
       }
@@ -6795,13 +6806,13 @@ while (*s)
 
 #else   /* EXPAND_DLFUNC */
       {
-      tree_node *t;
-      exim_dlfunc_t *func;
-      uschar *result;
+      tree_node * t;
+      exim_dlfunc_t * func;
+      uschar * result;
       int status, argc;
-      uschar *argv[EXPAND_DLFUNC_MAX_ARGS + 3];
+      uschar * argv[EXPAND_DLFUNC_MAX_ARGS + 3];
 
-      if ((expand_forbid & RDO_DLFUNC) != 0)
+      if (expand_forbid & RDO_DLFUNC)
         {
         expand_string_message =
           US"dynamically-loaded functions are not permitted";
@@ -6825,7 +6836,7 @@ while (*s)
 
       if (!(t = tree_search(dlobj_anchor, argv[0])))
         {
-        void *handle = dlopen(CS argv[0], RTLD_LAZY);
+        void * handle = dlopen(CS argv[0], RTLD_LAZY);
         if (!handle)
           {
           expand_string_message = string_sprintf("dlopen \"%s\" failed: %s",
@@ -6859,15 +6870,9 @@ while (*s)
 
       resetok = FALSE;
       result = NULL;
-      for (argc = 0; argv[argc]; argc++);
-      status = func(&result, argc - 2, &argv[2]);
-      if(status == OK)
-        {
-        if (!result) result = US"";
-        yield = string_cat(yield, result);
-        continue;
-        }
-      else
+      for (argc = 0; argv[argc]; argc++) ;
+
+      if ((status = func(&result, argc - 2, &argv[2])) != OK)
         {
         expand_string_message = result ? result : US"(no message)";
         if (status == FAIL_FORCED)
@@ -6877,6 +6882,9 @@ while (*s)
               argv[0], argv[1], status, expand_string_message);
         goto EXPAND_FAILED;
         }
+
+      if (result) yield = string_cat(yield, result);
+      break;
       }
 #endif /* EXPAND_DLFUNC */
 
@@ -6889,10 +6897,10 @@ while (*s)
        goto EXPAND_FAILED;
 
       key = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok);
-      if (!key) goto EXPAND_FAILED;                    /*{*/
+      if (!key) goto EXPAND_FAILED;                                    /*{{*/
       if (*s++ != '}')
         {
-        expand_string_message = US"missing '{' for name arg of env";   /*}*/
+        expand_string_message = US"missing '}' for name arg of env";
        goto EXPAND_FAILED_CURLY;
        }
 
@@ -6910,7 +6918,8 @@ while (*s)
         case 1: goto EXPAND_FAILED;          /* when all is well, the */
         case 2: goto EXPAND_FAILED_CURLY;    /* returned value is 0 */
         }
-      continue;
+      if (skipping) continue;
+      break;
       }
 
 #ifdef SUPPORT_SRS
@@ -6991,10 +7000,25 @@ while (*s)
       /* @$original_domain */
       yield = string_catn(yield, US"@", 1);
       yield = string_cat(yield, sub[2]);
-      continue;
+
+      if (skipping) continue;
+      break;
       }
 #endif /*SUPPORT_SRS*/
+
+    default:
+      goto NOT_ITEM;
     }  /* EITEM_* switch */
+    /*NOTREACHED*/
+
+  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);
+  continue;
+
+NOT_ITEM: ;
+  }
 
   /* Control reaches here if the name is not recognized as one of the more
   complicated expansion items. Check for the "operator" syntax (name terminated
@@ -7004,10 +7028,9 @@ while (*s)
   if (*s == ':')
     {
     int c;
-    uschar *arg = NULL;
-    uschar *sub;
+    uschar * arg = NULL, * sub;
 #ifndef DISABLE_TLS
-    var_entry *vp = NULL;
+    var_entry * vp = NULL;
 #endif
 
     /* Owing to an historical mis-design, an underscore may be part of the
@@ -8137,34 +8160,9 @@ while (*s)
        }       /* EOP_* switch */
 
        DEBUG(D_expand)
-       {
-       const uschar * s = yield->s + start;
-       int i = yield->ptr - start;
-       BOOL tainted = is_tainted(s);
-
-       DEBUG(D_noutf8)
-         {
-         debug_printf_indent("|-----op-res: %.*s\n", i, s);
-         if (tainted)
-           {
-           debug_printf_indent("%s     \\__", skipping ? "|     " : "      ");
-           debug_printf("(tainted)\n");
-           }
-         }
-       else
-         {
-         debug_printf_indent(UTF8_VERT_RIGHT
-           UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ
-           "op-res: %.*s\n", i, s);
-         if (tainted)
-           {
-           debug_printf_indent("%s",
-             skipping
-             ? UTF8_VERT "             " : "           " UTF8_UP_RIGHT UTF8_HORIZ UTF8_HORIZ);
-           debug_printf("(tainted)\n");
-           }
-         }
-       }
+       if (start > 0 || *s)            /* only if not the sole expansion of the line */
+         debug_expansion_interim(US"op-res",
+                                 yield->s + start, yield->ptr - start, skipping);
        continue;
        }
     }