blob: 9bc3e5e576ce211ebd044f11a587815169c69a22 [file] [log] [blame]
// 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"
"io/ioutil"
"strconv"
"strings"
"unicode"
)
// EncodeWord encodes a string into an RFC 2047 encoded-word.
func EncodeWord(s string) string {
// UTF-8 "Q" encoding
b := bytes.NewBufferString("=?utf-8?q?")
for i := 0; i < len(s); i++ {
switch c := s[i]; {
case c == ' ':
b.WriteByte('_')
case isVchar(c) && c != '=' && c != '?' && c != '_':
b.WriteByte(c)
default:
fmt.Fprintf(b, "=%02X", c)
}
}
b.WriteString("?=")
return b.String()
}
// DecodeWord decodes an RFC 2047 encoded-word.
func DecodeWord(s string) (string, error) {
fields := strings.Split(s, "?")
if len(fields) != 5 || fields[0] != "=" || fields[4] != "=" {
return "", errors.New("address not RFC 2047 encoded")
}
charset, enc := strings.ToLower(fields[1]), strings.ToLower(fields[2])
if charset != "us-ascii" && charset != "iso-8859-1" && charset != "utf-8" {
return "", fmt.Errorf("charset not supported: %q", charset)
}
in := bytes.NewBufferString(fields[3])
var r io.Reader
switch enc {
case "b":
r = base64.NewDecoder(base64.StdEncoding, in)
case "q":
r = qDecoder{r: in}
default:
return "", fmt.Errorf("RFC 2047 encoding not supported: %q", enc)
}
dec, err := ioutil.ReadAll(r)
if err != nil {
return "", err
}
switch charset {
case "us-ascii":
b := new(bytes.Buffer)
for _, c := range dec {
if c >= 0x80 {
b.WriteRune(unicode.ReplacementChar)
} else {
b.WriteRune(rune(c))
}
}
return b.String(), nil
case "iso-8859-1":
b := new(bytes.Buffer)
for _, c := range dec {
b.WriteRune(rune(c))
}
return b.String(), nil
case "utf-8":
return string(dec), nil
}
panic("unreachable")
}
type qDecoder struct {
r io.Reader
scratch [2]byte
}
func (qd qDecoder) Read(p []byte) (n int, err error) {
// This method writes at most one byte into p.
if len(p) == 0 {
return 0, nil
}
if _, err := qd.r.Read(qd.scratch[:1]); err != nil {
return 0, err
}
switch c := qd.scratch[0]; {
case c == '=':
if _, err := io.ReadFull(qd.r, qd.scratch[:2]); err != nil {
return 0, err
}
x, err := strconv.ParseInt(string(qd.scratch[:2]), 16, 64)
if err != nil {
return 0, fmt.Errorf("mime: invalid RFC 2047 encoding: %q", qd.scratch[:2])
}
p[0] = byte(x)
case c == '_':
p[0] = ' '
default:
p[0] = c
}
return 1, nil
}
// isVchar returns true if c is an RFC 5322 VCHAR character.
func isVchar(c byte) bool {
// Visible (printing) characters.
return '!' <= c && c <= '~'
}