Skip to content

Commit 83dff18

Browse files
authored
feat: encoding options added (#359)
1 parent 50133b8 commit 83dff18

File tree

4 files changed

+67
-4
lines changed

4 files changed

+67
-4
lines changed

encode.go

+9-4
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ func (p *Part) setupMIMEHeaders() transferEncoding {
117117
} else {
118118
cte = teBase64
119119
if p.TextContent() && p.ContentReader == nil {
120-
cte = selectTransferEncoding(p.Content, false)
120+
cte = p.selectTransferEncoding(p.Content, false)
121121
if p.Charset == "" {
122122
p.Charset = utf8
123123
}
@@ -144,7 +144,7 @@ func (p *Part) setupMIMEHeaders() transferEncoding {
144144
p.Header.Set(hnContentID, coding.ToIDHeader(p.ContentID))
145145
}
146146
fileName := p.FileName
147-
switch selectTransferEncoding([]byte(p.FileName), true) {
147+
switch p.selectTransferEncoding([]byte(p.FileName), true) {
148148
case teBase64:
149149
fileName = mime.BEncoding.Encode(utf8, p.FileName)
150150
case teQuoted:
@@ -195,7 +195,7 @@ func (p *Part) encodeHeader(b *bufio.Writer) error {
195195
for _, v := range p.Header[k] {
196196
encv := v
197197
if !rawContent {
198-
switch selectTransferEncoding([]byte(v), true) {
198+
switch p.selectTransferEncoding([]byte(v), true) {
199199
case teBase64:
200200
encv = mime.BEncoding.Encode(utf8, v)
201201
case teQuoted:
@@ -303,10 +303,15 @@ func (p *Part) encodeContentFromReader(b *bufio.Writer) error {
303303
}
304304

305305
// selectTransferEncoding scans content for non-ASCII characters and selects 'b' or 'q' encoding.
306-
func selectTransferEncoding(content []byte, quoteLineBreaks bool) transferEncoding {
306+
func (p *Part) selectTransferEncoding(content []byte, quoteLineBreaks bool) transferEncoding {
307307
if len(content) == 0 {
308308
return te7Bit
309309
}
310+
311+
if p.encoder != nil && p.encoder.forceQuotedPrintableCteOption {
312+
return teQuoted
313+
}
314+
310315
// Binary chars remaining before we choose b64 encoding.
311316
threshold := b64Percent * len(content) / 100
312317
bincount := 0

encode_test.go

+16
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99

1010
"github.com/jhillyerd/enmime/v2"
1111
"github.com/jhillyerd/enmime/v2/internal/test"
12+
"github.com/stretchr/testify/assert"
1213
)
1314

1415
func TestEncodePartEmpty(t *testing.T) {
@@ -340,6 +341,21 @@ func TestEncodePartContentBinary(t *testing.T) {
340341
test.DiffGolden(t, b.Bytes(), "testdata", "encode", "part-bin-content.golden")
341342
}
342343

344+
func TestEncodePartWithForceQuotedPrintableCte(t *testing.T) {
345+
nonASCIIcontent := bytes.Repeat([]byte{byte(0x10)}, 10)
346+
p := enmime.NewPart("text/plain").WithEncoder(enmime.NewEncoder(enmime.ForceQuotedPrintableCte(true)))
347+
p.Content = nonASCIIcontent
348+
b := &bytes.Buffer{}
349+
err := p.Encode(b)
350+
if err != nil {
351+
t.Fatal(err)
352+
}
353+
354+
// Verify output is QP encoded.
355+
assert.Equal(t, "quoted-printable", p.Header.Get("Content-Transfer-Encoding"))
356+
assert.Contains(t, b.String(), "=10=10=10")
357+
}
358+
343359
func TestEncodeFileModDate(t *testing.T) {
344360
p := enmime.NewPart("text/plain")
345361
p.Content = []byte("¡Hola, señor! Welcome to MIME")

encoder_options.go

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package enmime
2+
3+
type EncoderOption interface {
4+
apply(p *Encoder)
5+
}
6+
7+
// Encoder implements MIME part encoding options
8+
type Encoder struct {
9+
forceQuotedPrintableCteOption bool
10+
}
11+
12+
// ForceQuotedPrintableCte forces "quoted-printable" transfer encoding when selecting Content Transfer Encoding, preventing the use of base64.
13+
func ForceQuotedPrintableCte(b bool) EncoderOption {
14+
return forceQuotedPrintableCteOption(b)
15+
}
16+
17+
type forceQuotedPrintableCteOption bool
18+
19+
func (o forceQuotedPrintableCteOption) apply(p *Encoder) {
20+
p.forceQuotedPrintableCteOption = bool(o)
21+
}
22+
23+
func NewEncoder(ops ...EncoderOption) *Encoder {
24+
e := Encoder{
25+
forceQuotedPrintableCteOption: false,
26+
}
27+
28+
for _, o := range ops {
29+
o.apply(&e)
30+
}
31+
32+
return &e
33+
}

part.go

+9
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ type Part struct {
5050
parser *Parser // Provides access to parsing options.
5151

5252
randSource rand.Source // optional rand for uuid boundary generation
53+
54+
encoder *Encoder // provides encoding options
5355
}
5456

5557
// NewPart creates a new Part object.
@@ -494,3 +496,10 @@ func parseParts(parent *Part, reader *bufio.Reader) error {
494496
}
495497
return nil
496498
}
499+
500+
func (p *Part) WithEncoder(e *Encoder) *Part {
501+
if e != nil {
502+
p.encoder = e
503+
}
504+
return p
505+
}

0 commit comments

Comments
 (0)