mox/imapserver/utf7.go

147 lines
2.9 KiB
Go
Raw Normal View History

2023-01-30 16:27:06 +03:00
package imapserver
import (
"bytes"
2023-01-30 16:27:06 +03:00
"encoding/base64"
"errors"
"fmt"
"unicode/utf16"
2023-01-30 16:27:06 +03:00
)
// IMAP4rev1 uses a modified version of UTF-7.
// ../rfc/3501:1050
// ../rfc/2152:69
2023-01-30 16:27:06 +03:00
const utf7chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,"
var utf7encoding = base64.NewEncoding(utf7chars).WithPadding(base64.NoPadding)
var (
errUTF7SuperfluousShift = errors.New("utf7: superfluous unshift+shift")
errUTF7Base64 = errors.New("utf7: bad base64")
errUTF7OddSized = errors.New("utf7: odd-sized data")
errUTF7UnneededShift = errors.New("utf7: unneeded shift")
errUTF7UnfinishedShift = errors.New("utf7: unfinished shift")
errUTF7BadSurrogate = errors.New("utf7: bad utf16 surrogates")
2023-01-30 16:27:06 +03:00
)
func utf7decode(s string) (string, error) {
var r string
var shifted bool
var b string
lastunshift := -2
for i, c := range s {
if !shifted {
if c == '&' {
if lastunshift == i-1 {
return "", errUTF7SuperfluousShift
}
shifted = true
} else {
r += string(c)
}
continue
}
if c != '-' {
b += string(c)
continue
}
shifted = false
lastunshift = i
if b == "" {
r += "&"
continue
}
buf, err := utf7encoding.DecodeString(b)
if err != nil {
return "", fmt.Errorf("%w: %q: %v", errUTF7Base64, b, err)
}
b = ""
if len(buf)%2 != 0 {
return "", errUTF7OddSized
}
x := make([]rune, len(buf)/2)
j := 0
trymerge := false
2023-01-30 16:27:06 +03:00
for i := 0; i < len(buf); i += 2 {
x[j] = rune(buf[i])<<8 | rune(buf[i+1])
if trymerge {
s0 := utf16.IsSurrogate(x[j-1])
s1 := utf16.IsSurrogate(x[j])
if s0 && s1 {
c := utf16.DecodeRune(x[j-1], x[j])
if c == 0xfffd {
return "", fmt.Errorf("%w: decoding %x %x", errUTF7BadSurrogate, x[j-1], x[j])
}
x[j-1] = c
trymerge = false
continue
} else if s0 != s1 {
return "", fmt.Errorf("%w: not both surrogate: %x %x", errUTF7BadSurrogate, x[j-1], x[j])
}
}
2023-01-30 16:27:06 +03:00
j++
trymerge = true
2023-01-30 16:27:06 +03:00
}
x = x[:j]
2023-01-30 16:27:06 +03:00
for _, c := range x {
if c < 0x20 || c > 0x7e || c == '&' {
r += string(c)
} else {
// ../rfc/3501:1057
return "", errUTF7UnneededShift
2023-01-30 16:27:06 +03:00
}
}
}
if shifted {
return "", errUTF7UnfinishedShift
}
return r, nil
}
func utf7encode(s string) string {
var r string
var code string
flushcode := func() {
if code == "" {
return
}
var b bytes.Buffer
for _, c := range code {
high, low := utf16.EncodeRune(c)
if high == 0xfffd && low == 0xfffd {
b.WriteByte(byte(c >> 8))
b.WriteByte(byte(c >> 0))
} else {
b.WriteByte(byte(high >> 8))
b.WriteByte(byte(high >> 0))
b.WriteByte(byte(low >> 8))
b.WriteByte(byte(low >> 0))
}
}
r += "&" + utf7encoding.EncodeToString(b.Bytes()) + "-"
code = ""
}
for _, c := range s {
if c == '&' {
flushcode()
r += "&-"
} else if c >= ' ' && c < 0x7f {
flushcode()
r += string(c)
} else {
code += string(c)
}
}
flushcode()
return r
}