+ case 'Y': /* gstring pointer */
+ {
+ gstring * zg = va_arg(ap, gstring *);
+ if (zg) { s = CS zg->s; slen = gstring_length(zg); }
+ else { s = null; slen = Ustrlen(s); }
+ goto INSERT_GSTRING;
+ }
+#ifndef COMPILE_UTILITY
+ 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 *);
+ if (IS_DEBUG(D_noutf8))
+ for ( ; *s; s++)
+ zg = string_catn(zg, CUS (*s == 'K' ? "|" : s), 1);
+ else
+ for ( ; *s; s++) switch (*s)
+ {
+ case '\\': zg = string_catn(zg, US UTF8_UP_RIGHT, 3); break;
+ case '/': zg = string_catn(zg, US UTF8_DOWN_RIGHT, 3); break;
+ case '-':
+ case '_': zg = string_catn(zg, US UTF8_HORIZ, 3); break;
+ case '|': zg = string_catn(zg, US UTF8_VERT, 3); break;
+ case 'K': zg = string_catn(zg, US UTF8_VERT_RIGHT, 3); break;
+ case '<': zg = string_catn(zg, US UTF8_LEFT_TRIANGLE, 3); break;
+ case '>': zg = string_catn(zg, US UTF8_RIGHT_TRIANGLE, 3); break;
+ default: zg = string_catn(zg, CUS s, 1); break;
+ }
+
+ if (!zg)
+ break;
+ s = CS zg->s;
+ slen = gstring_length(zg);
+ goto INSERT_GSTRING;
+ }
+
+ case 'W': /* Maybe mark up ctrls, spaces & newlines */
+ s = va_arg(ap, char *);
+ if (s && !IS_DEBUG(D_noutf8))
+ {
+ gstring * zg = NULL;
+ int p = precision;
+
+ /* 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);
+ if (precision >= 0) precision += 2;
+ break;
+ case '\n':
+ zg = string_catn(zg, CUS UTF8_L_ARROW_HOOK "\n", 4);
+ if (precision >= 0) precision += 3;
+ break;
+ default:
+ 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;
+ }
+ if (zg) { s = CS zg->s; slen = gstring_length(zg); }
+ else { s = ""; slen = 0; }
+ }
+ else
+ {
+ if (!s) s = null;
+ slen = Ustrlen(s);
+ }
+ 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