|
@@ -4,21 +4,29 @@ import (
|
|
"bufio"
|
|
"bufio"
|
|
"bytes"
|
|
"bytes"
|
|
"crypto/md5"
|
|
"crypto/md5"
|
|
- "encoding/base64"
|
|
|
|
"errors"
|
|
"errors"
|
|
"fmt"
|
|
"fmt"
|
|
- "gopkg.in/iconv.v1"
|
|
|
|
"io"
|
|
"io"
|
|
- "io/ioutil"
|
|
|
|
- "mime/quotedprintable"
|
|
|
|
|
|
+ "mime"
|
|
"net/mail"
|
|
"net/mail"
|
|
"net/textproto"
|
|
"net/textproto"
|
|
- "regexp"
|
|
|
|
"strings"
|
|
"strings"
|
|
"sync"
|
|
"sync"
|
|
"time"
|
|
"time"
|
|
)
|
|
)
|
|
|
|
|
|
|
|
+// A WordDecoder decodes MIME headers containing RFC 2047 encoded-words.
|
|
|
|
+// Used by the MimeHeaderDecode function.
|
|
|
|
+// It's exposed public so that an alternative decoder can be set, eg Gnu iconv
|
|
|
|
+// by importing the mail/inconv package.
|
|
|
|
+// Another alternative would be to use https://godoc.org/golang.org/x/text/encoding
|
|
|
|
+var Dec mime.WordDecoder
|
|
|
|
+
|
|
|
|
+func init() {
|
|
|
|
+ // use the default decoder, without Gnu inconv. Import the mail/inconv package to use iconv.
|
|
|
|
+ Dec = mime.WordDecoder{}
|
|
|
|
+}
|
|
|
|
+
|
|
const maxHeaderChunk = 1 + (3 << 10) // 3KB
|
|
const maxHeaderChunk = 1 + (3 << 10) // 3KB
|
|
|
|
|
|
// Address encodes an email address of the form `<user@host>`
|
|
// Address encodes an email address of the form `<user@host>`
|
|
@@ -192,107 +200,55 @@ func (e *Envelope) PopRcpt() Address {
|
|
return ret
|
|
return ret
|
|
}
|
|
}
|
|
|
|
|
|
-var mimeRegex, _ = regexp.Compile(`=\?(.+?)\?([QBqp])\?(.+?)\?=`)
|
|
|
|
-
|
|
|
|
-// Decode strings in Mime header format
|
|
|
|
-// eg. =?ISO-2022-JP?B?GyRCIVo9dztSOWJAOCVBJWMbKEI=?=
|
|
|
|
-// This function uses GNU iconv under the hood, for more charset support than in Go's library
|
|
|
|
|
|
+// Converts 7 bit encoded mime header strings to UTF-8
|
|
func MimeHeaderDecode(str string) string {
|
|
func MimeHeaderDecode(str string) string {
|
|
|
|
+ state := 0
|
|
|
|
+ var buf bytes.Buffer
|
|
|
|
+ var out []byte
|
|
|
|
+ for i := 0; i < len(str); i++ {
|
|
|
|
+ switch state {
|
|
|
|
+ case 0:
|
|
|
|
+ if str[i] == '=' {
|
|
|
|
+ buf.WriteByte(str[i])
|
|
|
|
+ state = 1
|
|
|
|
+ } else {
|
|
|
|
+ out = append(out, str[i])
|
|
|
|
+ }
|
|
|
|
+ case 1:
|
|
|
|
+ if str[i] == '?' {
|
|
|
|
+ buf.WriteByte(str[i])
|
|
|
|
+ state = 2
|
|
|
|
+ } else {
|
|
|
|
+ out = append(out, str[i])
|
|
|
|
+ buf.Reset()
|
|
|
|
+ state = 0
|
|
|
|
+ }
|
|
|
|
|
|
- matched := mimeRegex.FindAllStringSubmatch(str, -1)
|
|
|
|
- var charset, encoding, payload string
|
|
|
|
- if matched != nil {
|
|
|
|
- for i := 0; i < len(matched); i++ {
|
|
|
|
- if len(matched[i]) > 2 {
|
|
|
|
- charset = matched[i][1]
|
|
|
|
- encoding = strings.ToUpper(matched[i][2])
|
|
|
|
- payload = matched[i][3]
|
|
|
|
- switch encoding {
|
|
|
|
- case "B":
|
|
|
|
- str = strings.Replace(
|
|
|
|
- str,
|
|
|
|
- matched[i][0],
|
|
|
|
- MailTransportDecode(payload, "base64", charset),
|
|
|
|
- 1)
|
|
|
|
- case "Q":
|
|
|
|
- str = strings.Replace(
|
|
|
|
- str,
|
|
|
|
- matched[i][0],
|
|
|
|
- MailTransportDecode(payload, "quoted-printable", charset),
|
|
|
|
- 1)
|
|
|
|
|
|
+ case 2:
|
|
|
|
+ if str[i] == ' ' {
|
|
|
|
+ d, err := Dec.Decode(buf.String())
|
|
|
|
+ if err == nil {
|
|
|
|
+ out = append(out, []byte(d)...)
|
|
|
|
+ } else {
|
|
|
|
+ out = append(out, buf.Bytes()...)
|
|
}
|
|
}
|
|
|
|
+ out = append(out, ' ')
|
|
|
|
+ buf.Reset()
|
|
|
|
+ state = 0
|
|
|
|
+ } else {
|
|
|
|
+ buf.WriteByte(str[i])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- return str
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-// decode from 7bit to 8bit UTF-8
|
|
|
|
-// encodingType can be "base64" or "quoted-printable"
|
|
|
|
-func MailTransportDecode(str string, encodingType string, charset string) string {
|
|
|
|
- if charset == "" {
|
|
|
|
- charset = "UTF-8"
|
|
|
|
- } else {
|
|
|
|
- charset = strings.ToUpper(charset)
|
|
|
|
- }
|
|
|
|
- if encodingType == "base64" {
|
|
|
|
- str = fromBase64(str)
|
|
|
|
- } else if encodingType == "quoted-printable" {
|
|
|
|
- str = fromQuotedP(str)
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if charset != "UTF-8" {
|
|
|
|
- charset = fixCharset(charset)
|
|
|
|
- // iconv is pretty good at what it does
|
|
|
|
- if cd, err := iconv.Open("UTF-8", charset); err == nil {
|
|
|
|
- defer func() {
|
|
|
|
- cd.Close()
|
|
|
|
- if r := recover(); r != nil {
|
|
|
|
- //logln(1, fmt.Sprintf("Recovered in %v", r))
|
|
|
|
- }
|
|
|
|
- }()
|
|
|
|
- // eg. charset can be "ISO-2022-JP"
|
|
|
|
- return cd.ConvString(str)
|
|
|
|
|
|
+ if buf.Len() > 0 {
|
|
|
|
+ d, err := Dec.Decode(buf.String())
|
|
|
|
+ if err == nil {
|
|
|
|
+ out = append(out, []byte(d)...)
|
|
|
|
+ } else {
|
|
|
|
+ out = append(out, buf.Bytes()...)
|
|
}
|
|
}
|
|
-
|
|
|
|
- }
|
|
|
|
- return str
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-func fromBase64(data string) string {
|
|
|
|
- buf := bytes.NewBufferString(data)
|
|
|
|
- decoder := base64.NewDecoder(base64.StdEncoding, buf)
|
|
|
|
- res, _ := ioutil.ReadAll(decoder)
|
|
|
|
- return string(res)
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-func fromQuotedP(data string) string {
|
|
|
|
- res, _ := ioutil.ReadAll(quotedprintable.NewReader(strings.NewReader(data)))
|
|
|
|
- return string(res)
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-var charsetRegex, _ = regexp.Compile(`[_:.\/\\]`)
|
|
|
|
-
|
|
|
|
-func fixCharset(charset string) string {
|
|
|
|
- fixed_charset := charsetRegex.ReplaceAllString(charset, "-")
|
|
|
|
- // Fix charset
|
|
|
|
- // borrowed from http://squirrelmail.svn.sourceforge.net/viewvc/squirrelmail/trunk/squirrelmail/include/languages.php?revision=13765&view=markup
|
|
|
|
- // OE ks_c_5601_1987 > cp949
|
|
|
|
- fixed_charset = strings.Replace(fixed_charset, "ks-c-5601-1987", "cp949", -1)
|
|
|
|
- // Moz x-euc-tw > euc-tw
|
|
|
|
- fixed_charset = strings.Replace(fixed_charset, "x-euc", "euc", -1)
|
|
|
|
- // Moz x-windows-949 > cp949
|
|
|
|
- fixed_charset = strings.Replace(fixed_charset, "x-windows_", "cp", -1)
|
|
|
|
- // windows-125x and cp125x charsets
|
|
|
|
- fixed_charset = strings.Replace(fixed_charset, "windows-", "cp", -1)
|
|
|
|
- // ibm > cp
|
|
|
|
- fixed_charset = strings.Replace(fixed_charset, "ibm", "cp", -1)
|
|
|
|
- // iso-8859-8-i -> iso-8859-8
|
|
|
|
- fixed_charset = strings.Replace(fixed_charset, "iso-8859-8-i", "iso-8859-8", -1)
|
|
|
|
- if charset != fixed_charset {
|
|
|
|
- return fixed_charset
|
|
|
|
}
|
|
}
|
|
- return charset
|
|
|
|
|
|
+ return string(out)
|
|
}
|
|
}
|
|
|
|
|
|
// Envelopes have their own pool
|
|
// Envelopes have their own pool
|