aboutsummaryrefslogtreecommitdiff
path: root/pkg/rfc822/writer.go
blob: 07751ea066fb786142cbb740e3e1ca67dfb091fc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
// Package rfc822 provides a writer that ensures the intrinsics of RFC 822.
//
// Rationale
//
// Cyrus IMAP really cares about the hard specifics of RFC 822, namely not allowing single \r and \n.
//
// See also: https://www.cyrusimap.org/imap/reference/faqs/interop-barenewlines.html
// and: https://github.com/Necoro/feed2imap-go/issues/46
//
// NB: This package currently only cares about the newlines.
package rfc822

import "io"

type rfc822Writer struct {
	w io.Writer
}

var lf = []byte{'\n'}
var cr = []byte{'\r'}

func (f rfc822Writer) Write(p []byte) (n int, err error) {
	crFound := false
	start := 0

	write := func(str []byte) {
		var j int
		j, err = f.w.Write(str)
		n = n + j
	}

	for idx, b := range p {
		if crFound && b != '\n' {
			// insert '\n'
			if write(p[start:idx]); err != nil {
				return
			}
			if write(lf); err != nil {
				return
			}

			start = idx
		} else if !crFound && b == '\n' {
			// insert '\r'
			if write(p[start:idx]); err != nil {
				return
			}
			if write(cr); err != nil {
				return
			}

			start = idx
		}
		crFound = b == '\r'
	}

	// write the remainder
	if write(p[start:]); err != nil {
		return
	}

	if crFound { // dangling \r
		write(lf)
	}

	return
}

// Writer creates a new RFC 822 conform writer.
func Writer(w io.Writer) io.Writer {
	return rfc822Writer{w}
}