package mime
import (
)
type WordEncoder byte
const (
BEncoding = WordEncoder('b')
QEncoding = WordEncoder('q')
)
var (
errInvalidWord = errors.New("mime: invalid RFC 2047 encoded-word")
)
func ( WordEncoder) (, string) string {
if !needsEncoding() {
return
}
return .encodeWord(, )
}
func ( string) bool {
for , := range {
if ( < ' ' || > '~') && != '\t' {
return true
}
}
return false
}
func ( WordEncoder) (, string) string {
var strings.Builder
.Grow(48)
.openWord(&, )
if == BEncoding {
.bEncode(&, , )
} else {
.qEncode(&, , )
}
closeWord(&)
return .String()
}
const (
maxEncodedWordLen = 75
maxContentLen = maxEncodedWordLen - len("=?UTF-8?q?") - len("?=")
)
var maxBase64Len = base64.StdEncoding.DecodedLen(maxContentLen)
func ( WordEncoder) ( *strings.Builder, , string) {
:= base64.NewEncoder(base64.StdEncoding, )
if !isUTF8() || base64.StdEncoding.EncodedLen(len()) <= maxContentLen {
io.WriteString(, )
.Close()
return
}
var , , int
for := 0; < len(); += {
_, = utf8.DecodeRuneInString([:])
if + <= maxBase64Len {
+=
} else {
io.WriteString(, [:])
.Close()
.splitWord(, )
=
=
}
}
io.WriteString(, [:])
.Close()
}
func ( WordEncoder) ( *strings.Builder, , string) {
if !isUTF8() {
writeQString(, )
return
}
var , int
for := 0; < len(); += {
:= []
var int
if >= ' ' && <= '~' && != '=' && != '?' && != '_' {
, = 1, 1
} else {
_, = utf8.DecodeRuneInString([:])
= 3 *
}
if + > maxContentLen {
.splitWord(, )
= 0
}
writeQString(, [:+])
+=
}
}
func ( *strings.Builder, string) {
for := 0; < len(); ++ {
switch := []; {
case == ' ':
.WriteByte('_')
case >= '!' && <= '~' && != '=' && != '?' && != '_':
.WriteByte()
default:
.WriteByte('=')
.WriteByte(upperhex[>>4])
.WriteByte(upperhex[&0x0f])
}
}
}
func ( WordEncoder) ( *strings.Builder, string) {
.WriteString("=?")
.WriteString()
.WriteByte('?')
.WriteByte(byte())
.WriteByte('?')
}
func ( *strings.Builder) {
.WriteString("?=")
}
func ( WordEncoder) ( *strings.Builder, string) {
closeWord()
.WriteByte(' ')
.openWord(, )
}
func ( string) bool {
return strings.EqualFold(, "UTF-8")
}
const upperhex = "0123456789ABCDEF"
type WordDecoder struct {
CharsetReader func(charset string, input io.Reader) (io.Reader, error)
}
func ( *WordDecoder) ( string) (string, error) {
if len() < 8 || !strings.HasPrefix(, "=?") || !strings.HasSuffix(, "?=") || strings.Count(, "?") != 4 {
return "", errInvalidWord
}
= [2 : len()-2]
:= strings.IndexByte(, '?')
:= [:]
if len() == 0 {
return "", errInvalidWord
}
if len() < +3 {
return "", errInvalidWord
}
:= [+1]
if [+2] != '?' {
return "", errInvalidWord
}
:= [+3:]
, := decode(, )
if != nil {
return "",
}
var strings.Builder
if := .convert(&, , ); != nil {
return "",
}
return .String(), nil
}
func ( *WordDecoder) ( string) (string, error) {
:= strings.Index(, "=?")
if == -1 {
return , nil
}
var strings.Builder
.WriteString([:])
= [:]
:= false
for {
:= strings.Index(, "=?")
if == -1 {
break
}
:= + len("=?")
:= strings.Index([:], "?")
if == -1 {
break
}
:= [ : +]
+= + len("?")
if len() < +len("Q??=") {
break
}
:= []
++
if [] != '?' {
break
}
++
:= strings.Index([:], "?=")
if == -1 {
break
}
:= [ : +]
:= + + len("?=")
, := decode(, )
if != nil {
= false
.WriteString([:+2])
= [+2:]
continue
}
if > 0 && (! || hasNonWhitespace([:])) {
.WriteString([:])
}
if := .convert(&, , ); != nil {
return "",
}
= [:]
= true
}
if len() > 0 {
.WriteString()
}
return .String(), nil
}
func ( byte, string) ([]byte, error) {
switch {
case 'B', 'b':
return base64.StdEncoding.DecodeString()
case 'Q', 'q':
return qDecode()
default:
return nil, errInvalidWord
}
}
func ( *WordDecoder) ( *strings.Builder, string, []byte) error {
switch {
case strings.EqualFold("utf-8", ):
.Write()
case strings.EqualFold("iso-8859-1", ):
for , := range {
.WriteRune(rune())
}
case strings.EqualFold("us-ascii", ):
for , := range {
if >= utf8.RuneSelf {
.WriteRune(unicode.ReplacementChar)
} else {
.WriteByte()
}
}
default:
if .CharsetReader == nil {
return fmt.Errorf("mime: unhandled charset %q", )
}
, := .CharsetReader(strings.ToLower(), bytes.NewReader())
if != nil {
return
}
if _, = io.Copy(, ); != nil {
return
}
}
return nil
}
func ( string) bool {
for , := range {
switch {
case ' ', '\t', '\n', '\r':
default:
return true
}
}
return false
}
func ( string) ([]byte, error) {
:= make([]byte, len())
:= 0
for := 0; < len(); ++ {
switch := []; {
case == '_':
[] = ' '
case == '=':
if +2 >= len() {
return nil, errInvalidWord
}
, := readHexByte([+1], [+2])
if != nil {
return nil,
}
[] =
+= 2
case ( <= '~' && >= ' ') || == '\n' || == '\r' || == '\t':
[] =
default:
return nil, errInvalidWord
}
++
}
return [:], nil
}
func (, byte) (byte, error) {
var , byte
var error
if , = fromHex(); != nil {
return 0,
}
if , = fromHex(); != nil {
return 0,
}
return <<4 | , nil
}
func ( byte) (byte, error) {
switch {
case >= '0' && <= '9':
return - '0', nil
case >= 'A' && <= 'F':
return - 'A' + 10, nil
case >= 'a' && <= 'f':
return - 'a' + 10, nil
}
return 0, fmt.Errorf("mime: invalid hex byte %#02x", )
}