1package message
2
3import (
4 "bytes"
5 "fmt"
6 "strings"
7)
8
9// HeaderWriter helps create headers, folding to the next line when it would
10// become too large. Useful for creating Received and DKIM-Signature headers.
11type HeaderWriter struct {
12 b *strings.Builder
13 lineLen int
14 nonfirst bool
15}
16
17// Addf formats the string and calls Add.
18func (w *HeaderWriter) Addf(separator string, format string, args ...any) {
19 w.Add(separator, fmt.Sprintf(format, args...))
20}
21
22// Add adds texts, each separated by separator. Individual elements in text are
23// not wrapped.
24func (w *HeaderWriter) Add(separator string, texts ...string) {
25 if w.b == nil {
26 w.b = &strings.Builder{}
27 }
28 for _, text := range texts {
29 n := len(text)
30 if w.nonfirst && w.lineLen > 1 && w.lineLen+len(separator)+n > 78 {
31 w.b.WriteString("\r\n\t")
32 w.lineLen = 1
33 } else if w.nonfirst && separator != "" {
34 w.b.WriteString(separator)
35 w.lineLen += len(separator)
36 }
37 w.b.WriteString(text)
38 w.lineLen += len(text)
39 w.nonfirst = true
40 }
41}
42
43// AddWrap adds data. If text is set, wrapping happens at space/tab, otherwise
44// anywhere in the buffer (e.g. for base64 data).
45func (w *HeaderWriter) AddWrap(buf []byte, text bool) {
46 for len(buf) > 0 {
47 line := buf
48 n := 78 - w.lineLen
49 if len(buf) > n {
50 if text {
51 if i := bytes.LastIndexAny(buf[:n], " \t"); i > 0 {
52 n = i
53 } else if i = bytes.IndexAny(buf, " \t"); i > 0 {
54 n = i
55 }
56 }
57 line, buf = buf[:n], buf[n:]
58 } else {
59 buf = nil
60 n = len(buf)
61 }
62 w.b.Write(line)
63 w.lineLen += n
64 if len(buf) > 0 {
65 w.b.WriteString("\r\n\t")
66 w.lineLen = 1
67 }
68 }
69}
70
71// Newline starts a new line.
72func (w *HeaderWriter) Newline() {
73 w.b.WriteString("\r\n\t")
74 w.lineLen = 1
75 w.nonfirst = true
76}
77
78// String returns the header in string form, ending with \r\n.
79func (w *HeaderWriter) String() string {
80 return w.b.String() + "\r\n"
81}
82