File: quotedprint.go

package info (click to toggle)
golang-github-jhillyerd-enmime 0.9.3-5
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,156 kB
  • sloc: makefile: 25; sh: 16
file content (151 lines) | stat: -rw-r--r-- 3,592 bytes parent folder | download | duplicates (2)
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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
package coding

import (
	"bufio"
	"fmt"
	"io"
)

// QPCleaner scans quoted printable content for invalid characters and encodes them so that
// Go's quoted-printable decoder does not abort with an error.
type QPCleaner struct {
	in       *bufio.Reader
	overflow []byte
	lineLen  int
}

// MaxQPLineLen is the maximum line length we allow before inserting `=\r\n`.  Prevents buffer
// overflows in mime/quotedprintable.Reader.
const MaxQPLineLen = 1024

var (
	_ io.Reader = &QPCleaner{} // Assert QPCleaner implements io.Reader.

	escapedEquals = []byte("=3D") // QP encoded value of an equals sign.
	lineBreak     = []byte("=\r\n")
)

// NewQPCleaner returns a QPCleaner for the specified reader.
func NewQPCleaner(r io.Reader) *QPCleaner {
	return &QPCleaner{
		in:       bufio.NewReader(r),
		overflow: nil,
		lineLen:  0,
	}
}

// Read method for io.Reader interface.
func (qp *QPCleaner) Read(dest []byte) (n int, err error) {
	destLen := len(dest)

	if len(qp.overflow) > 0 {
		// Copy bytes that didn't fit into dest buffer during previous read.
		n = copy(dest, qp.overflow)
		qp.overflow = qp.overflow[n:]
	}

	// writeByte outputs a single byte, space for which will have already been ensured by the loop
	// condition. Updates counters.
	writeByte := func(in byte) {
		dest[n] = in
		n++
		qp.lineLen++
	}

	// writeBytes outputs multiple bytes, storing overflow for next read. Updates counters.
	writeBytes := func(in []byte) {
		nc := copy(dest[n:], in)
		if nc < len(in) {
			// Stash unwritten bytes into overflow.
			qp.overflow = append(qp.overflow, []byte(in[nc:])...)
		}
		n += nc
		qp.lineLen += len(in)
	}

	// ensureLineLen ensures there is room to write `requested` bytes, preventing a line break being
	// inserted in the middle of the escaped string.  The requested count is in addition to the
	// byte that was already reserved for this loop iteration.
	ensureLineLen := func(requested int) {
		if qp.lineLen+requested >= MaxQPLineLen {
			writeBytes(lineBreak)
			qp.lineLen = 0
		}
	}

	// Loop over bytes in qp.in ByteReader while there is space in dest.
	for n < destLen {
		var b byte
		b, err = qp.in.ReadByte()
		if err != nil {
			return n, err
		}

		if qp.lineLen >= MaxQPLineLen {
			writeBytes(lineBreak)
			qp.lineLen = 0
			if n == destLen {
				break
			}
		}

		switch {
		// Pass valid hex bytes through, otherwise escapes the equals symbol.
		case b == '=':
			ensureLineLen(2)

			var hexBytes []byte
			hexBytes, err = qp.in.Peek(2)
			if err != nil && err != io.EOF {
				return 0, err
			}
			if validHexBytes(hexBytes) {
				dest[n] = b
				n++
			} else {
				writeBytes(escapedEquals)
			}

		// Valid special character.
		case b == '\t':
			writeByte(b)

		// Valid special characters that reset line length.
		case b == '\r' || b == '\n':
			writeByte(b)
			qp.lineLen = 0

		// Invalid characters, render as quoted-printable.
		case b < ' ' || '~' < b:
			ensureLineLen(2)
			writeBytes([]byte(fmt.Sprintf("=%02X", b)))

		// Acceptable characters.
		default:
			writeByte(b)
		}
	}

	return n, err
}

func validHexByte(b byte) bool {
	return '0' <= b && b <= '9' || 'A' <= b && b <= 'F' || 'a' <= b && b <= 'f'
}

// validHexBytes returns true if this byte sequence represents a valid quoted-printable escape
// sequence or line break, minus the initial equals sign.
func validHexBytes(v []byte) bool {
	if len(v) > 0 && v[0] == '\n' {
		// Soft line break.
		return true
	}
	if len(v) < 2 {
		return false
	}
	if v[0] == '\r' && v[1] == '\n' {
		// Soft line break.
		return true
	}
	return validHexByte(v[0]) && validHexByte(v[1])
}