Header-wrap expansion. Bug 2843
authorJeremy Harris <jgh146exb@wizmail.org>
Sat, 11 Mar 2023 17:48:28 +0000 (17:48 +0000)
committerJeremy Harris <jgh146exb@wizmail.org>
Sat, 11 Mar 2023 17:49:54 +0000 (17:49 +0000)
doc/doc-docbook/spec.xfpt
doc/doc-txt/NewStuff
src/src/deliver.c
src/src/expand.c
src/src/functions.h
src/src/header.c
test/scripts/0000-Basic/0002
test/stdout/0002
test/stdout/0998

index 1367cc6f2eb4ad4e5ef25c784db3e0490475d095..8e2e1d142317387906dd0a0aa099ad8a18cb6f21 100644 (file)
@@ -11060,6 +11060,24 @@ abbreviation &%h%& can be used when &%hash%& is used as an operator.
 
 
 
+.new
+.vitem &*${headerwrap_*&<&'cols'&>&*_*&<&'limit'&>&*:*&<&'string'&>&*}*&
+.cindex header "wrapping operator"
+.cindex expansion "header wrapping"
+This operator line-wraps its argument in a way useful for headers.
+The &'cols'& value gives the column number to wrap after,
+the &'limit'& gives a limit number of result characters to truncate at.
+Either just the &'limit'& and the preceding underbar, or both, can be omitted;
+the defaults are 80 and 998.
+Wrapping will be inserted at a space if possible before the
+column number is reached.
+Whitespace at a chosen wrap point is removed.
+A line-wrap consists of a newline followed by a tab,
+and the tab is counted as 8 columns.
+.wen
+
+
+
 .vitem &*${hex2b64:*&<&'hexstring'&>&*}*&
 .cindex "base64 encoding" "conversion from hex"
 .cindex "expansion" "hex to base64"
index 3143e476604c35118f44c759e09f71fc1780a11e..ba204c040761b64d94b02114eff994cb7f5e3115 100644 (file)
@@ -25,6 +25,8 @@ Version 4.97
 
  8. New utility exim_msgdate converts message-ids to human readable format.
 
+ 9. An expansion operator for wrapping long header lines.
+
 Version 4.96
 ------------
 
index 9b77b36197d16798371de1647e1f738f51df3ac3..e2994b116615ab4a074e50472e27a7a88ae4bcbf 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) The Exim Maintainers 2020 - 2022 */
+/* Copyright (c) The Exim Maintainers 2020 - 2023 */
 /* 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 */
@@ -5566,69 +5566,14 @@ Limit to about 1024 chars total. */
 static void
 dsn_put_wrapped(FILE * fp, const uschar * header, const uschar * s)
 {
-const uschar * t;
-int llen = fprintf(fp, "%s", CS header), sleft = Ustrlen(s);
-int remain = 1022 - llen;
+gstring * g = string_cat(NULL, header);
 
-if (*s && remain > 0)
-  {
-  for(;;)
-    {
-    unsigned ltail;    /* source chars to skip */
-
-    /* Chop at a newline, or end of string */
-
-    if ((t = Ustrchr(s, '\\')) && t[1] == 'n')
-      ltail = 2;
-    else if ((t = Ustrchr(s, '\n')))
-      ltail = 1;
-    else
-      {
-      t = s + sleft;
-      ltail = 0;
-      }
-
-    /* If that is too long, search backward for a space */
-
-    if ((llen + t - s) > 78)
-      {
-      const uschar * u;
-      for (u = s + 78 - llen; u > s + 10; --u) if (*u == ' ') break;
-      if (u > s + 10)
-       {                               /* found a space to linebreak at */
-       llen = u - s;
-       remain -= fprintf(fp, "%.*s", (int)llen, s);
-       s += ++llen;                    /* skip the space also */
-       }
-      else if (llen < 78)
-       {                               /* just linebreak at 78 */
-       llen = 78 - llen;
-       remain -= fprintf(fp, "%.*s", llen, s);
-       s += llen;
-       }
-      else                             /* header rather long */
-       llen = 0;
-      }
-    else
-      {
-      llen = t - s;
-      remain -= fprintf(fp, "%.*s", llen, s);
-      s = t + ltail;
-      }
+g = string_cat(g, s);
+gstring_release_unused(g);
+fprintf(fp, "%s\n", wrap_header(string_from_gstring(g), 79, 1023, US" ", 1));
+}
 
-    sleft -= llen;
-    remain -= 2;
-    if (!*s || remain <= 0)
-      break;
-    fputs("\n ", fp);
-    llen = 1;          /* one for the leading space output above */
-    }
 
-  if (s[-1] != '\n') fputs("\n", fp);
-  }
-else
-  fputs("\n", fp);
-}
 
 
 /*************************************************
index baf7134cdc2b79848cd323d7ae154cf7f310935f..6dcd450628d8d7c9599b224642af70551bb52a3d 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) The Exim Maintainers 2020 - 2022 */
+/* Copyright (c) The Exim Maintainers 2020 - 2023 */
 /* 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 */
