| // Copyright 2015 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package mime |
| |
| import ( |
| "bytes" |
| "encoding/base64" |
| "errors" |
| "fmt" |
| "io" |
| "strings" |
| "sync" |
| "unicode" |
| "unicode/utf8" |
| ) |
| |
| // A WordEncoder is an RFC 2047 encoded-word encoder. |
| type WordEncoder byte |
| |
| const ( |
| // BEncoding represents Base64 encoding scheme as defined by RFC 2045. |
| BEncoding = WordEncoder('b') |
| // QEncoding represents the Q-encoding scheme as defined by RFC 2047. |
| QEncoding = WordEncoder('q') |
| ) |
| |
| var ( |
| errInvalidWord = errors.New("mime: invalid RFC 2047 encoded-word") |
| ) |
| |
| // Encode returns the encoded-word form of s. If s is ASCII without special |
| // characters, it is returned unchanged. The provided charset is the IANA |
| // charset name of s. It is case insensitive. |
| func (e WordEncoder) Encode(charset, s string) string { |
| if !needsEncoding(s) { |
| return s |
| } |
| return e.encodeWord(charset, s) |
| } |
| |
| func needsEncoding(s string) bool { |
| for _, b := range s { |
| if (b < ' ' || b > '~') && b != '\t' { |
| return true |
| } |
| } |
| return false |
| } |
| |
| // encodeWord encodes a string into an encoded-word. |
| func (e WordEncoder) encodeWord(charset, s string) string { |
| buf := getBuffer() |
| defer putBuffer(buf) |
| |
| e.openWord(buf, charset) |
| if e == BEncoding { |
| e.bEncode(buf, charset, s) |
| } else { |
| e.qEncode(buf, charset, s) |
| } |
| closeWord(buf) |
| |
| return buf.String() |
| } |
| |
| const ( |
| // The maximum length of an encoded-word is 75 characters. |
| // See RFC 2047, section 2. |
| maxEncodedWordLen = 75 |
| // maxContentLen is how much content can be encoded, ignoring the header and |
| // 2-byte footer. |
| maxContentLen = maxEncodedWordLen - len("=?UTF-8?q?") - len("?=") |
| ) |
| |
| var maxBase64Len = base64.StdEncoding.DecodedLen(maxContentLen) |
| |
| // bEncode encodes s using base64 encoding and writes it to buf. |
| func (e WordEncoder) bEncode(buf *bytes.Buffer, charset, s string) { |
| w := base64.NewEncoder(base64.StdEncoding, buf) |
| // If the charset is not UTF-8 or if the content is short, do not bother |
| // splitting the encoded-word. |
| if !isUTF8(charset) || base64.StdEncoding.EncodedLen(len(s)) <= maxContentLen { |
| io.WriteString(w, s) |
| w.Close() |
| return |
| } |
| |
| var currentLen, last, runeLen int |
| for i := 0; i < len(s); i += runeLen { |
| // Multi-byte characters must not be split across encoded-words. |
| // See RFC 2047, section 5.3. |
| _, runeLen = utf8.DecodeRuneInString(s[i:]) |
| |
| if currentLen+runeLen <= maxBase64Len { |
| currentLen += runeLen |
| } else { |
| io.WriteString(w, s[last:i]) |
| w.Close() |
| e.splitWord(buf, charset) |
| last = i |
| currentLen = runeLen |
| } |
| } |
| io.WriteString(w, s[last:]) |
| w.Close() |
| } |
| |
| // qEncode encodes s using Q encoding and writes it to buf. It splits the |
| // encoded-words when necessary. |
| func (e WordEncoder) qEncode(buf *bytes.Buffer, charset, s string) { |
| // We only split encoded-words when the charset is UTF-8. |
| if !isUTF8(charset) { |
| writeQString(buf, s) |
| return |
| } |
| |
| var currentLen, runeLen int |
| for i := 0; i < len(s); i += runeLen { |
| b := s[i] |
| // Multi-byte characters must not be split across encoded-words. |
| // See RFC 2047, section 5.3. |
| var encLen int |
| if b >= ' ' && b <= '~' && b != '=' && b != '?' && b != '_' { |
| runeLen, encLen = 1, 1 |
| } else { |
| _, runeLen = utf8.DecodeRuneInString(s[i:]) |
| encLen = 3 * runeLen |
| } |
| |
| if currentLen+encLen > maxContentLen { |
| e.splitWord(buf, charset) |
| currentLen = 0 |
| } |
| writeQString(buf, s[i:i+runeLen]) |
| currentLen += encLen |
| } |
| } |
| |
| // writeQString encodes s using Q encoding and writes it to buf. |
| func writeQString(buf *bytes.Buffer, s string) { |
| for i := 0; i < len(s); i++ { |
| switch b := s[i]; { |
| case b == ' ': |
| buf.WriteByte('_') |
| case b >= '!' && b <= '~' && b != '=' && b != '?' && b != '_': |
| buf.WriteByte(b) |
| default: |
| buf.WriteByte('=') |
| buf.WriteByte(upperhex[b>>4]) |
| buf.WriteByte(upperhex[b&0x0f]) |
| } |
| } |
| } |
| |
| // openWord writes the beginning of an encoded-word into buf. |
| func (e WordEncoder) openWord(buf *bytes.Buffer, charset string) { |
| buf.WriteString("=?") |
| buf.WriteString(charset) |
| buf.WriteByte('?') |
| buf.WriteByte(byte(e)) |
| buf.WriteByte('?') |
| } |
| |
| // closeWord writes the end of an encoded-word into buf. |
| func closeWord(buf *bytes.Buffer) { |
| buf.WriteString("?=") |
| } |
| |
| // splitWord closes the current encoded-word and opens a new one. |
| func (e WordEncoder) splitWord(buf *bytes.Buffer, charset string) { |
| closeWord(buf) |
| buf.WriteByte(' ') |
| e.openWord(buf, charset) |
| } |
| |
| func isUTF8(charset string) bool { |
| return strings.EqualFold(charset, "UTF-8") |
| } |
| |
| const upperhex = "0123456789ABCDEF" |
| |
| // A WordDecoder decodes MIME headers containing RFC 2047 encoded-words. |
| type WordDecoder struct { |
| // CharsetReader, if non-nil, defines a function to generate |
| // charset-conversion readers, converting from the provided |
| // charset into UTF-8. |
| // Charsets are always lower-case. utf-8, iso-8859-1 and us-ascii charsets |
| // are handled by default. |
| // One of the the CharsetReader's result values must be non-nil. |
| CharsetReader func(charset string, input io.Reader) (io.Reader, error) |
| } |
| |
| // Decode decodes an RFC 2047 encoded-word. |
| func (d *WordDecoder) Decode(word string) (string, error) { |
| if !strings.HasPrefix(word, "=?") || !strings.HasSuffix(word, "?=") || strings.Count(word, "?") != 4 { |
| return "", errInvalidWord |
| } |
| word = word[2 : len(word)-2] |
| |
| // split delimits the first 2 fields |
| split := strings.IndexByte(word, '?') |
| // the field after split must only be one byte |
| if word[split+2] != '?' { |
| return "", errInvalidWord |
| } |
| |
| // split word "UTF-8?q?ascii" into "UTF-8", 'q', and "ascii" |
| charset := word[:split] |
| encoding := word[split+1] |
| text := word[split+3:] |
| |
| content, err := decode(encoding, text) |
| if err != nil { |
| return "", err |
| } |
| |
| buf := getBuffer() |
| defer putBuffer(buf) |
| |
| if err := d.convert(buf, charset, content); err != nil { |
| return "", err |
| } |
| |
| return buf.String(), nil |
| } |
| |
| // DecodeHeader decodes all encoded-words of the given string. It returns an |
| // error if and only if CharsetReader of d returns an error. |
| func (d *WordDecoder) DecodeHeader(header string) (string, error) { |
| // If there is no encoded-word, returns before creating a buffer. |
| i := strings.Index(header, "=?") |
| if i == -1 { |
| return header, nil |
| } |
| |
| buf := getBuffer() |
| defer putBuffer(buf) |
| |
| buf.WriteString(header[:i]) |
| header = header[i:] |
| |
| betweenWords := false |
| for { |
| start := strings.Index(header, "=?") |
| if start == -1 { |
| break |
| } |
| cur := start + len("=?") |
| |
| i := strings.Index(header[cur:], "?") |
| if i == -1 { |
| break |
| } |
| charset := header[cur : cur+i] |
| cur += i + len("?") |
| |
| if len(header) < cur+len("Q??=") { |
| break |
| } |
| encoding := header[cur] |
| cur++ |
| |
| if header[cur] != '?' { |
| break |
| } |
| cur++ |
| |
| j := strings.Index(header[cur:], "?=") |
| if j == -1 { |
| break |
| } |
| text := header[cur : cur+j] |
| end := cur + j + len("?=") |
| |
| content, err := decode(encoding, text) |
| if err != nil { |
| betweenWords = false |
| buf.WriteString(header[:start+2]) |
| header = header[start+2:] |
| continue |
| } |
| |
| // Write characters before the encoded-word. White-space and newline |
| // characters separating two encoded-words must be deleted. |
| if start > 0 && (!betweenWords || hasNonWhitespace(header[:start])) { |
| buf.WriteString(header[:start]) |
| } |
| |
| if err := d.convert(buf, charset, content); err != nil { |
| return "", err |
| } |
| |
| header = header[end:] |
| betweenWords = true |
| } |
| |
| if len(header) > 0 { |
| buf.WriteString(header) |
| } |
| |
| return buf.String(), nil |
| } |
| |
| func decode(encoding byte, text string) ([]byte, error) { |
| switch encoding { |
| case 'B', 'b': |
| return base64.StdEncoding.DecodeString(text) |
| case 'Q', 'q': |
| return qDecode(text) |
| default: |
| return nil, errInvalidWord |
| } |
| } |
| |
| func (d *WordDecoder) convert(buf *bytes.Buffer, charset string, content []byte) error { |
| switch { |
| case strings.EqualFold("utf-8", charset): |
| buf.Write(content) |
| case strings.EqualFold("iso-8859-1", charset): |
| for _, c := range content { |
| buf.WriteRune(rune(c)) |
| } |
| case strings.EqualFold("us-ascii", charset): |
| for _, c := range content { |
| if c >= utf8.RuneSelf { |
| buf.WriteRune(unicode.ReplacementChar) |
| } else { |
| buf.WriteByte(c) |
| } |
| } |
| default: |
| if d.CharsetReader == nil { |
| return fmt.Errorf("mime: unhandled charset %q", charset) |
| } |
| r, err := d.CharsetReader(strings.ToLower(charset), bytes.NewReader(content)) |
| if err != nil { |
| return err |
| } |
| if _, err = buf.ReadFrom(r); err != nil { |
| return err |
| } |
| } |
| return nil |
| } |
| |
| // hasNonWhitespace reports whether s (assumed to be ASCII) contains at least |
| // one byte of non-whitespace. |
| func hasNonWhitespace(s string) bool { |
| for _, b := range s { |
| switch b { |
| // Encoded-words can only be separated by linear white spaces which does |
| // not include vertical tabs (\v). |
| case ' ', '\t', '\n', '\r': |
| default: |
| return true |
| } |
| } |
| return false |
| } |
| |
| // qDecode decodes a Q encoded string. |
| func qDecode(s string) ([]byte, error) { |
| dec := make([]byte, len(s)) |
| n := 0 |
| for i := 0; i < len(s); i++ { |
| switch c := s[i]; { |
| case c == '_': |
| dec[n] = ' ' |
| case c == '=': |
| if i+2 >= len(s) { |
| return nil, errInvalidWord |
| } |
| b, err := readHexByte(s[i+1], s[i+2]) |
| if err != nil { |
| return nil, err |
| } |
| dec[n] = b |
| i += 2 |
| case (c <= '~' && c >= ' ') || c == '\n' || c == '\r' || c == '\t': |
| dec[n] = c |
| default: |
| return nil, errInvalidWord |
| } |
| n++ |
| } |
| |
| return dec[:n], nil |
| } |
| |
| // readHexByte returns the byte from its quoted-printable representation. |
| func readHexByte(a, b byte) (byte, error) { |
| var hb, lb byte |
| var err error |
| if hb, err = fromHex(a); err != nil { |
| return 0, err |
| } |
| if lb, err = fromHex(b); err != nil { |
| return 0, err |
| } |
| return hb<<4 | lb, nil |
| } |
| |
| func fromHex(b byte) (byte, error) { |
| switch { |
| case b >= '0' && b <= '9': |
| return b - '0', nil |
| case b >= 'A' && b <= 'F': |
| return b - 'A' + 10, nil |
| // Accept badly encoded bytes. |
| case b >= 'a' && b <= 'f': |
| return b - 'a' + 10, nil |
| } |
| return 0, fmt.Errorf("mime: invalid hex byte %#02x", b) |
| } |
| |
| var bufPool = sync.Pool{ |
| New: func() interface{} { |
| return new(bytes.Buffer) |
| }, |
| } |
| |
| func getBuffer() *bytes.Buffer { |
| return bufPool.Get().(*bytes.Buffer) |
| } |
| |
| func putBuffer(buf *bytes.Buffer) { |
| if buf.Len() > 1024 { |
| return |
| } |
| buf.Reset() |
| bufPool.Put(buf) |
| } |