summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeremy Harris <jgh146exb@wizmail.org>2023-03-11 17:48:28 +0000
committerJeremy Harris <jgh146exb@wizmail.org>2023-03-11 17:49:54 +0000
commita6d90e094d4e2a84d18859cf2005d10c2020e2d4 (patch)
treeedd8c087315fbc59f8c05c9e6a1f7c585958401a
parentca8410e981982edd16bcc8689e09c2c15d8267e7 (diff)
downloadexim4-a6d90e094d4e2a84d18859cf2005d10c2020e2d4.tar.gz
Header-wrap expansion. Bug 2843
-rw-r--r--doc/doc-docbook/spec.xfpt18
-rw-r--r--doc/doc-txt/NewStuff2
-rw-r--r--src/src/deliver.c67
-rw-r--r--src/src/expand.c31
-rw-r--r--src/src/functions.h3
-rw-r--r--src/src/header.c83
-rw-r--r--test/scripts/0000-Basic/000214
-rw-r--r--test/stdout/000232
-rw-r--r--test/stdout/09986
9 files changed, 187 insertions, 69 deletions
diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt
index 1367cc6f2..8e2e1d142 100644
--- a/doc/doc-docbook/spec.xfpt
+++ b/doc/doc-docbook/spec.xfpt
@@ -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"
diff --git a/doc/doc-txt/NewStuff b/doc/doc-txt/NewStuff
index 3143e4766..ba204c040 100644
--- a/doc/doc-txt/NewStuff
+++ b/doc/doc-txt/NewStuff
@@ -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
------------
diff --git a/src/src/deliver.c b/src/src/deliver.c
index 9b77b3619..e2994b116 100644
--- a/src/src/deliver.c
+++ b/src/src/deliver.c
@@ -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);
-}
/*************************************************
diff --git a/src/src/expand.c b/src/src/expand.c
index baf7134cd..6dcd45062 100644
--- a/src/src/expand.c
+++ b/src/src/expand.c
@@ -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:
diff --git a/src/src/functions.h b/src/src/functions.h
index 37f0a57bc..5fbb426ec 100644
--- a/src/src/functions.h
+++ b/src/src/functions.h
@@ -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);
/******************************************************************************/
diff --git a/src/src/header.c b/src/src/header.c
index a4dd6e72e..e2b3d8a9c 100644
--- a/src/src/header.c
+++ b/src/src/header.c
@@ -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 */
diff --git a/test/scripts/0000-Basic/0002 b/test/scripts/0000-Basic/0002
index 5ddc9b678..58ec29250 100644
--- a/test/scripts/0000-Basic/0002
+++ b/test/scripts/0000-Basic/0002
@@ -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}>
diff --git a/test/stdout/0002 b/test/stdout/0002
index d7e76e6c4..1da46e7a0 100644
--- a/test/stdout/0002
+++ b/test/stdout/0002
@@ -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>
diff --git a/test/stdout/0998 b/test/stdout/0998
index 3d494c0c5..0b70fd0f6 100644
--- a/test/stdout/0998
+++ b/test/stdout/0998
@@ -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]