Skip to content

Commit

Permalink
SMTP: Unifying handling of encoded message bodies
Browse files Browse the repository at this point in the history
  • Loading branch information
Lucas Hinderberger committed Jun 25, 2024
1 parent ff377b9 commit 1876fe7
Showing 1 changed file with 32 additions and 3 deletions.
35 changes: 32 additions & 3 deletions internal/smtp/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,20 @@ package smtp

import (
"bytes"
"encoding/base64"
"errors"
"fmt"
"io"
"mime"
"mime/multipart"
"mime/quotedprintable"
"net/mail"
"net/textproto"
"regexp"
"strings"
"time"

"github.com/sirupsen/logrus"
)

// ReceivedMessage contains a single email message as received via SMTP.
Expand Down Expand Up @@ -62,7 +66,7 @@ func NewReceivedMessage(
return nil, fmt.Errorf("could not parse message: %w", err)
}

body, err := io.ReadAll(io.LimitReader(parsedMsg.Body, maxMessageSize))
body, err := io.ReadAll(wrapBodyReader(parsedMsg.Body, parsedMsg.Header, maxMessageSize))
if err != nil {
return nil, fmt.Errorf("could not read message body: %w", err)
}
Expand Down Expand Up @@ -99,7 +103,7 @@ func NewReceivedMessage(
r := multipart.NewReader(bytes.NewReader(msg.body), boundary)

for i := 0; ; i++ {
rawPart, err := r.NextPart()
rawPart, err := r.NextRawPart()
if err != nil {
if errors.Is(err, io.EOF) {
break
Expand Down Expand Up @@ -161,7 +165,7 @@ func NewReceivedPart(index int, p *multipart.Part, maxMessageSize int64) (*Recei
maxMessageSize = DefaultMaxMessageSize
}

body, err := io.ReadAll(io.LimitReader(p, maxMessageSize))
body, err := io.ReadAll(wrapBodyReader(p, p.Header, maxMessageSize))
if err != nil {
return nil, fmt.Errorf("could not read message part body: %w", err)
}
Expand All @@ -175,6 +179,31 @@ func NewReceivedPart(index int, p *multipart.Part, maxMessageSize int64) (*Recei
return part, nil
}

// wrapBodyReader wraps the reader for a message / part body with size
// limitation and quoted-printable/base64 decoding (the latter based on
// the Content-Transfer-Encoding header, if any is set).
func wrapBodyReader(r io.Reader, headers map[string][]string, maxMessageSize int64) io.Reader {
r = io.LimitReader(r, maxMessageSize)

enc, ok := headers["Content-Transfer-Encoding"]
if ok {
if len(enc) != 1 {
logrus.Error("Content-Transfer-Encoding must have exactly one value")
}

switch enc[0] {
case "base64":
r = base64.NewDecoder(base64.StdEncoding, r)
case "quoted-printable":
r = quotedprintable.NewReader(r)
default:
logrus.Errorf("encountered unknown Content-Transfer-Encoding %q", enc)
}
}

return r
}

// =======
// Getters
// =======
Expand Down

0 comments on commit 1876fe7

Please sign in to comment.