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