1package imapserver
2
3import (
4 "bytes"
5 "encoding/base64"
6 "errors"
7 "fmt"
8 "strings"
9 "unicode/utf16"
10)
11
12// IMAP4rev1 uses a modified version of UTF-7.
13// ../rfc/3501:1050
14// ../rfc/2152:69
15
16const utf7chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,"
17
18var utf7encoding = base64.NewEncoding(utf7chars).WithPadding(base64.NoPadding)
19
20var (
21 errUTF7SuperfluousShift = errors.New("utf7: superfluous unshift+shift")
22 errUTF7Base64 = errors.New("utf7: bad base64")
23 errUTF7OddSized = errors.New("utf7: odd-sized data")
24 errUTF7UnneededShift = errors.New("utf7: unneeded shift")
25 errUTF7UnfinishedShift = errors.New("utf7: unfinished shift")
26 errUTF7BadSurrogate = errors.New("utf7: bad utf16 surrogates")
27)
28
29func utf7decode(s string) (string, error) {
30 var r strings.Builder
31 var shifted bool
32 var b string
33 lastunshift := -2
34
35 for i, c := range s {
36 if !shifted {
37 if c == '&' {
38 if lastunshift == i-1 {
39 return "", errUTF7SuperfluousShift
40 }
41 shifted = true
42 } else {
43 r.WriteString(string(c))
44 }
45 continue
46 }
47
48 if c != '-' {
49 b += string(c)
50 continue
51 }
52
53 shifted = false
54 lastunshift = i
55 if b == "" {
56 r.WriteString("&")
57 continue
58 }
59 buf, err := utf7encoding.DecodeString(b)
60 if err != nil {
61 return "", fmt.Errorf("%w: %q: %v", errUTF7Base64, b, err)
62 }
63 b = ""
64
65 if len(buf)%2 != 0 {
66 return "", errUTF7OddSized
67 }
68
69 x := make([]rune, len(buf)/2)
70 j := 0
71 trymerge := false
72 for i := 0; i < len(buf); i += 2 {
73 x[j] = rune(buf[i])<<8 | rune(buf[i+1])
74 if trymerge {
75 s0 := utf16.IsSurrogate(x[j-1])
76 s1 := utf16.IsSurrogate(x[j])
77 if s0 && s1 {
78 c := utf16.DecodeRune(x[j-1], x[j])
79 if c == 0xfffd {
80 return "", fmt.Errorf("%w: decoding %x %x", errUTF7BadSurrogate, x[j-1], x[j])
81 }
82 x[j-1] = c
83 trymerge = false
84 continue
85 } else if s0 != s1 {
86 return "", fmt.Errorf("%w: not both surrogate: %x %x", errUTF7BadSurrogate, x[j-1], x[j])
87 }
88 }
89 j++
90 trymerge = true
91 }
92 x = x[:j]
93
94 for _, c := range x {
95 if c < 0x20 || c > 0x7e || c == '&' {
96 r.WriteString(string(c))
97 } else {
98 // ../rfc/3501:1057
99 return "", errUTF7UnneededShift
100 }
101 }
102 }
103 if shifted {
104 return "", errUTF7UnfinishedShift
105 }
106 return r.String(), nil
107}
108
109func utf7encode(s string) string {
110 var r strings.Builder
111 var code string
112
113 flushcode := func() {
114 if code == "" {
115 return
116 }
117 var b bytes.Buffer
118 for _, c := range code {
119 high, low := utf16.EncodeRune(c)
120 if high == 0xfffd && low == 0xfffd {
121 b.WriteByte(byte(c >> 8))
122 b.WriteByte(byte(c >> 0))
123 } else {
124 b.WriteByte(byte(high >> 8))
125 b.WriteByte(byte(high >> 0))
126 b.WriteByte(byte(low >> 8))
127 b.WriteByte(byte(low >> 0))
128 }
129 }
130 r.WriteString("&" + utf7encoding.EncodeToString(b.Bytes()) + "-")
131 code = ""
132 }
133
134 for _, c := range s {
135 if c == '&' {
136 flushcode()
137 r.WriteString("&-")
138 } else if c >= ' ' && c < 0x7f {
139 flushcode()
140 r.WriteString(string(c))
141 } else {
142 code += string(c)
143 }
144 }
145 flushcode()
146 return r.String()
147}
148