build: use pkg-config for i18n
[exim.git] / src / src / string.c
index cd76e1f1c1ec27d37e42cfe6b821b13d0562afef..1169f0e2c8e2aa6c29e57a578ce71b7f791638b5 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) The Exim Maintainers 2020 - 2023 */
+/* Copyright (c) The Exim Maintainers 2020 - 2024 */
 /* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 /* SPDX-License-Identifier: GPL-2.0-or-later */
 /* Copyright (c) University of Cambridge 1995 - 2018 */
 /* See the file NOTICE for conditions of use and distribution. */
 /* SPDX-License-Identifier: GPL-2.0-or-later */
@@ -1092,6 +1092,49 @@ return list;
 
 
 
 
 
 
+/* Listmaker that takes a format string and args for the element.
+A flag arg is required to handle embedded sep chars in the (expanded) element;
+if false then no check is done */
+
+gstring *
+string_append_listele_fmt(gstring * list, uschar sep, BOOL check,
+  const char * fmt, ...)
+{
+va_list ap;
+unsigned start;
+gstring * g;
+
+if (list && list->ptr)
+  {
+  list = string_catn(list, &sep, 1);
+  start = list->ptr;
+  }
+else
+  start = 0;
+
+va_start(ap, fmt);
+list = string_vformat_trc(list, US __FUNCTION__, __LINE__,
+         STRING_SPRINTF_BUFFER_SIZE, SVFMT_REBUFFER|SVFMT_EXTEND, fmt, ap);
+va_end(ap);
+
+(void) string_from_gstring(list);
+
+/* if the appended element turns out to have an embedded sep char, rewind
+and do the lazy-coded separate string method */
+
+if (!check || !Ustrchr(&list->s[start], sep))
+  return list;
+
+va_start(ap, fmt);
+g = string_vformat_trc(NULL, US __FUNCTION__, __LINE__,
+       STRING_SPRINTF_BUFFER_SIZE, SVFMT_REBUFFER|SVFMT_EXTEND, fmt, ap);
+va_end(ap);
+
+list->ptr = start;
+return string_append_listele_n(list, sep, g->s, g->ptr);
+}
+
+
 /* A slightly-bogus listmaker utility; the separator is a string so
 can be multiple chars - there is no checking for the element content
 containing any of the separator. */
 /* A slightly-bogus listmaker utility; the separator is a string so
 can be multiple chars - there is no checking for the element content
 containing any of the separator. */
@@ -1341,7 +1384,8 @@ The return value can be NULL to signify overflow.
 Field width:           decimal digits, or *
 Precision:             dot, followed by decimal digits or *
 Length modifiers:      h  L  l  ll  z
 Field width:           decimal digits, or *
 Precision:             dot, followed by decimal digits or *
 Length modifiers:      h  L  l  ll  z
-Conversion specifiers: n d o u x X p f e E g G % c s S T W V Y D M
+Conversion specifiers: n d o u x X p f e E g G % c s S T W V Y D M H Z b
+Alternate-form:                #: s/Y/b are silent about a null string
 
 Returns the possibly-new (if copy for growth or taint-handling was needed)
 string, not nul-terminated.
 
 Returns the possibly-new (if copy for growth or taint-handling was needed)
 string, not nul-terminated.
@@ -1595,7 +1639,15 @@ while (*fp)
       goto INSERT_GSTRING;
       }
 #ifndef COMPILE_UTILITY
       goto INSERT_GSTRING;
       }
 #ifndef COMPILE_UTILITY
-    case 'V':                  /* Maybe convert ascii-art to UTF-8 chars */
+    case 'b':                  /* blob pointer, carrying a string */
+      {
+      blob * b = va_arg(ap, blob *);
+      if (b) { s = CS b->data; slen = b->len; }
+      else   { s = null;       slen = Ustrlen(s); }
+      goto INSERT_GSTRING;
+      }
+
+    case 'V':          /* string; maybe convert ascii-art to UTF-8 chars */
       {
       gstring * zg = NULL;
       s = va_arg(ap, char *);
       {
       gstring * zg = NULL;
       s = va_arg(ap, char *);
@@ -1623,19 +1675,20 @@ while (*fp)
       goto INSERT_GSTRING;
       }
 
       goto INSERT_GSTRING;
       }
 
-    case 'W':                  /* Maybe mark up spaces & newlines */
+    case 'W':                  /* Maybe mark up ctrls, spaces & newlines */
       s = va_arg(ap, char *);
       s = va_arg(ap, char *);
