136 lines
2.8 KiB
Go
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()
|
|
}
|