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