Expansion item ${listquote }. Bug 1066
[exim.git] / src / src / expand.c
index 4377ea1aa2ed9e518a8047e8584e962674fa9126..6d9a1695bd9e4e3c3769374b63e941689969b0a0 100644 (file)
@@ -117,6 +117,7 @@ static uschar *item_table[] = {
 #endif
   US"length",
   US"listextract",
+  US"listquote",
   US"lookup",
   US"map",
   US"nhash",
@@ -151,6 +152,7 @@ enum {
 #endif
   EITEM_LENGTH,
   EITEM_LISTEXTRACT,
+  EITEM_LISTQUOTE,
   EITEM_LOOKUP,
   EITEM_MAP,
   EITEM_NHASH,
@@ -1174,7 +1176,7 @@ static uschar *
 expand_getkeyed(uschar * key, const uschar * s)
 {
 int length = Ustrlen(key);
-while (isspace(*s)) s++;
+Uskip_whitespace(&s);
 
 /* Loop to search for the key */
 
@@ -1186,14 +1188,13 @@ while (*s)
 
   while (*s && *s != '=' && !isspace(*s)) s++;
   dkeylength = s - dkey;
-  while (isspace(*s)) s++;
-  if (*s == '=') while (isspace((*(++s))));
+  if (Uskip_whitespace(&s) == '=') while (isspace((*(++s))));
 
   data = string_dequote(&s);
   if (length == dkeylength && strncmpic(key, dkey, length) == 0)
     return data;
 
-  while (isspace(*s)) s++;
+  Uskip_whitespace(&s);
   }
 
 return NULL;
@@ -2044,7 +2045,7 @@ switch (vp->type)
     s = find_header(US"reply-to:", newsize,
                exists_only ? FH_EXISTS_ONLY|FH_WANT_RAW : FH_WANT_RAW,
                headers_charset);
-    if (s) while (isspace(*s)) s++;
+    if (s) Uskip_whitespace(&s);
     if (!s || !*s)
       {
       *newsize = 0;                            /* For the *s==0 case */
@@ -2055,8 +2056,8 @@ switch (vp->type)
     if (s)
       {
       uschar *t;
-      while (isspace(*s)) s++;
-      for (t = s; *t != 0; t++) if (*t == '\n') *t = ' ';
+      Uskip_whitespace(&s);
+      for (t = s; *t; t++) if (*t == '\n') *t = ' ';
       while (t > s && isspace(t[-1])) t--;
       *t = 0;
       }
@@ -2144,7 +2145,7 @@ read_subs(uschar **sub, int n, int m, const uschar **sptr, BOOL skipping,
 {
 const uschar *s = *sptr;
 
-while (isspace(*s)) s++;
+Uskip_whitespace(&s);
 for (int i = 0; i < n; i++)
   {
   if (*s != '{')
@@ -2161,7 +2162,7 @@ for (int i = 0; i < n; i++)
   if (!(sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, resetok)))
     return 3;
   if (*s++ != '}') return 1;
-  while (isspace(*s)) s++;
+  Uskip_whitespace(&s);
   }
 if (check_end && *s++ != '}')
   {
@@ -2284,9 +2285,7 @@ uschar * p = s;
 unsigned depth = 0;
 BOOL quotesmode = wrap[0] == wrap[1];
 
-while (isspace(*p)) p++;
-
-if (*p == *wrap)
+if (Uskip_whitespace(&p) == *wrap)
   {
   s = ++p;
   wrap++;
@@ -2747,7 +2746,7 @@ switch(cond_type = identify_operator(&s, &opname))
     if (*s++ != '{') goto COND_FAILED_CURLY_START;     /*}*/
 
     switch(read_subs(sub, nelem(sub), 1,
-      &s, yield == NULL, TRUE, US"acl", resetok))
+      &s, yield == NULL, TRUE, name, resetok))
       {
       case 1: expand_string_message = US"too few arguments or bracketing "
         "error for acl";
@@ -2798,7 +2797,7 @@ switch(cond_type = identify_operator(&s, &opname))
     uschar *sub[4];
     while (isspace(*s)) s++;
     if (*s++ != '{') goto COND_FAILED_CURLY_START;     /* }-for-text-editors */
-    switch(read_subs(sub, nelem(sub), 2, &s, yield == NULL, TRUE, US"saslauthd",
+    switch(read_subs(sub, nelem(sub), 2, &s, yield == NULL, TRUE, name,
                    resetok))
       {
       case 1: expand_string_message = US"too few arguments or bracketing "
@@ -3460,7 +3459,7 @@ switch(cond_type = identify_operator(&s, &opname))
     uschar cksum[4];
     BOOL boolvalue = FALSE;
 
-    switch(read_subs(sub, 2, 2, CUSS &s, yield == NULL, FALSE, US"inbound_srs", resetok))
+    switch(read_subs(sub, 2, 2, CUSS &s, yield == NULL, FALSE, name, resetok))
       {
       case 1: expand_string_message = US"too few arguments or bracketing "
        "error for inbound_srs";
@@ -4026,8 +4025,7 @@ int c;
 int_eximarith_t n;
 uschar *s = *sptr;
 
-while (isspace(*s)) s++;
-if (isdigit((c = *s)))
+if (isdigit((c = Uskip_whitespace(&s))))
   {
   int count;
   (void)sscanf(CS s, (decimal? SC_EXIM_DEC "%n" : SC_EXIM_ARITH "%n"), &n, &count);
@@ -4039,7 +4037,7 @@ if (isdigit((c = *s)))
     case 'm': n *= 1024*1024; s++; break;
     case 'g': n *= 1024*1024*1024; s++; break;
     }
-  while (isspace (*s)) s++;
+  Uskip_whitespace(&s);
   }
 else if (c == '(')
   {
@@ -4061,7 +4059,7 @@ eval_op_unary(uschar **sptr, BOOL decimal, uschar **error)
 {
 uschar *s = *sptr;
 int_eximarith_t x;
-while (isspace(*s)) s++;
+Uskip_whitespace(&s);
 if (*s == '+' || *s == '-' || *s == '~')
   {
   int op = *s++;
@@ -4603,7 +4601,7 @@ while (*s != 0)
       uschar *user_msg;
       int rc;
 
-      switch(read_subs(sub, nelem(sub), 1, &s, skipping, TRUE, US"acl",
+      switch(read_subs(sub, nelem(sub), 1, &s, skipping, TRUE, name,
                      &resetok))
         {
         case 1: goto EXPAND_FAILED_CURLY;
@@ -4779,7 +4777,7 @@ while (*s != 0)
       int expand_setup = 0;
       int nameptr = 0;
       uschar *key, *filename;
-      const uschar *affix;
+      const uschar * affix, * opts;
       uschar *save_lookup_value = lookup_value;
       int save_expand_nmax =
         save_expand_strings(save_expand_nstring, save_expand_nlength);
@@ -4819,20 +4817,19 @@ while (*s != 0)
       kinds. Allow everything except space or { to appear; the actual content
       is checked by search_findtype_partial. */                /*}*/
 
-      while (*s != 0 && *s != '{' && !isspace(*s))     /*}*/
+      while (*s && *s != '{' && !isspace(*s))          /*}*/
         {
         if (nameptr < sizeof(name) - 1) name[nameptr++] = *s;
         s++;
         }
-      name[nameptr] = 0;
+      name[nameptr] = '\0';
       while (isspace(*s)) s++;
 
       /* Now check for the individual search type and any partial or default
       options. Only those types that are actually in the binary are valid. */
 
-      stype = search_findtype_partial(name, &partial, &affix, &affixlen,
-        &starflags);
-      if (stype < 0)
+      if ((stype = search_findtype_partial(name, &partial, &affix, &affixlen,
+         &starflags, &opts)) < 0)
         {
         expand_string_message = search_error_message;
         goto EXPAND_FAILED;
@@ -4892,16 +4889,13 @@ while (*s != 0)
         if (mac_islookup(stype, lookup_querystyle))
           filename = NULL;
         else
-          {
-          if (*filename != '/')
-            {
-            expand_string_message = string_sprintf(
-              "absolute file name expected for \"%s\" lookup", name);
-            goto EXPAND_FAILED;
-            }
-          while (*key != 0 && !isspace(*key)) key++;
-          if (*key != 0) *key++ = 0;
-          }
+          if (*filename == '/')
+           {
+           while (*key && !isspace(*key)) key++;
+           if (*key) *key++ = '\0';
+           }
+         else
+           filename = NULL;
         }
 
       /* If skipping, don't do the next bit - just lookup_value == NULL, as if
@@ -4928,7 +4922,7 @@ while (*s != 0)
           goto EXPAND_FAILED;
           }
         lookup_value = search_find(handle, filename, key, partial, affix,
-          affixlen, starflags, &expand_setup);
+          affixlen, starflags, &expand_setup, opts);
         if (f.search_find_defer)
           {
           expand_string_message =
@@ -4988,7 +4982,7 @@ while (*s != 0)
         }
 
       switch(read_subs(sub_arg, EXIM_PERL_MAX_ARGS + 1, 1, &s, skipping, TRUE,
-           US"perl", &resetok))
+           name, &resetok))
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
@@ -5059,7 +5053,7 @@ while (*s != 0)
       uschar *sub_arg[3];
       uschar *p,*domain;
 
-      switch(read_subs(sub_arg, 3, 2, &s, skipping, TRUE, US"prvs", &resetok))
+      switch(read_subs(sub_arg, 3, 2, &s, skipping, TRUE, name, &resetok))
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
@@ -5133,7 +5127,7 @@ while (*s != 0)
       prvscheck_address = NULL;
       prvscheck_keynum = NULL;
 
-      switch(read_subs(sub_arg, 1, 1, &s, skipping, FALSE, US"prvs", &resetok))
+      switch(read_subs(sub_arg, 1, 1, &s, skipping, FALSE, name, &resetok))
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
@@ -5165,7 +5159,7 @@ while (*s != 0)
         prvscheck_keynum = string_copy(key_num);
 
         /* Now expand the second argument */
-        switch(read_subs(sub_arg, 1, 1, &s, skipping, FALSE, US"prvs", &resetok))
+        switch(read_subs(sub_arg, 1, 1, &s, skipping, FALSE, name, &resetok))
           {
           case 1: goto EXPAND_FAILED_CURLY;
           case 2:
@@ -5219,7 +5213,7 @@ while (*s != 0)
         /* 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, US"prvs", &resetok))
+        switch(read_subs(sub_arg, 1, 0, &s, skipping, TRUE, name, &resetok))
           {
           case 1: goto EXPAND_FAILED_CURLY;
           case 2:
@@ -5240,7 +5234,7 @@ while (*s != 0)
            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, US"prvs", &resetok))
+        switch(read_subs(sub_arg, 2, 1, &s, skipping, TRUE, name, &resetok))
           {
           case 1: goto EXPAND_FAILED_CURLY;
           case 2:
@@ -5263,7 +5257,7 @@ while (*s != 0)
         goto EXPAND_FAILED;
         }
 
-      switch(read_subs(sub_arg, 2, 1, &s, skipping, TRUE, US"readfile", &resetok))
+      switch(read_subs(sub_arg, 2, 1, &s, skipping, TRUE, name, &resetok))
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
@@ -5313,7 +5307,7 @@ while (*s != 0)
       /* 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, US"readsocket", &resetok))
+      switch(read_subs(sub_arg, 4, 2, &s, skipping, FALSE, name, &resetok))
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:                             /* Won't occur: no end check */
@@ -5335,8 +5329,9 @@ while (*s != 0)
        uschar * item;
        int sep = 0;
 
-       item = string_nextinlist(&list, &sep, NULL, 0);
-        if ((timeout = readconf_readtime(item, 0, FALSE)) < 0)
+       if (  !(item = string_nextinlist(&list, &sep, NULL, 0))
+          || !*item
+          || (timeout = readconf_readtime(item, 0, FALSE)) < 0)
           {
           expand_string_message = string_sprintf("bad time value %s", item);
           goto EXPAND_FAILED;
@@ -5624,7 +5619,8 @@ while (*s != 0)
 
         /* Create the child process, making it a group leader. */
 
-        if ((pid = child_open(USS argv, NULL, 0077, &fd_in, &fd_out, TRUE)) < 0)
+        if ((pid = child_open(USS argv, NULL, 0077, &fd_in, &fd_out, TRUE,
+                             US"expand-run")) < 0)
           {
           expand_string_message =
             string_sprintf("couldn't create child process: %s", strerror(errno));
@@ -5698,7 +5694,7 @@ while (*s != 0)
       int o2m;
       uschar *sub[3];
 
-      switch(read_subs(sub, 3, 3, &s, skipping, TRUE, US"tr", &resetok))
+      switch(read_subs(sub, 3, 3, &s, skipping, TRUE, name, &resetok))
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
@@ -5910,7 +5906,7 @@ while (*s != 0)
       int save_expand_nmax =
         save_expand_strings(save_expand_nstring, save_expand_nlength);
 
-      switch(read_subs(sub, 3, 3, &s, skipping, TRUE, US"sg", &resetok))
+      switch(read_subs(sub, 3, 3, &s, skipping, TRUE, name, &resetok))
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
@@ -6259,7 +6255,7 @@ while (*s != 0)
 
       for (int i = 0; i < 2; i++)
         {
-        while (isspace(*s)) s++;
+        skip_whitespace(&s);
         if (*s != '{')                                 /*'}'*/
          {
          expand_string_message = string_sprintf(
@@ -6344,6 +6340,23 @@ while (*s != 0)
       continue;
       }
 
+    case EITEM_LISTQUOTE:
+      {
+      uschar * sub[2];
+      switch(read_subs(sub, 2, 2, &s, skipping, TRUE, name, &resetok))
+        {
+        case 1: goto EXPAND_FAILED_CURLY;
+        case 2:
+        case 3: goto EXPAND_FAILED;
+        }
+      for (uschar sep = *sub[0], c; c = *sub[1]; sub[1]++)
+       {
+       if (c == sep) yield = string_catn(yield, sub[1], 1);
+       yield = string_catn(yield, sub[1], 1);
+       }
+      continue;
+      }
+
 #ifndef DISABLE_TLS
     case EITEM_CERTEXTRACT:
       {
@@ -6353,7 +6366,7 @@ while (*s != 0)
         save_expand_strings(save_expand_nstring, save_expand_nlength);
 
       /* Read the field argument */
-      while (isspace(*s)) s++;
+      skip_whitespace(&s);
       if (*s != '{')                                   /*}*/
        {
        expand_string_message = US"missing '{' for field arg of certextract";
@@ -6831,7 +6844,7 @@ while (*s != 0)
         }
 
       switch(read_subs(argv, EXPAND_DLFUNC_MAX_ARGS + 2, 2, &s, skipping,
-           TRUE, US"dlfunc", &resetok))
+           TRUE, name, &resetok))
         {
         case 1: goto EXPAND_FAILED_CURLY;
         case 2:
@@ -7451,6 +7464,8 @@ while (*s != 0)
         continue;
        }
 
+      /* quote a list-item for the given list-separator */
+
       /* mask applies a mask to an IP address; for example the result of
       ${mask:131.111.10.206/28} is 131.111.10.192/28. */