-      if (Ustrpbrk(s, " \n") && !IS_DEBUG(D_noutf8))
+      if (s && !IS_DEBUG(D_noutf8))
        {
        gstring * zg = NULL;
        int p = precision;
        {
        gstring * zg = NULL;
        int p = precision;
-       for ( ; *s; s++)
-         {
-         /* Take a given precision as applying to the input; expand
-         it for the transformed result */
 
 
-         if (p >= 0 && --p < 0) break;
-         switch (*s)
+       /* If a precision was given, we can handle embedded NULs. Take it as
+       applying to the input and expand it for the transformed result */
+
+       for ( ; precision >= 0 || *s; s++)
+         if (p >= 0 && --p < 0)
+           break;
+         else switch (*s)
            {
            case ' ':
              zg = string_catn(zg, CUS UTF8_LIGHT_SHADE, 3);
            {
            case ' ':
              zg = string_catn(zg, CUS UTF8_LIGHT_SHADE, 3);
@@ -1646,12 +1699,19 @@ while (*fp)
              if (precision >= 0) precision += 3;
              break;
            default:
              if (precision >= 0) precision += 3;
              break;
            default:
-             zg = string_catn(zg, CUS s, 1);
+             if (*s <= ' ')
+               {       /* base of UTF8 symbols for ASCII control chars */
+               uschar ctrl_symbol[3] = {[0]=0xe2, [1]=0x90, [2]=0x80};
+               ctrl_symbol[2] |= *s;
+               zg = string_catn(zg, ctrl_symbol, 3);
+               if (precision >= 0) precision += 2;
+               }
+             else
+               zg = string_catn(zg, CUS s, 1);
              break;
            }
              break;
            }
-         }
        if (zg) { s = CS zg->s; slen = gstring_length(zg); }
        if (zg) { s = CS zg->s; slen = gstring_length(zg); }
-       else    { s = null;     slen = Ustrlen(s); }
+       else    { s = "";       slen = 0; }
        }
       else
        {
        }
       else
        {
@@ -1660,6 +1720,56 @@ while (*fp)
        }
       goto INSERT_GSTRING;
 
        }
       goto INSERT_GSTRING;
 
+    case 'Z':                  /* pdkim-style "quoteprint" */
+       {
+       gstring * zg = NULL;
+       int p = precision;      /* If given, we can handle embedded NULs */
+
+       s = va_arg(ap, char *);
+       for ( ; precision >= 0 || *s; s++)
+         if (p >= 0 && --p < 0)
+           break;
+         else switch (*s)
+           {
+           case ' ' : zg = string_catn(zg, US"{SP}", 4); break;
+           case '\t': zg = string_catn(zg, US"{TB}", 4); break;
+           case '\r': zg = string_catn(zg, US"{CR}", 4); break;
+           case '\n': zg = string_catn(zg, US"{LF}", 4); break;
+           case '{' : zg = string_catn(zg, US"{BO}", 4); break;
+           case '}' : zg = string_catn(zg, US"{BC}", 4); break;
+           default:
+             {
+             uschar u = *s;
+             if ( (u < 32) || (u > 127) )
+               zg = string_fmt_append(zg, "{%02x}", u);
+             else
+               zg = string_catn(zg, US s, 1);
+             break;
+             }
+           }
+       if (zg) { s = CS zg->s; precision = slen = gstring_length(zg); }
+       else    { s = "";       slen = 0; }
+       }
+      goto INSERT_GSTRING;
+
+    case 'H':                  /* pdkim-style "hexprint" */
+      {
+      s = va_arg(ap, char *);
+      if (precision < 0) break;        /* precision must be given */
+      if (s)
+       {
+       gstring * zg = NULL;
+       for (int p = precision; p > 0; p--)
+         zg = string_fmt_append(zg, "%02x", * US s++);
+
+       if (zg) { s = CS zg->s; precision = slen = gstring_length(zg); }
+       else    { s = "";       slen = 0; }
+       }
+      else
+       { s = "<NULL>"; precision = slen = 6; }
+      }
+      goto INSERT_GSTRING;
+
 #endif
     case 's':
     case 'S':                   /* Forces *lower* case */
 #endif
     case 's':
     case 'S':                   /* Forces *lower* case */
@@ -1698,7 +1808,7 @@ while (*fp)
        }
 
       /* If a width is not specified and the precision is specified, set
        }
 
       /* If a width is not specified and the precision is specified, set
-      the width to the precision, or the string length if shorted. */
+      the width to the precision, or the string length if shorter. */
 
       else if (precision >= 0)
        width = precision < slen ? precision : slen;
 
       else if (precision >= 0)
        width = precision < slen ? precision : slen;