github.com/rsc/go@v0.0.0-20150416155037-e040fd465409/src/internal/mime/header.go (about) 1 // Copyright 2015 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package mime 6 7 import ( 8 "bytes" 9 "encoding/base64" 10 "errors" 11 "fmt" 12 "io" 13 "io/ioutil" 14 "strconv" 15 "strings" 16 "unicode" 17 ) 18 19 // EncodeWord encodes a string into an RFC 2047 encoded-word. 20 func EncodeWord(s string) string { 21 // UTF-8 "Q" encoding 22 b := bytes.NewBufferString("=?utf-8?q?") 23 for i := 0; i < len(s); i++ { 24 switch c := s[i]; { 25 case c == ' ': 26 b.WriteByte('_') 27 case isVchar(c) && c != '=' && c != '?' && c != '_': 28 b.WriteByte(c) 29 default: 30 fmt.Fprintf(b, "=%02X", c) 31 } 32 } 33 b.WriteString("?=") 34 return b.String() 35 } 36 37 // DecodeWord decodes an RFC 2047 encoded-word. 38 func DecodeWord(s string) (string, error) { 39 fields := strings.Split(s, "?") 40 if len(fields) != 5 || fields[0] != "=" || fields[4] != "=" { 41 return "", errors.New("address not RFC 2047 encoded") 42 } 43 charset, enc := strings.ToLower(fields[1]), strings.ToLower(fields[2]) 44 if charset != "us-ascii" && charset != "iso-8859-1" && charset != "utf-8" { 45 return "", fmt.Errorf("charset not supported: %q", charset) 46 } 47 48 in := bytes.NewBufferString(fields[3]) 49 var r io.Reader 50 switch enc { 51 case "b": 52 r = base64.NewDecoder(base64.StdEncoding, in) 53 case "q": 54 r = qDecoder{r: in} 55 default: 56 return "", fmt.Errorf("RFC 2047 encoding not supported: %q", enc) 57 } 58 59 dec, err := ioutil.ReadAll(r) 60 if err != nil { 61 return "", err 62 } 63 64 switch charset { 65 case "us-ascii": 66 b := new(bytes.Buffer) 67 for _, c := range dec { 68 if c >= 0x80 { 69 b.WriteRune(unicode.ReplacementChar) 70 } else { 71 b.WriteRune(rune(c)) 72 } 73 } 74 return b.String(), nil 75 case "iso-8859-1": 76 b := new(bytes.Buffer) 77 for _, c := range dec { 78 b.WriteRune(rune(c)) 79 } 80 return b.String(), nil 81 case "utf-8": 82 return string(dec), nil 83 } 84 panic("unreachable") 85 } 86 87 type qDecoder struct { 88 r io.Reader 89 scratch [2]byte 90 } 91 92 func (qd qDecoder) Read(p []byte) (n int, err error) { 93 // This method writes at most one byte into p. 94 if len(p) == 0 { 95 return 0, nil 96 } 97 if _, err := qd.r.Read(qd.scratch[:1]); err != nil { 98 return 0, err 99 } 100 switch c := qd.scratch[0]; { 101 case c == '=': 102 if _, err := io.ReadFull(qd.r, qd.scratch[:2]); err != nil { 103 return 0, err 104 } 105 x, err := strconv.ParseInt(string(qd.scratch[:2]), 16, 64) 106 if err != nil { 107 return 0, fmt.Errorf("mime: invalid RFC 2047 encoding: %q", qd.scratch[:2]) 108 } 109 p[0] = byte(x) 110 case c == '_': 111 p[0] = ' ' 112 default: 113 p[0] = c 114 } 115 return 1, nil 116 } 117 118 // isVchar returns true if c is an RFC 5322 VCHAR character. 119 func isVchar(c byte) bool { 120 // Visible (printing) characters. 121 return '!' <= c && c <= '~' 122 }