@@ -237,6 +237,7 @@ static uschar *op_table_main[] = {
   US"expand",
   US"h",
   US"hash",
+  US"headerwrap",
   US"hex2b64",
   US"hexquote",
   US"ipv6denorm",
@@ -284,6 +285,7 @@ enum {
   EOP_EXPAND,
   EOP_H,
   EOP_HASH,
+  EOP_HEADERWRAP,
   EOP_HEX2B64,
   EOP_HEXQUOTE,
   EOP_IPV6DENORM,
@@ -7262,7 +7264,7 @@ NOT_ITEM: ;
        {
        uschar *t;
        unsigned long int n = Ustrtoul(sub, &t, 10);
-       if (*t != 0)
+       if (*t)
          {
          expand_string_message = string_sprintf("argument for base62 "
            "operator is \"%s\", which is not a decimal number", sub);
@@ -7278,7 +7280,7 @@ NOT_ITEM: ;
        {
        uschar *tt = sub;
        unsigned long int n = 0;
-       while (*tt != 0)
+       while (*tt)
          {
          uschar *t = Ustrchr(base62_chars, *tt++);
          if (!t)
@@ -7428,6 +7430,29 @@ NOT_ITEM: ;
        goto EXPAND_FAILED;
 #endif
 
+      /* Line-wrap a string as if it is a header line */
+
+      case EOP_HEADERWRAP:
+       {
+       unsigned col = 80, lim = 998;
+       uschar * s;
+
+       if (arg)
+         {
+         const uschar * list = arg;
+         int sep = '_';
+         if ((s = string_nextinlist(&list, &sep, NULL, 0)))
+           {
+           col = atoi(CS s);
+           if ((s = string_nextinlist(&list, &sep, NULL, 0)))
+             lim = atoi(CS s);
+           }
+         }
+         if ((s =  wrap_header(sub, col, lim, US"\t", 8)))
+           yield = string_cat(yield, s);
+       }
+       break;
+
       /* Convert hex encoding to base64 encoding */
 
       case EOP_HEX2B64:
index 37f0a57bc6e8412ae73f75da03c9e44c4d34826c..5fbb426eca7517ca2fc91e9dc3707ea05b533301 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) The Exim Maintainers 2020 - 2022 */
+/* Copyright (c) The Exim Maintainers 2020 - 2023 */
 /* 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 */
@@ -678,6 +678,7 @@ extern void    version_init(void);
 
 extern BOOL    write_chunk(transport_ctx *, uschar *, int);
 extern ssize_t write_to_fd_buf(int, const uschar *, size_t);
+extern uschar *wrap_header(const uschar *, unsigned, unsigned, const uschar *, unsigned);
 
 
 /******************************************************************************/
index a4dd6e72e487acee1bd5e5c2f3c58e28b4db0c54..e2b3d8a9c2c9f92d0c5f69efe1682adc5704313d 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) The Exim Maintainers 2020 - 2022 */
+/* Copyright (c) The Exim Maintainers 2020 - 2023 */
 /* Copyright (c) University of Cambridge 1995 - 2016 */
 /* See the file NOTICE for conditions of use and distribution. */
 /* SPDX-License-Identifier: GPL-2.0-or-later */
@@ -466,4 +466,85 @@ va_end(ap);
 return !cond;
 }
 
+
+
+/* Wrap and truncate a string for use as a header.
+Convert either the sequence "\n" or a real newline into newline plus indent.
+If that still takes us past the column limit, look for the last space
+and split there too.
+Limit to the given max total char count.
+
+Return: string or NULL */
+
+uschar *
+wrap_header(const uschar * s, unsigned cols, unsigned maxchars,
+  const uschar * indent, unsigned indent_cols)
+{
+gstring * g = NULL;
+
+if (maxchars == 0) maxchars = INT_MAX;
+if (cols == 0) cols = INT_MAX;
+
+if (s && *s)
+  {
+  int sleft = Ustrlen(s);
+  for(unsigned llen = 0; ; llen = indent_cols)
+    {
+    const uschar * t;
+    unsigned ltail = 0, glen;
+
+    if ((t = Ustrchr(s, '\\')) && t[1] == 'n')
+      ltail = 2;
+    else if ((t = Ustrchr(s, '\n')))
+      ltail = 1;
+    else
+      t = s + sleft;
+
+    if ((llen + t - s) > cols)         /* more than a linesworth of s */
+      {                                        /* look backward for whitespace */
+      for (const uschar * u = s + cols - llen; u > s + 10; --u) if (isspace(*u))
+       {
+       llen = u - s;
+       while (u > s+1 && isspace(u[-1])) --u;  /* find start of whitespace */
+       g = string_catn(g, s, u - s);
+       s += ++llen;                            /* skip the space */
+       while (*s && isspace(*s))               /* and any trailing */
+         s++, llen++;
+       goto LDONE;
+       }
+                                       /* no whitespace */
+      if (llen < cols)
+       {                                       /* just linebreak at 80 */
+       llen = cols - llen;
+       g = string_catn(g, s, llen);
+       s += llen;
+       }
+      else
+        llen = 0;
+      LDONE:
+      }
+    else                               /* rest of s fits in line */
+      {
+      llen = t - s;
+      g = string_catn(g, s, llen);
+      s = t + ltail;
+      }
+
+    if (!*s)
+      break;                           /* no trailing linebreak */
+    if ((glen = gstring_length(g)) >= maxchars)
+      {
+      gstring_trim(g, glen - maxchars);
+      break;                           /* no trailing linebreak */
+      }
+    sleft -= llen;
+    g = string_catn(g, US"\n", 1);
+    g = string_catn(g, indent, 1);
+    }
+  }
+gstring_release_unused(g);
+return string_from_gstring(g);
+}
+
+
 /* End of header.c */
index 5ddc9b678f878e89528047dbb86c7ebcf8cca721..58ec292509345961301facd4885e740fe41e5892 100644 (file)
@@ -206,6 +206,20 @@ hex2b64:${hex2b64:1a2b3c4d5e6g}
 hex2b64:${hex2b64:${md5:the quick brown fox}}
 hex2b64:${hex2b64:${sha1:the quick brown fox}}
 
+headerwrap:${headerwrap:}
+headerwrap:${headerwrap:a}
+headerwrap:${headerwrap:ab}
+headerwrap:${headerwrap:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaz}
+headerwrap_79:${headerwrap_79:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaz}
+headerwrap:${headerwrap:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab}
+headerwrap:${headerwrap:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaz Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab}
+headerwrap:${headerwrap:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaz  Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab}
+headerwrap:${headerwrap:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaz Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbz}
+headerwrap:${headerwrap:123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789(100).6789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789(200).6789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789(300).678901234567890123456789012345678901234567890123456789012345678901234567890123456789(400).67890123456789012345678901234567890123456789012345678901234567890123456789012345\
+67890123456789(500).678901234567890123456789012345678901234567890123456789012345678901234567890123456789(600).6789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789(700).6789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789(800).6789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789(900).6789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789(1000).789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789(100).67890123456789}
+headerwrap_81_100:${headerwrap_81_100:123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789(100).6789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789(200).6789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789(300).678901234567890123456789012345678901234567890123456789012345678901234567890123456789(400).67890123456789012345678901234567890123456789012345678901234567890123456789012345\
+67890123456789(500).678901234567890123456789012345678901234567890123456789012345678901234567890123456789(600).6789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789(700).6789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789(800).6789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789(900).6789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789(1000).789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789(100).67890123456789}
+
 base32: 0  <${base32:0}>
 base32: 1  <${base32:1}>
 base32: 31 <${base32:31}>
index d7e76e6c4be47ccd90fba5387e737af8ed269d96..1da46e7a09292f45d393aaa1facde2d95fcb8429 100644 (file)
@@ -197,6 +197,38 @@ newline    tab\134backslash ~tilde\177DEL\200\201.
 > hex2b64:MPPJPkZDbetYunCBao7BJA==
 > hex2b64:ztcfpyNSMb7Tg/rP3EHE3cwi7PE=
 > 
+> headerwrap:
+> headerwrap:a
+> headerwrap:ab
+> headerwrap:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaz
+> headerwrap_79:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+       z
+> headerwrap:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+       b
+> headerwrap:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaz
+       Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab
+> headerwrap:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaz
+       Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab
+> headerwrap:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaz
+       Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+       bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbz
+> headerwrap:12345678901234567890123456789012345678901234567890123456789012345678901234567890
+       1234567890123456789(100).67890123456789012345678901234567890123456789012
+       34567890123456789012345678901234567890123456789(200).6789012345678901234
+       567890123456789012345678901234567890123456789012345678901234567890123456
+       789(300).678901234567890123456789012345678901234567890123456789012345678
+       901234567890123456789(400).678901234567890123456789012345678901234567890
+       1234567890123456789012345678901234567890123456789(500).67890123456789012
+       3456789012345678901234567890123456789012345678901234567890123456789(600)
+       .67890123456789012345678901234567890123456789012345678901234567890123456
+       78901234567890123456789(700).6789012345678901234567890123456789012345678
+       901234567890123456789012345678901234567890123456789(800).678901234567890
+       123456789012345678901234567890123456789012345678901234567890123456789012
+       3456789(900).67890123456789012345678901234567890123456789012345678901234
+       5678901234567890123456789012
+> headerwrap_81_100:123456789012345678901234567890123456789012345678901234567890123456789012345678901
+       23456789012345678
+> 
 > base32: 0  <>
 > base32: 1  <b>
 > base32: 31 <7>
index 3d494c0c5120df083f44bf39ba0e57650be9d93a..0b70fd0f6372ed82442a0ca60618645c905fd251 100644 (file)
@@ -69,16 +69,16 @@ X-Exim-Diagnostic: X-str; SMTP error from remote mail server after RCPT
  550-123456789 100       123456789 a really long line to blow the limits
  123456789 123456789 123456789 123456789 200       123456789 123456789
  123456789 123456789 123456789 123456789 123456789 123456789 123456789 300
  123456789 123456789 123456789 123456789 123456789 123456789 123456789
+ 123456789 123456789 123456789 123456789 123456789 123456789 123456789
  123456789 123456789 400       123456789 123456789 123456789 123456789
  123456789 123456789 123456789 123456789 123456789 500       123456789
  123456789 123456789 123456789 123456789 123456789 123456789 123456789
  123456789 600       123456789 123456789 123456789 123456789 123456789
  123456789 123456789 123456789 123456789 700       123456789 123456789
  123456789 123456789 123456789 123456789 123456789 123456789 123456789 800
  123456789 123456789 123456789 123456789 123456789 123456789 123456789
+ 123456789 123456789 123456789 123456789 123456789 123456789 123456789
  123456789 123456789 900       123456789 123456789 123456789 123456789
- 123456789 123456789 123456789 123456789 123456789 1000      123456789 12
+ 123456789 123456789 123456789 123456789 123456
 Diagnostic-Code: smtp; 550-no mate
  550-123456789 100       123456789 a really long line to blow the limits   123456789 123456789 123456789 123456789 200       123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 300       123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 400       123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 500       123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 600       123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 700       123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 800       123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 900       123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 1[truncated]