Skip to content

Commit

Permalink
MIMEHeader: use Go's net/textproto for public interface (#297)
Browse files Browse the repository at this point in the history
* MIMEHeader: use Go's net/textproto for public interface

* Use `textproto` prefix for the stdlib package where applicable

* Use inttp prefix in detect.go
  • Loading branch information
jhillyerd committed Aug 22, 2023
1 parent bb96b5a commit 17d8c7d
Show file tree
Hide file tree
Showing 6 changed files with 28 additions and 22 deletions.
2 changes: 1 addition & 1 deletion builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ import (
"math/rand"
"mime"
"net/mail"
"net/textproto"
"os"
"path/filepath"
"reflect"
"time"

"github.com/jhillyerd/enmime/internal/stringutil"
"github.com/jhillyerd/enmime/internal/textproto"
)

// MailBuilder facilitates the easy construction of a MIME message. Each manipulation method
Expand Down
15 changes: 8 additions & 7 deletions detect.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package enmime
import (
"strings"

"github.com/jhillyerd/enmime/internal/textproto"
inttp "github.com/jhillyerd/enmime/internal/textproto"
"github.com/jhillyerd/enmime/mediatype"
)

Expand Down Expand Up @@ -35,7 +35,7 @@ func detectMultipartMessage(root *Part, multipartWOBoundaryAsSinglepart bool) bo
// - Content-Disposition: attachment; filename="frog.jpg"
// - Content-Disposition: inline; filename="frog.jpg"
// - Content-Type: attachment; filename="frog.jpg"
func detectAttachmentHeader(header textproto.MIMEHeader) bool {
func detectAttachmentHeader(header inttp.MIMEHeader) bool {
mtype, params, _, _ := mediatype.Parse(header.Get(hnContentDisposition))
if strings.ToLower(mtype) == cdAttachment ||
(strings.ToLower(mtype) == cdInline && len(params) > 0) {
Expand All @@ -49,7 +49,7 @@ func detectAttachmentHeader(header textproto.MIMEHeader) bool {
// detectTextHeader returns true, if the the MIME headers define a valid 'text/plain' or 'text/html'
// part. If the emptyContentTypeIsPlain argument is set to true, a missing Content-Type header will
// result in a positive plain part detection.
func detectTextHeader(header textproto.MIMEHeader, emptyContentTypeIsText bool) bool {
func detectTextHeader(header inttp.MIMEHeader, emptyContentTypeIsText bool) bool {
ctype := header.Get(hnContentType)
if ctype == "" && emptyContentTypeIsText {
return true
Expand All @@ -67,23 +67,24 @@ func detectTextHeader(header textproto.MIMEHeader, emptyContentTypeIsText bool)

// detectBinaryBody returns true if the mail header defines a binary body.
func detectBinaryBody(root *Part) bool {
if detectTextHeader(root.Header, true) {
header := inttp.MIMEHeader(root.Header) // Use internal header methods.
if detectTextHeader(header, true) {
// It is text/plain, but an attachment.
// Content-Type: text/plain; name="test.csv"
// Content-Disposition: attachment; filename="test.csv"
// Check for attachment only, or inline body is marked
// as attachment, too.
mtype, _, _, _ := mediatype.Parse(root.Header.Get(hnContentDisposition))
mtype, _, _, _ := mediatype.Parse(header.Get(hnContentDisposition))
return strings.ToLower(mtype) == cdAttachment
}

isBin := detectAttachmentHeader(root.Header)
isBin := detectAttachmentHeader(header)
if !isBin {
// This must be an attachment, if the Content-Type is not
// 'text/plain' or 'text/html'.
// Example:
// Content-Type: application/pdf; name="doc.pdf"
mtype, _, _, _ := mediatype.Parse(root.Header.Get(hnContentType))
mtype, _, _, _ := mediatype.Parse(header.Get(hnContentType))
mtype = strings.ToLower(mtype)
if mtype != ctTextPlain && mtype != ctTextHTML {
return true
Expand Down
2 changes: 1 addition & 1 deletion encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import (
"io"
"mime"
"mime/quotedprintable"
"net/textproto"
"sort"
"time"

"github.com/jhillyerd/enmime/internal/coding"
"github.com/jhillyerd/enmime/internal/stringutil"
"github.com/jhillyerd/enmime/internal/textproto"
)

// b64Percent determines the percent of non-ASCII characters enmime will tolerate before switching
Expand Down
5 changes: 3 additions & 2 deletions envelope.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import (
"io"
"mime"
"net/mail"
"net/textproto"
"strings"
"time"

"github.com/jaytaylor/html2text"
"github.com/jhillyerd/enmime/internal/coding"
"github.com/jhillyerd/enmime/internal/textproto"
inttp "github.com/jhillyerd/enmime/internal/textproto"
"github.com/jhillyerd/enmime/mediatype"

"github.com/pkg/errors"
Expand Down Expand Up @@ -58,7 +59,7 @@ func (e *Envelope) GetHeaderValues(name string) []string {
return []string{}
}

rawValues := (*e.header)[textproto.CanonicalEmailMIMEHeaderKey(name)]
rawValues := (*e.header)[inttp.CanonicalEmailMIMEHeaderKey(name)]
values := make([]string, 0, len(rawValues))
for _, v := range rawValues {
values = append(values, coding.DecodeExtHeader(v))
Expand Down
15 changes: 8 additions & 7 deletions header.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ import (
"fmt"
"mime"
"net/mail"
"net/textproto"
"strings"

"github.com/jhillyerd/enmime/internal/coding"
"github.com/jhillyerd/enmime/internal/stringutil"
"github.com/jhillyerd/enmime/internal/textproto"
inttp "github.com/jhillyerd/enmime/internal/textproto"
"github.com/jhillyerd/enmime/mediatype"

"github.com/pkg/errors"
Expand Down Expand Up @@ -123,7 +124,7 @@ func ParseMediaType(ctype string) (mtype string, params map[string]string, inval
func readHeader(r *bufio.Reader, p *Part) (textproto.MIMEHeader, error) {
// buf holds the massaged output for textproto.Reader.ReadMIMEHeader()
buf := &bytes.Buffer{}
tp := textproto.NewReader(r)
tp := inttp.NewReader(r)
firstHeader := true
line:
for {
Expand All @@ -139,7 +140,7 @@ line:
if firstSpace == 0 {
// Starts with space: continuation
buf.WriteByte(' ')
buf.Write(textproto.TrimBytes(s))
buf.Write(inttp.TrimBytes(s))
continue
}
if firstColon == 0 {
Expand All @@ -160,7 +161,7 @@ line:
// Behavior change in net/textproto package in Golang 1.20: invalid characters
// in header keys are no longer allowed; https://github.com/golang/go/issues/53188
for _, c := range s[:firstColon] {
if c != ' ' && !textproto.ValidEmailHeaderFieldByte(c) {
if c != ' ' && !inttp.ValidEmailHeaderFieldByte(c) {
p.addError(
ErrorMalformedHeader, "Header name %q contains invalid character %q", s, c)
continue line
Expand All @@ -173,7 +174,7 @@ line:
buf.Write([]byte{'\r', '\n'})
}

s = textproto.TrimBytes(s)
s = inttp.TrimBytes(s)
buf.Write(s)
firstHeader = false
} else {
Expand All @@ -192,9 +193,9 @@ line:
}

buf.Write([]byte{'\r', '\n'})
tr := textproto.NewReader(bufio.NewReader(buf))
tr := inttp.NewReader(bufio.NewReader(buf))
header, err := tr.ReadEmailMIMEHeader()
return header, errors.WithStack(err)
return textproto.MIMEHeader(header), errors.WithStack(err)
}

// decodeToUTF8Base64Header decodes a MIME header per RFC 2047, reencoding to =?utf-8b?
Expand Down
11 changes: 7 additions & 4 deletions part.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ import (
"io"
"math/rand"
"mime/quotedprintable"
"net/textproto"
"strconv"
"strings"
"time"

"github.com/gogs/chardet"
"github.com/jhillyerd/enmime/internal/coding"
"github.com/jhillyerd/enmime/internal/textproto"
inttp "github.com/jhillyerd/enmime/internal/textproto"
"github.com/jhillyerd/enmime/mediatype"

"github.com/pkg/errors"
Expand Down Expand Up @@ -115,7 +116,7 @@ func (p *Part) setupHeaders(r *bufio.Reader, defaultContentType string) error {
if err != nil {
return err
}
p.Header = header
p.Header = textproto.MIMEHeader(header)
ctype := header.Get(hnContentType)
if ctype == "" {
if defaultContentType == "" {
Expand Down Expand Up @@ -146,8 +147,9 @@ func (p *Part) setupHeaders(r *bufio.Reader, defaultContentType string) error {
// setupContentHeaders uses Content-Type media params and Content-Disposition headers to populate
// the disposition, filename, and charset fields.
func (p *Part) setupContentHeaders(mediaParams map[string]string) {
header := inttp.MIMEHeader(p.Header)
// Determine content disposition, filename, character set.
disposition, dparams, _, err := mediatype.Parse(p.Header.Get(hnContentDisposition))
disposition, dparams, _, err := mediatype.Parse(header.Get(hnContentDisposition))
if err == nil {
// Disposition is optional
p.Disposition = disposition
Expand Down Expand Up @@ -269,12 +271,13 @@ func (p *Part) convertFromStatedCharset(r io.Reader) io.Reader {
// placing the result into Part.Content. IO errors will be returned immediately; other errors
// and warnings will be added to Part.Errors.
func (p *Part) decodeContent(r io.Reader, readPartErrorPolicy ReadPartErrorPolicy) error {
header := inttp.MIMEHeader(p.Header)
// contentReader will point to the end of the content decoding pipeline.
contentReader := r
// b64cleaner aggregates errors, must maintain a reference to it to get them later.
var b64cleaner *coding.Base64Cleaner
// Build content decoding reader.
encoding := p.Header.Get(hnContentEncoding)
encoding := header.Get(hnContentEncoding)
validEncoding := true
switch strings.ToLower(encoding) {
case cteQuotedPrintable:
Expand Down

0 comments on commit 17d8c7d

Please sign in to comment.