Files
postmoogle/vendor/github.com/jhillyerd/enmime/internal/coding/headerext.go
2022-11-16 12:08:51 +02:00

136 lines
2.8 KiB
Go

package coding
import (
"fmt"
"io"
"mime"
"strings"
)
// NewExtMimeDecoder creates new MIME word decoder which allows decoding of additional charsets.
func NewExtMimeDecoder() *mime.WordDecoder {
return &mime.WordDecoder{
CharsetReader: NewCharsetReader,
}
}
// DecodeExtHeader decodes a single line (per RFC 2047, aka Message Header Extensions) using Golang's
// mime.WordDecoder.
func DecodeExtHeader(input string) string {
if !strings.Contains(input, "=?") {
// Don't scan if there is nothing to do here
return input
}
header, err := NewExtMimeDecoder().DecodeHeader(input)
if err != nil {
return input
}
return header
}
// RFC2047Decode returns a decoded string if the input uses RFC2047 encoding, otherwise it will
// return the input.
//
// RFC2047 Example: `=?UTF-8?B?bmFtZT0iw7DCn8KUwoo=?=`
func RFC2047Decode(s string) string {
// Convert CR/LF to spaces.
s = strings.Map(func(r rune) rune {
if r == '\n' || r == '\r' {
return ' '
}
return r
}, s)
var err error
decoded := false
for {
s, err = rfc2047Recurse(s)
switch err {
case nil:
decoded = true
continue
default:
if decoded {
keyValuePair := strings.SplitAfter(s, "=")
if len(keyValuePair) < 2 {
return s
}
// Add quotes as needed.
if !strings.HasPrefix(keyValuePair[1], "\"") {
keyValuePair[1] = fmt.Sprintf("\"%s", keyValuePair[1])
}
if !strings.HasSuffix(keyValuePair[1], "\"") {
keyValuePair[1] = fmt.Sprintf("%s\"", keyValuePair[1])
}
return strings.Join(keyValuePair, "")
}
return s
}
}
}
// rfc2047Recurse is called for if the value contains content encoded in RFC2047 format and decodes
// it.
func rfc2047Recurse(s string) (string, error) {
us := strings.ToUpper(s)
if !strings.Contains(us, "?Q?") && !strings.Contains(us, "?B?") {
return s, io.EOF
}
var val string
if val = DecodeExtHeader(s); val == s {
if val = DecodeExtHeader(fixRFC2047String(val)); val == s {
return val, io.EOF
}
}
return val, nil
}
// fixRFC2047String removes the following characters from charset and encoding segments of an
// RFC2047 string: '\n', '\r' and ' '
func fixRFC2047String(s string) string {
inString := false
isWithinTerminatingEqualSigns := false
questionMarkCount := 0
sb := &strings.Builder{}
for _, v := range s {
switch v {
case '=':
if questionMarkCount == 3 {
inString = false
} else {
isWithinTerminatingEqualSigns = true
}
sb.WriteRune(v)
case '?':
if isWithinTerminatingEqualSigns {
inString = true
} else {
questionMarkCount++
}
isWithinTerminatingEqualSigns = false
sb.WriteRune(v)
case '\n', '\r', ' ':
if !inString {
sb.WriteRune(v)
}
isWithinTerminatingEqualSigns = false
default:
isWithinTerminatingEqualSigns = false
sb.WriteRune(v)
}
}
return sb.String()
}