Fixing to have the proper version of go-imap from foxcpp.
This commit is contained in:
@@ -1,13 +0,0 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"github.com/emersion/go-imap/v2"
|
||||
)
|
||||
|
||||
func FormatRights(rm imap.RightModification, rs imap.RightSet) string {
|
||||
s := ""
|
||||
if rm != imap.RightModificationReplace {
|
||||
s = string(rm)
|
||||
}
|
||||
return s + string(rs)
|
||||
}
|
||||
@@ -1,306 +0,0 @@
|
||||
package imapnum
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Range represents a single seq-number or seq-range value (RFC 3501 ABNF). Values
|
||||
// may be static (e.g. "1", "2:4") or dynamic (e.g. "*", "1:*"). A seq-number is
|
||||
// represented by setting Start = Stop. Zero is used to represent "*", which is
|
||||
// safe because seq-number uses nz-number rule. The order of values is always
|
||||
// Start <= Stop, except when representing "n:*", where Start = n and Stop = 0.
|
||||
type Range struct {
|
||||
Start, Stop uint32
|
||||
}
|
||||
|
||||
// Contains returns true if the seq-number q is contained in range value s.
|
||||
// The dynamic value "*" contains only other "*" values, the dynamic range "n:*"
|
||||
// contains "*" and all numbers >= n.
|
||||
func (s Range) Contains(q uint32) bool {
|
||||
if q == 0 {
|
||||
return s.Stop == 0 // "*" is contained only in "*" and "n:*"
|
||||
}
|
||||
return s.Start != 0 && s.Start <= q && (q <= s.Stop || s.Stop == 0)
|
||||
}
|
||||
|
||||
// Less returns true if s precedes and does not contain seq-number q.
|
||||
func (s Range) Less(q uint32) bool {
|
||||
return (s.Stop < q || q == 0) && s.Stop != 0
|
||||
}
|
||||
|
||||
// Merge combines range values s and t into a single union if the two
|
||||
// intersect or one is a superset of the other. The order of s and t does not
|
||||
// matter. If the values cannot be merged, s is returned unmodified and ok is
|
||||
// set to false.
|
||||
func (s Range) Merge(t Range) (union Range, ok bool) {
|
||||
union = s
|
||||
if s == t {
|
||||
return s, true
|
||||
}
|
||||
if s.Start != 0 && t.Start != 0 {
|
||||
// s and t are any combination of "n", "n:m", or "n:*"
|
||||
if s.Start > t.Start {
|
||||
s, t = t, s
|
||||
}
|
||||
// s starts at or before t, check where it ends
|
||||
if (s.Stop >= t.Stop && t.Stop != 0) || s.Stop == 0 {
|
||||
return s, true // s is a superset of t
|
||||
}
|
||||
// s is "n" or "n:m", if m == ^uint32(0) then t is "n:*"
|
||||
if s.Stop+1 >= t.Start || s.Stop == ^uint32(0) {
|
||||
return Range{s.Start, t.Stop}, true // s intersects or touches t
|
||||
}
|
||||
return union, false
|
||||
}
|
||||
// exactly one of s and t is "*"
|
||||
if s.Start == 0 {
|
||||
if t.Stop == 0 {
|
||||
return t, true // s is "*", t is "n:*"
|
||||
}
|
||||
} else if s.Stop == 0 {
|
||||
return s, true // s is "n:*", t is "*"
|
||||
}
|
||||
return union, false
|
||||
}
|
||||
|
||||
// String returns range value s as a seq-number or seq-range string.
|
||||
func (s Range) String() string {
|
||||
if s.Start == s.Stop {
|
||||
if s.Start == 0 {
|
||||
return "*"
|
||||
}
|
||||
return strconv.FormatUint(uint64(s.Start), 10)
|
||||
}
|
||||
b := strconv.AppendUint(make([]byte, 0, 24), uint64(s.Start), 10)
|
||||
if s.Stop == 0 {
|
||||
return string(append(b, ':', '*'))
|
||||
}
|
||||
return string(strconv.AppendUint(append(b, ':'), uint64(s.Stop), 10))
|
||||
}
|
||||
|
||||
func (s Range) append(nums []uint32) (out []uint32, ok bool) {
|
||||
if s.Start == 0 || s.Stop == 0 {
|
||||
return nil, false
|
||||
}
|
||||
for n := s.Start; n <= s.Stop; n++ {
|
||||
nums = append(nums, n)
|
||||
}
|
||||
return nums, true
|
||||
}
|
||||
|
||||
// Set is used to represent a set of message sequence numbers or UIDs (see
|
||||
// sequence-set ABNF rule). The zero value is an empty set.
|
||||
type Set []Range
|
||||
|
||||
// AddNum inserts new numbers into the set. The value 0 represents "*".
|
||||
func (s *Set) AddNum(q ...uint32) {
|
||||
for _, v := range q {
|
||||
s.insert(Range{v, v})
|
||||
}
|
||||
}
|
||||
|
||||
// AddRange inserts a new range into the set.
|
||||
func (s *Set) AddRange(start, stop uint32) {
|
||||
if (stop < start && stop != 0) || start == 0 {
|
||||
s.insert(Range{stop, start})
|
||||
} else {
|
||||
s.insert(Range{start, stop})
|
||||
}
|
||||
}
|
||||
|
||||
// AddSet inserts all values from t into s.
|
||||
func (s *Set) AddSet(t Set) {
|
||||
for _, v := range t {
|
||||
s.insert(v)
|
||||
}
|
||||
}
|
||||
|
||||
// Dynamic returns true if the set contains "*" or "n:*" values.
|
||||
func (s Set) Dynamic() bool {
|
||||
return len(s) > 0 && s[len(s)-1].Stop == 0
|
||||
}
|
||||
|
||||
// Contains returns true if the non-zero sequence number or UID q is contained
|
||||
// in the set. The dynamic range "n:*" contains all q >= n. It is the caller's
|
||||
// responsibility to handle the special case where q is the maximum UID in the
|
||||
// mailbox and q < n (i.e. the set cannot match UIDs against "*:n" or "*" since
|
||||
// it doesn't know what the maximum value is).
|
||||
func (s Set) Contains(q uint32) bool {
|
||||
if _, ok := s.search(q); ok {
|
||||
return q != 0
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Nums returns a slice of all numbers contained in the set.
|
||||
func (s Set) Nums() (nums []uint32, ok bool) {
|
||||
for _, v := range s {
|
||||
nums, ok = v.append(nums)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
return nums, true
|
||||
}
|
||||
|
||||
// String returns a sorted representation of all contained number values.
|
||||
func (s Set) String() string {
|
||||
if len(s) == 0 {
|
||||
return ""
|
||||
}
|
||||
b := make([]byte, 0, 64)
|
||||
for _, v := range s {
|
||||
b = append(b, ',')
|
||||
if v.Start == 0 {
|
||||
b = append(b, '*')
|
||||
continue
|
||||
}
|
||||
b = strconv.AppendUint(b, uint64(v.Start), 10)
|
||||
if v.Start != v.Stop {
|
||||
if v.Stop == 0 {
|
||||
b = append(b, ':', '*')
|
||||
continue
|
||||
}
|
||||
b = strconv.AppendUint(append(b, ':'), uint64(v.Stop), 10)
|
||||
}
|
||||
}
|
||||
return string(b[1:])
|
||||
}
|
||||
|
||||
// insert adds range value v to the set.
|
||||
func (ptr *Set) insert(v Range) {
|
||||
s := *ptr
|
||||
defer func() {
|
||||
*ptr = s
|
||||
}()
|
||||
|
||||
i, _ := s.search(v.Start)
|
||||
merged := false
|
||||
if i > 0 {
|
||||
// try merging with the preceding entry (e.g. "1,4".insert(2), i == 1)
|
||||
s[i-1], merged = s[i-1].Merge(v)
|
||||
}
|
||||
if i == len(s) {
|
||||
// v was either merged with the last entry or needs to be appended
|
||||
if !merged {
|
||||
s.insertAt(i, v)
|
||||
}
|
||||
return
|
||||
} else if merged {
|
||||
i--
|
||||
} else if s[i], merged = s[i].Merge(v); !merged {
|
||||
s.insertAt(i, v) // insert in the middle (e.g. "1,5".insert(3), i == 1)
|
||||
return
|
||||
}
|
||||
// v was merged with s[i], continue trying to merge until the end
|
||||
for j := i + 1; j < len(s); j++ {
|
||||
if s[i], merged = s[i].Merge(s[j]); !merged {
|
||||
if j > i+1 {
|
||||
// cut out all entries between i and j that were merged
|
||||
s = append(s[:i+1], s[j:]...)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
// everything after s[i] was merged
|
||||
s = s[:i+1]
|
||||
}
|
||||
|
||||
// insertAt inserts a new range value v at index i, resizing s.Set as needed.
|
||||
func (ptr *Set) insertAt(i int, v Range) {
|
||||
s := *ptr
|
||||
defer func() {
|
||||
*ptr = s
|
||||
}()
|
||||
|
||||
if n := len(s); i == n {
|
||||
// insert at the end
|
||||
s = append(s, v)
|
||||
return
|
||||
} else if n < cap(s) {
|
||||
// enough space, shift everything at and after i to the right
|
||||
s = s[:n+1]
|
||||
copy(s[i+1:], s[i:])
|
||||
} else {
|
||||
// allocate new slice and copy everything, n is at least 1
|
||||
set := make([]Range, n+1, n*2)
|
||||
copy(set, s[:i])
|
||||
copy(set[i+1:], s[i:])
|
||||
s = set
|
||||
}
|
||||
s[i] = v
|
||||
}
|
||||
|
||||
// search attempts to find the index of the range set value that contains q.
|
||||
// If no values contain q, the returned index is the position where q should be
|
||||
// inserted and ok is set to false.
|
||||
func (s Set) search(q uint32) (i int, ok bool) {
|
||||
min, max := 0, len(s)-1
|
||||
for min < max {
|
||||
if mid := (min + max) >> 1; s[mid].Less(q) {
|
||||
min = mid + 1
|
||||
} else {
|
||||
max = mid
|
||||
}
|
||||
}
|
||||
if max < 0 || s[min].Less(q) {
|
||||
return len(s), false // q is the new largest value
|
||||
}
|
||||
return min, s[min].Contains(q)
|
||||
}
|
||||
|
||||
// errBadNumSet is used to report problems with the format of a number set
|
||||
// value.
|
||||
type errBadNumSet string
|
||||
|
||||
func (err errBadNumSet) Error() string {
|
||||
return fmt.Sprintf("imap: bad number set value %q", string(err))
|
||||
}
|
||||
|
||||
// parseNum parses a single seq-number value (non-zero uint32 or "*").
|
||||
func parseNum(v string) (uint32, error) {
|
||||
if n, err := strconv.ParseUint(v, 10, 32); err == nil && v[0] != '0' {
|
||||
return uint32(n), nil
|
||||
} else if v == "*" {
|
||||
return 0, nil
|
||||
}
|
||||
return 0, errBadNumSet(v)
|
||||
}
|
||||
|
||||
// parseNumRange creates a new seq instance by parsing strings in the format
|
||||
// "n" or "n:m", where n and/or m may be "*". An error is returned for invalid
|
||||
// values.
|
||||
func parseNumRange(v string) (Range, error) {
|
||||
var (
|
||||
r Range
|
||||
err error
|
||||
)
|
||||
if sep := strings.IndexRune(v, ':'); sep < 0 {
|
||||
r.Start, err = parseNum(v)
|
||||
r.Stop = r.Start
|
||||
return r, err
|
||||
} else if r.Start, err = parseNum(v[:sep]); err == nil {
|
||||
if r.Stop, err = parseNum(v[sep+1:]); err == nil {
|
||||
if (r.Stop < r.Start && r.Stop != 0) || r.Start == 0 {
|
||||
r.Start, r.Stop = r.Stop, r.Start
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
}
|
||||
return r, errBadNumSet(v)
|
||||
}
|
||||
|
||||
// ParseSet returns a new Set after parsing the set string.
|
||||
func ParseSet(set string) (Set, error) {
|
||||
var s Set
|
||||
for _, sv := range strings.Split(set, ",") {
|
||||
r, err := parseNumRange(sv)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
s.AddRange(r.Start, r.Stop)
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
@@ -1,724 +0,0 @@
|
||||
package imapnum
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const max = ^uint32(0)
|
||||
|
||||
func TestParseNumRange(t *testing.T) {
|
||||
tests := []struct {
|
||||
in string
|
||||
out Range
|
||||
ok bool
|
||||
}{
|
||||
// Invalid number
|
||||
{"", Range{}, false},
|
||||
{" ", Range{}, false},
|
||||
{"A", Range{}, false},
|
||||
{"0", Range{}, false},
|
||||
{" 1", Range{}, false},
|
||||
{"1 ", Range{}, false},
|
||||
{"*1", Range{}, false},
|
||||
{"1*", Range{}, false},
|
||||
{"-1", Range{}, false},
|
||||
{"01", Range{}, false},
|
||||
{"0x1", Range{}, false},
|
||||
{"1 2", Range{}, false},
|
||||
{"1,2", Range{}, false},
|
||||
{"1.2", Range{}, false},
|
||||
{"4294967296", Range{}, false},
|
||||
|
||||
// Valid number
|
||||
{"*", Range{0, 0}, true},
|
||||
{"1", Range{1, 1}, true},
|
||||
{"42", Range{42, 42}, true},
|
||||
{"1000", Range{1000, 1000}, true},
|
||||
{"4294967295", Range{max, max}, true},
|
||||
|
||||
// Invalid range
|
||||
{":", Range{}, false},
|
||||
{"*:", Range{}, false},
|
||||
{":*", Range{}, false},
|
||||
{"1:", Range{}, false},
|
||||
{":1", Range{}, false},
|
||||
{"0:0", Range{}, false},
|
||||
{"0:*", Range{}, false},
|
||||
{"0:1", Range{}, false},
|
||||
{"1:0", Range{}, false},
|
||||
{"1:2 ", Range{}, false},
|
||||
{"1: 2", Range{}, false},
|
||||
{"1:2:", Range{}, false},
|
||||
{"1:2,", Range{}, false},
|
||||
{"1:2:3", Range{}, false},
|
||||
{"1:2,3", Range{}, false},
|
||||
{"*:4294967296", Range{}, false},
|
||||
{"0:4294967295", Range{}, false},
|
||||
{"1:4294967296", Range{}, false},
|
||||
{"4294967296:*", Range{}, false},
|
||||
{"4294967295:0", Range{}, false},
|
||||
{"4294967296:1", Range{}, false},
|
||||
{"4294967295:4294967296", Range{}, false},
|
||||
|
||||
// Valid range
|
||||
{"*:*", Range{0, 0}, true},
|
||||
{"1:*", Range{1, 0}, true},
|
||||
{"*:1", Range{1, 0}, true},
|
||||
{"2:2", Range{2, 2}, true},
|
||||
{"2:42", Range{2, 42}, true},
|
||||
{"42:2", Range{2, 42}, true},
|
||||
{"*:4294967294", Range{max - 1, 0}, true},
|
||||
{"*:4294967295", Range{max, 0}, true},
|
||||
{"4294967294:*", Range{max - 1, 0}, true},
|
||||
{"4294967295:*", Range{max, 0}, true},
|
||||
{"1:4294967294", Range{1, max - 1}, true},
|
||||
{"1:4294967295", Range{1, max}, true},
|
||||
{"4294967295:1000", Range{1000, max}, true},
|
||||
{"4294967294:4294967295", Range{max - 1, max}, true},
|
||||
{"4294967295:4294967295", Range{max, max}, true},
|
||||
}
|
||||
for _, test := range tests {
|
||||
out, err := parseNumRange(test.in)
|
||||
if !test.ok {
|
||||
if err == nil {
|
||||
t.Errorf("parseSeq(%q) expected error; got %q", test.in, out)
|
||||
}
|
||||
} else if err != nil {
|
||||
t.Errorf("parseSeq(%q) expected %q; got %v", test.in, test.out, err)
|
||||
} else if out != test.out {
|
||||
t.Errorf("parseSeq(%q) expected %q; got %q", test.in, test.out, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNumRangeContainsLess(t *testing.T) {
|
||||
tests := []struct {
|
||||
s string
|
||||
q uint32
|
||||
contains bool
|
||||
less bool
|
||||
}{
|
||||
{"2", 0, false, true},
|
||||
{"2", 1, false, false},
|
||||
{"2", 2, true, false},
|
||||
{"2", 3, false, true},
|
||||
{"2", max, false, true},
|
||||
|
||||
{"*", 0, true, false},
|
||||
{"*", 1, false, false},
|
||||
{"*", 2, false, false},
|
||||
{"*", 3, false, false},
|
||||
{"*", max, false, false},
|
||||
|
||||
{"2:3", 0, false, true},
|
||||
{"2:3", 1, false, false},
|
||||
{"2:3", 2, true, false},
|
||||
{"2:3", 3, true, false},
|
||||
{"2:3", 4, false, true},
|
||||
{"2:3", 5, false, true},
|
||||
|
||||
{"2:4", 0, false, true},
|
||||
{"2:4", 1, false, false},
|
||||
{"2:4", 2, true, false},
|
||||
{"2:4", 3, true, false},
|
||||
{"2:4", 4, true, false},
|
||||
{"2:4", 5, false, true},
|
||||
|
||||
{"4:4294967295", 0, false, true},
|
||||
{"4:4294967295", 1, false, false},
|
||||
{"4:4294967295", 2, false, false},
|
||||
{"4:4294967295", 3, false, false},
|
||||
{"4:4294967295", 4, true, false},
|
||||
{"4:4294967295", 5, true, false},
|
||||
{"4:4294967295", max, true, false},
|
||||
|
||||
{"4:*", 0, true, false},
|
||||
{"4:*", 1, false, false},
|
||||
{"4:*", 2, false, false},
|
||||
{"4:*", 3, false, false},
|
||||
{"4:*", 4, true, false},
|
||||
{"4:*", 5, true, false},
|
||||
{"4:*", max, true, false},
|
||||
}
|
||||
for _, test := range tests {
|
||||
s, err := parseNumRange(test.s)
|
||||
if err != nil {
|
||||
t.Errorf("parseSeq(%q) unexpected error; %v", test.s, err)
|
||||
continue
|
||||
}
|
||||
if s.Contains(test.q) != test.contains {
|
||||
t.Errorf("%q.Contains(%d) expected %v", test.s, test.q, test.contains)
|
||||
}
|
||||
if s.Less(test.q) != test.less {
|
||||
t.Errorf("%q.Less(%d) expected %v", test.s, test.q, test.less)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNumRangeMerge(T *testing.T) {
|
||||
tests := []struct {
|
||||
s, t, out string
|
||||
}{
|
||||
// Number with number
|
||||
{"1", "1", "1"},
|
||||
{"1", "2", "1:2"},
|
||||
{"1", "3", ""},
|
||||
{"1", "4294967295", ""},
|
||||
{"1", "*", ""},
|
||||
|
||||
{"4", "1", ""},
|
||||
{"4", "2", ""},
|
||||
{"4", "3", "3:4"},
|
||||
{"4", "4", "4"},
|
||||
{"4", "5", "4:5"},
|
||||
{"4", "6", ""},
|
||||
|
||||
{"4294967295", "4294967293", ""},
|
||||
{"4294967295", "4294967294", "4294967294:4294967295"},
|
||||
{"4294967295", "4294967295", "4294967295"},
|
||||
{"4294967295", "*", ""},
|
||||
|
||||
{"*", "1", ""},
|
||||
{"*", "2", ""},
|
||||
{"*", "4294967294", ""},
|
||||
{"*", "4294967295", ""},
|
||||
{"*", "*", "*"},
|
||||
|
||||
// Range with number
|
||||
{"1:3", "1", "1:3"},
|
||||
{"1:3", "2", "1:3"},
|
||||
{"1:3", "3", "1:3"},
|
||||
{"1:3", "4", "1:4"},
|
||||
{"1:3", "5", ""},
|
||||
{"1:3", "*", ""},
|
||||
|
||||
{"3:4", "1", ""},
|
||||
{"3:4", "2", "2:4"},
|
||||
{"3:4", "3", "3:4"},
|
||||
{"3:4", "4", "3:4"},
|
||||
{"3:4", "5", "3:5"},
|
||||
{"3:4", "6", ""},
|
||||
{"3:4", "*", ""},
|
||||
|
||||
{"2:3", "5", ""},
|
||||
{"2:4", "5", "2:5"},
|
||||
{"2:5", "5", "2:5"},
|
||||
{"2:6", "5", "2:6"},
|
||||
{"2:7", "5", "2:7"},
|
||||
{"2:*", "5", "2:*"},
|
||||
{"3:4", "5", "3:5"},
|
||||
{"3:5", "5", "3:5"},
|
||||
{"3:6", "5", "3:6"},
|
||||
{"3:7", "5", "3:7"},
|
||||
{"3:*", "5", "3:*"},
|
||||
{"4:5", "5", "4:5"},
|
||||
{"4:6", "5", "4:6"},
|
||||
{"4:7", "5", "4:7"},
|
||||
{"4:*", "5", "4:*"},
|
||||
{"5:6", "5", "5:6"},
|
||||
{"5:7", "5", "5:7"},
|
||||
{"5:*", "5", "5:*"},
|
||||
{"6:7", "5", "5:7"},
|
||||
{"6:*", "5", "5:*"},
|
||||
{"7:8", "5", ""},
|
||||
{"7:*", "5", ""},
|
||||
|
||||
{"3:4294967294", "1", ""},
|
||||
{"3:4294967294", "2", "2:4294967294"},
|
||||
{"3:4294967294", "3", "3:4294967294"},
|
||||
{"3:4294967294", "4", "3:4294967294"},
|
||||
{"3:4294967294", "4294967293", "3:4294967294"},
|
||||
{"3:4294967294", "4294967294", "3:4294967294"},
|
||||
{"3:4294967294", "4294967295", "3:4294967295"},
|
||||
{"3:4294967294", "*", ""},
|
||||
|
||||
{"3:4294967295", "1", ""},
|
||||
{"3:4294967295", "2", "2:4294967295"},
|
||||
{"3:4294967295", "3", "3:4294967295"},
|
||||
{"3:4294967295", "4", "3:4294967295"},
|
||||
{"3:4294967295", "4294967294", "3:4294967295"},
|
||||
{"3:4294967295", "4294967295", "3:4294967295"},
|
||||
{"3:4294967295", "*", ""},
|
||||
|
||||
{"1:4294967295", "1", "1:4294967295"},
|
||||
{"1:4294967295", "4294967295", "1:4294967295"},
|
||||
{"1:4294967295", "*", ""},
|
||||
|
||||
{"1:*", "1", "1:*"},
|
||||
{"1:*", "2", "1:*"},
|
||||
{"1:*", "4294967294", "1:*"},
|
||||
{"1:*", "4294967295", "1:*"},
|
||||
{"1:*", "*", "1:*"},
|
||||
|
||||
// Range with range
|
||||
{"5:8", "1:2", ""},
|
||||
{"5:8", "1:3", ""},
|
||||
{"5:8", "1:4", "1:8"},
|
||||
{"5:8", "1:5", "1:8"},
|
||||
{"5:8", "1:6", "1:8"},
|
||||
{"5:8", "1:7", "1:8"},
|
||||
{"5:8", "1:8", "1:8"},
|
||||
{"5:8", "1:9", "1:9"},
|
||||
{"5:8", "1:10", "1:10"},
|
||||
{"5:8", "1:11", "1:11"},
|
||||
{"5:8", "1:*", "1:*"},
|
||||
|
||||
{"5:8", "2:3", ""},
|
||||
{"5:8", "2:4", "2:8"},
|
||||
{"5:8", "2:5", "2:8"},
|
||||
{"5:8", "2:6", "2:8"},
|
||||
{"5:8", "2:7", "2:8"},
|
||||
{"5:8", "2:8", "2:8"},
|
||||
{"5:8", "2:9", "2:9"},
|
||||
{"5:8", "2:10", "2:10"},
|
||||
{"5:8", "2:11", "2:11"},
|
||||
{"5:8", "2:*", "2:*"},
|
||||
|
||||
{"5:8", "3:4", "3:8"},
|
||||
{"5:8", "3:5", "3:8"},
|
||||
{"5:8", "3:6", "3:8"},
|
||||
{"5:8", "3:7", "3:8"},
|
||||
{"5:8", "3:8", "3:8"},
|
||||
{"5:8", "3:9", "3:9"},
|
||||
{"5:8", "3:10", "3:10"},
|
||||
{"5:8", "3:11", "3:11"},
|
||||
{"5:8", "3:*", "3:*"},
|
||||
|
||||
{"5:8", "4:5", "4:8"},
|
||||
{"5:8", "4:6", "4:8"},
|
||||
{"5:8", "4:7", "4:8"},
|
||||
{"5:8", "4:8", "4:8"},
|
||||
{"5:8", "4:9", "4:9"},
|
||||
{"5:8", "4:10", "4:10"},
|
||||
{"5:8", "4:11", "4:11"},
|
||||
{"5:8", "4:*", "4:*"},
|
||||
|
||||
{"5:8", "5:6", "5:8"},
|
||||
{"5:8", "5:7", "5:8"},
|
||||
{"5:8", "5:8", "5:8"},
|
||||
{"5:8", "5:9", "5:9"},
|
||||
{"5:8", "5:10", "5:10"},
|
||||
{"5:8", "5:11", "5:11"},
|
||||
{"5:8", "5:*", "5:*"},
|
||||
|
||||
{"5:8", "6:7", "5:8"},
|
||||
{"5:8", "6:8", "5:8"},
|
||||
{"5:8", "6:9", "5:9"},
|
||||
{"5:8", "6:10", "5:10"},
|
||||
{"5:8", "6:11", "5:11"},
|
||||
{"5:8", "6:*", "5:*"},
|
||||
|
||||
{"5:8", "7:8", "5:8"},
|
||||
{"5:8", "7:9", "5:9"},
|
||||
{"5:8", "7:10", "5:10"},
|
||||
{"5:8", "7:11", "5:11"},
|
||||
{"5:8", "7:*", "5:*"},
|
||||
|
||||
{"5:8", "8:9", "5:9"},
|
||||
{"5:8", "8:10", "5:10"},
|
||||
{"5:8", "8:11", "5:11"},
|
||||
{"5:8", "8:*", "5:*"},
|
||||
|
||||
{"5:8", "9:10", "5:10"},
|
||||
{"5:8", "9:11", "5:11"},
|
||||
{"5:8", "9:*", "5:*"},
|
||||
|
||||
{"5:8", "10:11", ""},
|
||||
{"5:8", "10:*", ""},
|
||||
|
||||
{"1:*", "1:*", "1:*"},
|
||||
{"1:*", "2:*", "1:*"},
|
||||
{"1:*", "1:4294967294", "1:*"},
|
||||
{"1:*", "1:4294967295", "1:*"},
|
||||
{"1:*", "2:4294967295", "1:*"},
|
||||
|
||||
{"1:4294967295", "1:4294967294", "1:4294967295"},
|
||||
{"1:4294967295", "1:4294967295", "1:4294967295"},
|
||||
{"1:4294967295", "2:4294967295", "1:4294967295"},
|
||||
{"1:4294967295", "2:*", "1:*"},
|
||||
}
|
||||
for _, test := range tests {
|
||||
s, err := parseNumRange(test.s)
|
||||
if err != nil {
|
||||
T.Errorf("parseSeq(%q) unexpected error; %v", test.s, err)
|
||||
continue
|
||||
}
|
||||
t, err := parseNumRange(test.t)
|
||||
if err != nil {
|
||||
T.Errorf("parseSeq(%q) unexpected error; %v", test.t, err)
|
||||
continue
|
||||
}
|
||||
testOK := test.out != ""
|
||||
for i := 0; i < 2; i++ {
|
||||
if !testOK {
|
||||
test.out = test.s
|
||||
}
|
||||
out, ok := s.Merge(t)
|
||||
if out.String() != test.out || ok != testOK {
|
||||
T.Errorf("%q.Merge(%q) expected %q; got %q", test.s, test.t, test.out, out)
|
||||
}
|
||||
// Swap s & t, result should be identical
|
||||
test.s, test.t = test.t, test.s
|
||||
s, t = t, s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func checkNumSet(s Set, t *testing.T) {
|
||||
n := len(s)
|
||||
for i, v := range s {
|
||||
if v.Start == 0 {
|
||||
if v.Stop != 0 {
|
||||
t.Errorf(`NumSet(%q) index %d: "*:n" range`, s, i)
|
||||
} else if i != n-1 {
|
||||
t.Errorf(`NumSet(%q) index %d: "*" not at the end`, s, i)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if i > 0 && s[i-1].Stop >= v.Start-1 {
|
||||
t.Errorf(`NumSet(%q) index %d: overlap`, s, i)
|
||||
}
|
||||
if v.Stop < v.Start {
|
||||
if v.Stop != 0 {
|
||||
t.Errorf(`NumSet(%q) index %d: reversed range`, s, i)
|
||||
} else if i != n-1 {
|
||||
t.Errorf(`NumSet(%q) index %d: "n:*" not at the end`, s, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNumSetInfo(t *testing.T) {
|
||||
tests := []struct {
|
||||
s string
|
||||
q uint32
|
||||
contains bool
|
||||
}{
|
||||
{"", 0, false},
|
||||
{"", 1, false},
|
||||
{"", 2, false},
|
||||
{"", 3, false},
|
||||
{"", max, false},
|
||||
|
||||
{"2", 0, false},
|
||||
{"2", 1, false},
|
||||
{"2", 2, true},
|
||||
{"2", 3, false},
|
||||
{"2", max, false},
|
||||
|
||||
{"*", 0, false}, // Contains("*") is always false, use Dynamic() instead
|
||||
{"*", 1, false},
|
||||
{"*", 2, false},
|
||||
{"*", 3, false},
|
||||
{"*", max, false},
|
||||
|
||||
{"1:*", 0, false},
|
||||
{"1:*", 1, true},
|
||||
{"1:*", max, true},
|
||||
|
||||
{"2:4", 0, false},
|
||||
{"2:4", 1, false},
|
||||
{"2:4", 2, true},
|
||||
{"2:4", 3, true},
|
||||
{"2:4", 4, true},
|
||||
{"2:4", 5, false},
|
||||
{"2:4", max, false},
|
||||
|
||||
{"2,4", 0, false},
|
||||
{"2,4", 1, false},
|
||||
{"2,4", 2, true},
|
||||
{"2,4", 3, false},
|
||||
{"2,4", 4, true},
|
||||
{"2,4", 5, false},
|
||||
{"2,4", max, false},
|
||||
|
||||
{"2:4,6", 0, false},
|
||||
{"2:4,6", 1, false},
|
||||
{"2:4,6", 2, true},
|
||||
{"2:4,6", 3, true},
|
||||
{"2:4,6", 4, true},
|
||||
{"2:4,6", 5, false},
|
||||
{"2:4,6", 6, true},
|
||||
{"2:4,6", 7, false},
|
||||
|
||||
{"2,4:6", 0, false},
|
||||
{"2,4:6", 1, false},
|
||||
{"2,4:6", 2, true},
|
||||
{"2,4:6", 3, false},
|
||||
{"2,4:6", 4, true},
|
||||
{"2,4:6", 5, true},
|
||||
{"2,4:6", 6, true},
|
||||
{"2,4:6", 7, false},
|
||||
|
||||
{"2,4,6", 0, false},
|
||||
{"2,4,6", 1, false},
|
||||
{"2,4,6", 2, true},
|
||||
{"2,4,6", 3, false},
|
||||
{"2,4,6", 4, true},
|
||||
{"2,4,6", 5, false},
|
||||
{"2,4,6", 6, true},
|
||||
{"2,4,6", 7, false},
|
||||
|
||||
{"1,3:5,7,9:*", 0, false},
|
||||
{"1,3:5,7,9:*", 1, true},
|
||||
{"1,3:5,7,9:*", 2, false},
|
||||
{"1,3:5,7,9:*", 3, true},
|
||||
{"1,3:5,7,9:*", 4, true},
|
||||
{"1,3:5,7,9:*", 5, true},
|
||||
{"1,3:5,7,9:*", 6, false},
|
||||
{"1,3:5,7,9:*", 7, true},
|
||||
{"1,3:5,7,9:*", 8, false},
|
||||
{"1,3:5,7,9:*", 9, true},
|
||||
{"1,3:5,7,9:*", 10, true},
|
||||
{"1,3:5,7,9:*", max, true},
|
||||
|
||||
{"1,3:5,7,9,42", 0, false},
|
||||
{"1,3:5,7,9,42", 1, true},
|
||||
{"1,3:5,7,9,42", 2, false},
|
||||
{"1,3:5,7,9,42", 3, true},
|
||||
{"1,3:5,7,9,42", 4, true},
|
||||
{"1,3:5,7,9,42", 5, true},
|
||||
{"1,3:5,7,9,42", 6, false},
|
||||
{"1,3:5,7,9,42", 7, true},
|
||||
{"1,3:5,7,9,42", 8, false},
|
||||
{"1,3:5,7,9,42", 9, true},
|
||||
{"1,3:5,7,9,42", 10, false},
|
||||
{"1,3:5,7,9,42", 41, false},
|
||||
{"1,3:5,7,9,42", 42, true},
|
||||
{"1,3:5,7,9,42", 43, false},
|
||||
{"1,3:5,7,9,42", max, false},
|
||||
|
||||
{"1,3:5,7,9,42,*", 0, false},
|
||||
{"1,3:5,7,9,42,*", 1, true},
|
||||
{"1,3:5,7,9,42,*", 2, false},
|
||||
{"1,3:5,7,9,42,*", 3, true},
|
||||
{"1,3:5,7,9,42,*", 4, true},
|
||||
{"1,3:5,7,9,42,*", 5, true},
|
||||
{"1,3:5,7,9,42,*", 6, false},
|
||||
{"1,3:5,7,9,42,*", 7, true},
|
||||
{"1,3:5,7,9,42,*", 8, false},
|
||||
{"1,3:5,7,9,42,*", 9, true},
|
||||
{"1,3:5,7,9,42,*", 10, false},
|
||||
{"1,3:5,7,9,42,*", 41, false},
|
||||
{"1,3:5,7,9,42,*", 42, true},
|
||||
{"1,3:5,7,9,42,*", 43, false},
|
||||
{"1,3:5,7,9,42,*", max, false},
|
||||
|
||||
{"1,3:5,7,9,42,60:70,100:*", 0, false},
|
||||
{"1,3:5,7,9,42,60:70,100:*", 1, true},
|
||||
{"1,3:5,7,9,42,60:70,100:*", 2, false},
|
||||
{"1,3:5,7,9,42,60:70,100:*", 3, true},
|
||||
{"1,3:5,7,9,42,60:70,100:*", 4, true},
|
||||
{"1,3:5,7,9,42,60:70,100:*", 5, true},
|
||||
{"1,3:5,7,9,42,60:70,100:*", 6, false},
|
||||
{"1,3:5,7,9,42,60:70,100:*", 7, true},
|
||||
{"1,3:5,7,9,42,60:70,100:*", 8, false},
|
||||
{"1,3:5,7,9,42,60:70,100:*", 9, true},
|
||||
{"1,3:5,7,9,42,60:70,100:*", 10, false},
|
||||
{"1,3:5,7,9,42,60:70,100:*", 41, false},
|
||||
{"1,3:5,7,9,42,60:70,100:*", 42, true},
|
||||
{"1,3:5,7,9,42,60:70,100:*", 43, false},
|
||||
{"1,3:5,7,9,42,60:70,100:*", 59, false},
|
||||
{"1,3:5,7,9,42,60:70,100:*", 60, true},
|
||||
{"1,3:5,7,9,42,60:70,100:*", 65, true},
|
||||
{"1,3:5,7,9,42,60:70,100:*", 70, true},
|
||||
{"1,3:5,7,9,42,60:70,100:*", 71, false},
|
||||
{"1,3:5,7,9,42,60:70,100:*", 99, false},
|
||||
{"1,3:5,7,9,42,60:70,100:*", 100, true},
|
||||
{"1,3:5,7,9,42,60:70,100:*", 1000, true},
|
||||
{"1,3:5,7,9,42,60:70,100:*", max, true},
|
||||
}
|
||||
for _, test := range tests {
|
||||
s, _ := ParseSet(test.s)
|
||||
checkNumSet(s, t)
|
||||
if s.Contains(test.q) != test.contains {
|
||||
t.Errorf("%q.Contains(%v) expected %v", test.s, test.q, test.contains)
|
||||
}
|
||||
if str := s.String(); str != test.s {
|
||||
t.Errorf("%q.String() expected %q; got %q", test.s, test.s, str)
|
||||
}
|
||||
testEmpty := len(test.s) == 0
|
||||
if (len(s) == 0) != testEmpty {
|
||||
t.Errorf("%q.Empty() expected %v", test.s, testEmpty)
|
||||
}
|
||||
testDynamic := !testEmpty && test.s[len(test.s)-1] == '*'
|
||||
if s.Dynamic() != testDynamic {
|
||||
t.Errorf("%q.Dynamic() expected %v", test.s, testDynamic)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseNumSet(t *testing.T) {
|
||||
tests := []struct {
|
||||
in string
|
||||
out string
|
||||
}{
|
||||
{"1,1", "1"},
|
||||
{"1,2", "1:2"},
|
||||
{"1,3", "1,3"},
|
||||
{"1,*", "1,*"},
|
||||
|
||||
{"1,1,1", "1"},
|
||||
{"1,1,2", "1:2"},
|
||||
{"1,1:2", "1:2"},
|
||||
{"1,1,3", "1,3"},
|
||||
{"1,1:3", "1:3"},
|
||||
{"1,2,2", "1:2"},
|
||||
{"1,2,3", "1:3"},
|
||||
{"1,2:3", "1:3"},
|
||||
{"1,2,4", "1:2,4"},
|
||||
{"1,3,3", "1,3"},
|
||||
{"1,3,4", "1,3:4"},
|
||||
{"1,3:4", "1,3:4"},
|
||||
{"1,3,5", "1,3,5"},
|
||||
{"1,3:5", "1,3:5"},
|
||||
{"1:3,5", "1:3,5"},
|
||||
{"1:5,3", "1:5"},
|
||||
|
||||
{"1,2,3,4", "1:4"},
|
||||
{"1,2,4,5", "1:2,4:5"},
|
||||
{"1,2,4:5", "1:2,4:5"},
|
||||
{"1:2,4:5", "1:2,4:5"},
|
||||
|
||||
{"1,2,3,4,5", "1:5"},
|
||||
{"1,2:3,4:5", "1:5"},
|
||||
|
||||
{"1,2,4,5,7,9", "1:2,4:5,7,9"},
|
||||
{"1,2,4,5,7:9", "1:2,4:5,7:9"},
|
||||
{"1:2,4:5,7:9", "1:2,4:5,7:9"},
|
||||
{"1,2,4,5,7,8,9", "1:2,4:5,7:9"},
|
||||
{"1:2,4:5,7,8,9", "1:2,4:5,7:9"},
|
||||
|
||||
{"3,5:10,15:20", "3,5:10,15:20"},
|
||||
{"4,5:10,15:20", "4:10,15:20"},
|
||||
{"5,5:10,15:20", "5:10,15:20"},
|
||||
{"7,5:10,15:20", "5:10,15:20"},
|
||||
{"10,5:10,15:20", "5:10,15:20"},
|
||||
{"11,5:10,15:20", "5:11,15:20"},
|
||||
{"12,5:10,15:20", "5:10,12,15:20"},
|
||||
{"14,5:10,15:20", "5:10,14:20"},
|
||||
{"17,5:10,15:20", "5:10,15:20"},
|
||||
{"21,5:10,15:20", "5:10,15:21"},
|
||||
{"22,5:10,15:20", "5:10,15:20,22"},
|
||||
{"*,5:10,15:20", "5:10,15:20,*"},
|
||||
|
||||
{"1:3,5:10,15:20", "1:3,5:10,15:20"},
|
||||
{"1:4,5:10,15:20", "1:10,15:20"},
|
||||
{"1:8,5:10,15:20", "1:10,15:20"},
|
||||
{"1:13,5:10,15:20", "1:13,15:20"},
|
||||
{"1:14,5:10,15:20", "1:20"},
|
||||
{"7:17,5:10,15:20", "5:20"},
|
||||
{"11:14,5:10,15:20", "5:20"},
|
||||
{"12,13,5:10,15:20", "5:10,12:13,15:20"},
|
||||
{"12:13,5:10,15:20", "5:10,12:13,15:20"},
|
||||
{"12:14,5:10,15:20", "5:10,12:20"},
|
||||
{"11:13,5:10,15:20", "5:13,15:20"},
|
||||
{"11,12,13,14,5:10,15:20", "5:20"},
|
||||
|
||||
{"1:*,5:10,15:20", "1:*"},
|
||||
{"4:*,5:10,15:20", "4:*"},
|
||||
{"6:*,5:10,15:20", "5:*"},
|
||||
{"12:*,5:10,15:20", "5:10,12:*"},
|
||||
{"19:*,5:10,15:20", "5:10,15:*"},
|
||||
|
||||
{"5:8,6,7:10,15,16,17,18:20,19,21:*", "5:10,15:*"},
|
||||
|
||||
{"4:13,1,5,10,15,20", "1,4:13,15,20"},
|
||||
{"4:14,1,5,10,15,20", "1,4:15,20"},
|
||||
{"4:15,1,5,10,15,20", "1,4:15,20"},
|
||||
{"4:16,1,5,10,15,20", "1,4:16,20"},
|
||||
{"4:17,1,5,10,15,20", "1,4:17,20"},
|
||||
{"4:18,1,5,10,15,20", "1,4:18,20"},
|
||||
{"4:19,1,5,10,15,20", "1,4:20"},
|
||||
{"4:20,1,5,10,15,20", "1,4:20"},
|
||||
{"4:21,1,5,10,15,20", "1,4:21"},
|
||||
{"4:*,1,5,10,15,20", "1,4:*"},
|
||||
|
||||
{"1,3,5,7,9,11,13,15,17,19", "1,3,5,7,9,11,13,15,17,19"},
|
||||
{"1,3,5,7,9,11:13,15,17,19", "1,3,5,7,9,11:13,15,17,19"},
|
||||
{"1,3,5,7,9:11,13:15,17,19", "1,3,5,7,9:11,13:15,17,19"},
|
||||
{"1,3,5,7:9,11:13,15:17,19", "1,3,5,7:9,11:13,15:17,19"},
|
||||
{"1,3,5,7,9,11,13,15,17,19,*", "1,3,5,7,9,11,13,15,17,19,*"},
|
||||
{"1,3,5,7,9,11,13,15,17,19:*", "1,3,5,7,9,11,13,15,17,19:*"},
|
||||
{"1:20,3,5,7,9,11,13,15,17,19,*", "1:20,*"},
|
||||
{"1:20,3,5,7,9,11,13,15,17,19:*", "1:*"},
|
||||
|
||||
{"4294967295,*", "4294967295,*"},
|
||||
{"1,4294967295,*", "1,4294967295,*"},
|
||||
{"1:4294967295,*", "1:4294967295,*"},
|
||||
{"1,4294967295:*", "1,4294967295:*"},
|
||||
{"1:*,4294967295", "1:*"},
|
||||
{"1:*,4294967295:*", "1:*"},
|
||||
{"1:4294967295,4294967295:*", "1:*"},
|
||||
}
|
||||
prng := rand.New(rand.NewSource(19860201))
|
||||
done := make(map[string]bool)
|
||||
permute := func(in string) string {
|
||||
v := strings.Split(in, ",")
|
||||
r := make([]string, len(v))
|
||||
|
||||
// Try to find a permutation that hasn't been checked already
|
||||
for i := 0; i < 50; i++ {
|
||||
for i, j := range prng.Perm(len(v)) {
|
||||
r[i] = v[j]
|
||||
}
|
||||
if s := strings.Join(r, ","); !done[s] {
|
||||
done[s] = true
|
||||
return s
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
for _, test := range tests {
|
||||
for i := 0; i < 100 && test.in != ""; i++ {
|
||||
s, err := ParseSet(test.in)
|
||||
if err != nil {
|
||||
t.Errorf("Add(%q) unexpected error; %v", test.in, err)
|
||||
i = 100
|
||||
}
|
||||
checkNumSet(s, t)
|
||||
if out := s.String(); out != test.out {
|
||||
t.Errorf("%q.String() expected %q; got %q", test.in, test.out, out)
|
||||
i = 100
|
||||
}
|
||||
test.in = permute(test.in)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNumSetAddNumRangeSet(t *testing.T) {
|
||||
type num []uint32
|
||||
tests := []struct {
|
||||
num num
|
||||
rng Range
|
||||
set string
|
||||
out string
|
||||
}{
|
||||
{num{5}, Range{1, 3}, "1:2,5,7:13,15,17:*", "1:3,5,7:13,15,17:*"},
|
||||
{num{5}, Range{3, 1}, "2:3,7:13,15,17:*", "1:3,5,7:13,15,17:*"},
|
||||
|
||||
{num{15}, Range{17, 0}, "1:3,5,7:13", "1:3,5,7:13,15,17:*"},
|
||||
{num{15}, Range{0, 17}, "1:3,5,7:13", "1:3,5,7:13,15,17:*"},
|
||||
|
||||
{num{1, 3, 5, 7, 9, 11, 0}, Range{8, 13}, "2,15,17:*", "1:3,5,7:13,15,17:*"},
|
||||
{num{5, 1, 7, 3, 9, 0, 11}, Range{8, 13}, "2,15,17:*", "1:3,5,7:13,15,17:*"},
|
||||
{num{5, 1, 7, 3, 9, 0, 11}, Range{13, 8}, "2,15,17:*", "1:3,5,7:13,15,17:*"},
|
||||
}
|
||||
for _, test := range tests {
|
||||
other, _ := ParseSet(test.set)
|
||||
|
||||
var s Set
|
||||
s.AddNum(test.num...)
|
||||
checkNumSet(s, t)
|
||||
s.AddRange(test.rng.Start, test.rng.Stop)
|
||||
checkNumSet(s, t)
|
||||
s.AddSet(other)
|
||||
checkNumSet(s, t)
|
||||
|
||||
if out := s.String(); out != test.out {
|
||||
t.Errorf("(%v + %v + %q).String() expected %q; got %q", test.num, test.rng, test.set, test.out, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,654 +0,0 @@
|
||||
package imapwire
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/emersion/go-imap/v2"
|
||||
"github.com/emersion/go-imap/v2/internal/imapnum"
|
||||
"github.com/emersion/go-imap/v2/internal/utf7"
|
||||
)
|
||||
|
||||
// This limits the max list nesting depth to prevent stack overflow.
|
||||
const maxListDepth = 1000
|
||||
|
||||
// IsAtomChar returns true if ch is an ATOM-CHAR.
|
||||
func IsAtomChar(ch byte) bool {
|
||||
switch ch {
|
||||
case '(', ')', '{', ' ', '%', '*', '"', '\\', ']':
|
||||
return false
|
||||
default:
|
||||
return !unicode.IsControl(rune(ch))
|
||||
}
|
||||
}
|
||||
|
||||
// Is non-empty char
|
||||
func isAStringChar(ch byte) bool {
|
||||
return IsAtomChar(ch) || ch == ']'
|
||||
}
|
||||
|
||||
// DecoderExpectError is an error due to the Decoder.Expect family of methods.
|
||||
type DecoderExpectError struct {
|
||||
Message string
|
||||
}
|
||||
|
||||
func (err *DecoderExpectError) Error() string {
|
||||
return fmt.Sprintf("imapwire: %v", err.Message)
|
||||
}
|
||||
|
||||
// A Decoder reads IMAP data.
|
||||
//
|
||||
// There are multiple families of methods:
|
||||
//
|
||||
// - Methods directly named after IMAP grammar elements attempt to decode
|
||||
// said element, and return false if it's another element.
|
||||
// - "Expect" methods do the same, but set the decoder error (see Err) on
|
||||
// failure.
|
||||
type Decoder struct {
|
||||
// CheckBufferedLiteralFunc is called when a literal is about to be decoded
|
||||
// and needs to be fully buffered in memory.
|
||||
CheckBufferedLiteralFunc func(size int64, nonSync bool) error
|
||||
// MaxSize defines a maximum number of bytes to be read from the input.
|
||||
// Literals are ignored.
|
||||
MaxSize int64
|
||||
|
||||
r *bufio.Reader
|
||||
side ConnSide
|
||||
err error
|
||||
literal bool
|
||||
crlf bool
|
||||
listDepth int
|
||||
readBytes int64
|
||||
}
|
||||
|
||||
// NewDecoder creates a new decoder.
|
||||
func NewDecoder(r *bufio.Reader, side ConnSide) *Decoder {
|
||||
return &Decoder{r: r, side: side}
|
||||
}
|
||||
|
||||
func (dec *Decoder) mustUnreadByte() {
|
||||
if err := dec.r.UnreadByte(); err != nil {
|
||||
panic(fmt.Errorf("imapwire: failed to unread byte: %v", err))
|
||||
}
|
||||
dec.readBytes--
|
||||
}
|
||||
|
||||
// Err returns the decoder error, if any.
|
||||
func (dec *Decoder) Err() error {
|
||||
return dec.err
|
||||
}
|
||||
|
||||
func (dec *Decoder) returnErr(err error) bool {
|
||||
if err == nil {
|
||||
return true
|
||||
}
|
||||
if dec.err == nil {
|
||||
dec.err = err
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (dec *Decoder) readByte() (byte, bool) {
|
||||
if dec.MaxSize > 0 && dec.readBytes > dec.MaxSize {
|
||||
return 0, dec.returnErr(fmt.Errorf("imapwire: max size exceeded"))
|
||||
}
|
||||
dec.crlf = false
|
||||
if dec.literal {
|
||||
return 0, dec.returnErr(fmt.Errorf("imapwire: cannot decode while a literal is open"))
|
||||
}
|
||||
b, err := dec.r.ReadByte()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
err = io.ErrUnexpectedEOF
|
||||
}
|
||||
return b, dec.returnErr(err)
|
||||
}
|
||||
dec.readBytes++
|
||||
return b, true
|
||||
}
|
||||
|
||||
func (dec *Decoder) acceptByte(want byte) bool {
|
||||
got, ok := dec.readByte()
|
||||
if !ok {
|
||||
return false
|
||||
} else if got != want {
|
||||
dec.mustUnreadByte()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// EOF returns true if end-of-file is reached.
|
||||
func (dec *Decoder) EOF() bool {
|
||||
_, err := dec.r.ReadByte()
|
||||
if err == io.EOF {
|
||||
return true
|
||||
} else if err != nil {
|
||||
return dec.returnErr(err)
|
||||
}
|
||||
dec.mustUnreadByte()
|
||||
return false
|
||||
}
|
||||
|
||||
// Expect sets the decoder error if ok is false.
|
||||
func (dec *Decoder) Expect(ok bool, name string) bool {
|
||||
if !ok {
|
||||
msg := fmt.Sprintf("expected %v", name)
|
||||
if dec.r.Buffered() > 0 {
|
||||
b, _ := dec.r.Peek(1)
|
||||
msg += fmt.Sprintf(", got %q", b)
|
||||
}
|
||||
return dec.returnErr(&DecoderExpectError{Message: msg})
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (dec *Decoder) SP() bool {
|
||||
if dec.acceptByte(' ') {
|
||||
// https://github.com/emersion/go-imap/issues/571
|
||||
b, ok := dec.readByte()
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
dec.mustUnreadByte()
|
||||
return b != '\r' && b != '\n'
|
||||
}
|
||||
|
||||
// Special case: SP is optional if the next field is a parenthesized list
|
||||
b, ok := dec.readByte()
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
dec.mustUnreadByte()
|
||||
return b == '('
|
||||
}
|
||||
|
||||
func (dec *Decoder) ExpectSP() bool {
|
||||
return dec.Expect(dec.SP(), "SP")
|
||||
}
|
||||
|
||||
func (dec *Decoder) CRLF() bool {
|
||||
dec.acceptByte(' ') // https://github.com/emersion/go-imap/issues/540
|
||||
dec.acceptByte('\r') // be liberal in what we receive and accept lone LF
|
||||
if !dec.acceptByte('\n') {
|
||||
return false
|
||||
}
|
||||
dec.crlf = true
|
||||
return true
|
||||
}
|
||||
|
||||
func (dec *Decoder) ExpectCRLF() bool {
|
||||
return dec.Expect(dec.CRLF(), "CRLF")
|
||||
}
|
||||
|
||||
func (dec *Decoder) Func(ptr *string, valid func(ch byte) bool) bool {
|
||||
var sb strings.Builder
|
||||
for {
|
||||
b, ok := dec.readByte()
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if !valid(b) {
|
||||
dec.mustUnreadByte()
|
||||
break
|
||||
}
|
||||
|
||||
sb.WriteByte(b)
|
||||
}
|
||||
if sb.Len() == 0 {
|
||||
return false
|
||||
}
|
||||
*ptr = sb.String()
|
||||
return true
|
||||
}
|
||||
|
||||
func (dec *Decoder) Atom(ptr *string) bool {
|
||||
return dec.Func(ptr, IsAtomChar)
|
||||
}
|
||||
|
||||
func (dec *Decoder) ExpectAtom(ptr *string) bool {
|
||||
return dec.Expect(dec.Atom(ptr), "atom")
|
||||
}
|
||||
|
||||
func (dec *Decoder) ExpectNIL() bool {
|
||||
var s string
|
||||
return dec.ExpectAtom(&s) && dec.Expect(s == "NIL", "NIL")
|
||||
}
|
||||
|
||||
func (dec *Decoder) Special(b byte) bool {
|
||||
return dec.acceptByte(b)
|
||||
}
|
||||
|
||||
func (dec *Decoder) ExpectSpecial(b byte) bool {
|
||||
return dec.Expect(dec.Special(b), fmt.Sprintf("'%v'", string(b)))
|
||||
}
|
||||
|
||||
func (dec *Decoder) Text(ptr *string) bool {
|
||||
var sb strings.Builder
|
||||
for {
|
||||
b, ok := dec.readByte()
|
||||
if !ok {
|
||||
return false
|
||||
} else if b == '\r' || b == '\n' {
|
||||
dec.mustUnreadByte()
|
||||
break
|
||||
}
|
||||
sb.WriteByte(b)
|
||||
}
|
||||
if sb.Len() == 0 {
|
||||
return false
|
||||
}
|
||||
*ptr = sb.String()
|
||||
return true
|
||||
}
|
||||
|
||||
func (dec *Decoder) ExpectText(ptr *string) bool {
|
||||
return dec.Expect(dec.Text(ptr), "text")
|
||||
}
|
||||
|
||||
func (dec *Decoder) DiscardUntilByte(untilCh byte) {
|
||||
for {
|
||||
ch, ok := dec.readByte()
|
||||
if !ok {
|
||||
return
|
||||
} else if ch == untilCh {
|
||||
dec.mustUnreadByte()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (dec *Decoder) DiscardLine() {
|
||||
if dec.crlf {
|
||||
return
|
||||
}
|
||||
var text string
|
||||
dec.Text(&text)
|
||||
dec.CRLF()
|
||||
}
|
||||
|
||||
func (dec *Decoder) DiscardValue() bool {
|
||||
var s string
|
||||
if dec.String(&s) {
|
||||
return true
|
||||
}
|
||||
|
||||
isList, err := dec.List(func() error {
|
||||
if !dec.DiscardValue() {
|
||||
return dec.Err()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return false
|
||||
} else if isList {
|
||||
return true
|
||||
}
|
||||
|
||||
if dec.Atom(&s) {
|
||||
return true
|
||||
}
|
||||
|
||||
dec.Expect(false, "value")
|
||||
return false
|
||||
}
|
||||
|
||||
func (dec *Decoder) numberStr() (s string, ok bool) {
|
||||
var sb strings.Builder
|
||||
for {
|
||||
ch, ok := dec.readByte()
|
||||
if !ok {
|
||||
return "", false
|
||||
} else if ch < '0' || ch > '9' {
|
||||
dec.mustUnreadByte()
|
||||
break
|
||||
}
|
||||
sb.WriteByte(ch)
|
||||
}
|
||||
if sb.Len() == 0 {
|
||||
return "", false
|
||||
}
|
||||
return sb.String(), true
|
||||
}
|
||||
|
||||
func (dec *Decoder) Number(ptr *uint32) bool {
|
||||
s, ok := dec.numberStr()
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
v64, err := strconv.ParseUint(s, 10, 32)
|
||||
if err != nil {
|
||||
return false // can happen on overflow
|
||||
}
|
||||
*ptr = uint32(v64)
|
||||
return true
|
||||
}
|
||||
|
||||
func (dec *Decoder) ExpectNumber(ptr *uint32) bool {
|
||||
return dec.Expect(dec.Number(ptr), "number")
|
||||
}
|
||||
|
||||
func (dec *Decoder) ExpectBodyFldOctets(ptr *uint32) bool {
|
||||
// Workaround: some servers incorrectly return "-1" for the body structure
|
||||
// size. See:
|
||||
// https://github.com/emersion/go-imap/issues/534
|
||||
if dec.acceptByte('-') {
|
||||
*ptr = 0
|
||||
return dec.Expect(dec.acceptByte('1'), "-1 (body-fld-octets workaround)")
|
||||
}
|
||||
return dec.ExpectNumber(ptr)
|
||||
}
|
||||
|
||||
func (dec *Decoder) Number64(ptr *int64) bool {
|
||||
s, ok := dec.numberStr()
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
v, err := strconv.ParseInt(s, 10, 64)
|
||||
if err != nil {
|
||||
return false // can happen on overflow
|
||||
}
|
||||
*ptr = v
|
||||
return true
|
||||
}
|
||||
|
||||
func (dec *Decoder) ExpectNumber64(ptr *int64) bool {
|
||||
return dec.Expect(dec.Number64(ptr), "number64")
|
||||
}
|
||||
|
||||
func (dec *Decoder) ModSeq(ptr *uint64) bool {
|
||||
s, ok := dec.numberStr()
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
v, err := strconv.ParseUint(s, 10, 64)
|
||||
if err != nil {
|
||||
return false // can happen on overflow
|
||||
}
|
||||
*ptr = v
|
||||
return true
|
||||
}
|
||||
|
||||
func (dec *Decoder) ExpectModSeq(ptr *uint64) bool {
|
||||
return dec.Expect(dec.ModSeq(ptr), "mod-sequence-value")
|
||||
}
|
||||
|
||||
func (dec *Decoder) Quoted(ptr *string) bool {
|
||||
if !dec.Special('"') {
|
||||
return false
|
||||
}
|
||||
var sb strings.Builder
|
||||
for {
|
||||
ch, ok := dec.readByte()
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if ch == '"' {
|
||||
break
|
||||
}
|
||||
|
||||
if ch == '\\' {
|
||||
ch, ok = dec.readByte()
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
sb.WriteByte(ch)
|
||||
}
|
||||
*ptr = sb.String()
|
||||
return true
|
||||
}
|
||||
|
||||
func (dec *Decoder) ExpectAString(ptr *string) bool {
|
||||
if dec.Quoted(ptr) {
|
||||
return true
|
||||
}
|
||||
if dec.Literal(ptr) {
|
||||
return true
|
||||
}
|
||||
// We cannot do dec.Atom(ptr) here because sometimes mailbox names are unquoted,
|
||||
// and they can contain special characters like `]`.
|
||||
return dec.Expect(dec.Func(ptr, isAStringChar), "ASTRING-CHAR")
|
||||
}
|
||||
|
||||
func (dec *Decoder) String(ptr *string) bool {
|
||||
return dec.Quoted(ptr) || dec.Literal(ptr)
|
||||
}
|
||||
|
||||
func (dec *Decoder) ExpectString(ptr *string) bool {
|
||||
return dec.Expect(dec.String(ptr), "string")
|
||||
}
|
||||
|
||||
func (dec *Decoder) ExpectNString(ptr *string) bool {
|
||||
var s string
|
||||
if dec.Atom(&s) {
|
||||
if !dec.Expect(s == "NIL", "nstring") {
|
||||
return false
|
||||
}
|
||||
*ptr = ""
|
||||
return true
|
||||
}
|
||||
return dec.ExpectString(ptr)
|
||||
}
|
||||
|
||||
func (dec *Decoder) ExpectNStringReader() (lit *LiteralReader, nonSync, ok bool) {
|
||||
var s string
|
||||
if dec.Atom(&s) {
|
||||
if !dec.Expect(s == "NIL", "nstring") {
|
||||
return nil, false, false
|
||||
}
|
||||
return nil, true, true
|
||||
}
|
||||
// TODO: read quoted string as a string instead of buffering
|
||||
if dec.Quoted(&s) {
|
||||
return newLiteralReaderFromString(s), true, true
|
||||
}
|
||||
if lit, nonSync, ok = dec.LiteralReader(); ok {
|
||||
return lit, nonSync, true
|
||||
} else {
|
||||
return nil, false, dec.Expect(false, "nstring")
|
||||
}
|
||||
}
|
||||
|
||||
func (dec *Decoder) List(f func() error) (isList bool, err error) {
|
||||
if !dec.Special('(') {
|
||||
return false, nil
|
||||
}
|
||||
if dec.Special(')') {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
dec.listDepth++
|
||||
defer func() {
|
||||
dec.listDepth--
|
||||
}()
|
||||
|
||||
if dec.listDepth >= maxListDepth {
|
||||
return false, fmt.Errorf("imapwire: exceeded max depth")
|
||||
}
|
||||
|
||||
for {
|
||||
if err := f(); err != nil {
|
||||
return true, err
|
||||
}
|
||||
|
||||
if dec.Special(')') {
|
||||
return true, nil
|
||||
} else if !dec.ExpectSP() {
|
||||
return true, dec.Err()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (dec *Decoder) ExpectList(f func() error) error {
|
||||
isList, err := dec.List(f)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !dec.Expect(isList, "(") {
|
||||
return dec.Err()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dec *Decoder) ExpectNList(f func() error) error {
|
||||
var s string
|
||||
if dec.Atom(&s) {
|
||||
if !dec.Expect(s == "NIL", "NIL") {
|
||||
return dec.Err()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return dec.ExpectList(f)
|
||||
}
|
||||
|
||||
func (dec *Decoder) ExpectMailbox(ptr *string) bool {
|
||||
var name string
|
||||
if !dec.ExpectAString(&name) {
|
||||
return false
|
||||
}
|
||||
if strings.EqualFold(name, "INBOX") {
|
||||
*ptr = "INBOX"
|
||||
return true
|
||||
}
|
||||
name, err := utf7.Decode(name)
|
||||
if err == nil {
|
||||
*ptr = name
|
||||
}
|
||||
return dec.returnErr(err)
|
||||
}
|
||||
|
||||
func (dec *Decoder) ExpectUID(ptr *imap.UID) bool {
|
||||
var num uint32
|
||||
if !dec.ExpectNumber(&num) {
|
||||
return false
|
||||
}
|
||||
*ptr = imap.UID(num)
|
||||
return true
|
||||
}
|
||||
|
||||
func (dec *Decoder) ExpectNumSet(kind NumKind, ptr *imap.NumSet) bool {
|
||||
if dec.Special('$') {
|
||||
*ptr = imap.SearchRes()
|
||||
return true
|
||||
}
|
||||
|
||||
var s string
|
||||
if !dec.Expect(dec.Func(&s, isNumSetChar), "sequence-set") {
|
||||
return false
|
||||
}
|
||||
numSet, err := imapnum.ParseSet(s)
|
||||
if err != nil {
|
||||
return dec.returnErr(err)
|
||||
}
|
||||
|
||||
switch kind {
|
||||
case NumKindSeq:
|
||||
*ptr = seqSetFromNumSet(numSet)
|
||||
case NumKindUID:
|
||||
*ptr = uidSetFromNumSet(numSet)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (dec *Decoder) ExpectUIDSet(ptr *imap.UIDSet) bool {
|
||||
var numSet imap.NumSet
|
||||
ok := dec.ExpectNumSet(NumKindUID, &numSet)
|
||||
if ok {
|
||||
*ptr = numSet.(imap.UIDSet)
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
func isNumSetChar(ch byte) bool {
|
||||
return ch == '*' || IsAtomChar(ch)
|
||||
}
|
||||
|
||||
func (dec *Decoder) Literal(ptr *string) bool {
|
||||
lit, nonSync, ok := dec.LiteralReader()
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if dec.CheckBufferedLiteralFunc != nil {
|
||||
if err := dec.CheckBufferedLiteralFunc(lit.Size(), nonSync); err != nil {
|
||||
lit.cancel()
|
||||
return false
|
||||
}
|
||||
}
|
||||
var sb strings.Builder
|
||||
_, err := io.Copy(&sb, lit)
|
||||
if err == nil {
|
||||
*ptr = sb.String()
|
||||
}
|
||||
return dec.returnErr(err)
|
||||
}
|
||||
|
||||
func (dec *Decoder) LiteralReader() (lit *LiteralReader, nonSync, ok bool) {
|
||||
if !dec.Special('{') {
|
||||
return nil, false, false
|
||||
}
|
||||
var size int64
|
||||
if !dec.ExpectNumber64(&size) {
|
||||
return nil, false, false
|
||||
}
|
||||
if dec.side == ConnSideServer {
|
||||
nonSync = dec.acceptByte('+')
|
||||
}
|
||||
if !dec.ExpectSpecial('}') || !dec.ExpectCRLF() {
|
||||
return nil, false, false
|
||||
}
|
||||
dec.literal = true
|
||||
lit = &LiteralReader{
|
||||
dec: dec,
|
||||
size: size,
|
||||
r: io.LimitReader(dec.r, size),
|
||||
}
|
||||
return lit, nonSync, true
|
||||
}
|
||||
|
||||
func (dec *Decoder) ExpectLiteralReader() (lit *LiteralReader, nonSync bool, err error) {
|
||||
lit, nonSync, ok := dec.LiteralReader()
|
||||
if !dec.Expect(ok, "literal") {
|
||||
return nil, false, dec.Err()
|
||||
}
|
||||
return lit, nonSync, nil
|
||||
}
|
||||
|
||||
type LiteralReader struct {
|
||||
dec *Decoder
|
||||
size int64
|
||||
r io.Reader
|
||||
}
|
||||
|
||||
func newLiteralReaderFromString(s string) *LiteralReader {
|
||||
return &LiteralReader{
|
||||
size: int64(len(s)),
|
||||
r: strings.NewReader(s),
|
||||
}
|
||||
}
|
||||
|
||||
func (lit *LiteralReader) Size() int64 {
|
||||
return lit.size
|
||||
}
|
||||
|
||||
func (lit *LiteralReader) Read(b []byte) (int, error) {
|
||||
n, err := lit.r.Read(b)
|
||||
if err == io.EOF {
|
||||
lit.cancel()
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (lit *LiteralReader) cancel() {
|
||||
if lit.dec == nil {
|
||||
return
|
||||
}
|
||||
lit.dec.literal = false
|
||||
lit.dec = nil
|
||||
}
|
||||
@@ -1,341 +0,0 @@
|
||||
package imapwire
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/emersion/go-imap/v2"
|
||||
"github.com/emersion/go-imap/v2/internal/utf7"
|
||||
)
|
||||
|
||||
// An Encoder writes IMAP data.
|
||||
//
|
||||
// Most methods don't return an error, instead they defer error handling until
|
||||
// CRLF is called. These methods return the Encoder so that calls can be
|
||||
// chained.
|
||||
type Encoder struct {
|
||||
// QuotedUTF8 allows raw UTF-8 in quoted strings. This requires IMAP4rev2
|
||||
// to be available, or UTF8=ACCEPT to be enabled.
|
||||
QuotedUTF8 bool
|
||||
// LiteralMinus enables non-synchronizing literals for short payloads.
|
||||
// This requires IMAP4rev2 or LITERAL-. This is only meaningful for
|
||||
// clients.
|
||||
LiteralMinus bool
|
||||
// LiteralPlus enables non-synchronizing literals for all payloads. This
|
||||
// requires LITERAL+. This is only meaningful for clients.
|
||||
LiteralPlus bool
|
||||
// NewContinuationRequest creates a new continuation request. This is only
|
||||
// meaningful for clients.
|
||||
NewContinuationRequest func() *ContinuationRequest
|
||||
|
||||
w *bufio.Writer
|
||||
side ConnSide
|
||||
err error
|
||||
literal bool
|
||||
}
|
||||
|
||||
// NewEncoder creates a new encoder.
|
||||
func NewEncoder(w *bufio.Writer, side ConnSide) *Encoder {
|
||||
return &Encoder{w: w, side: side}
|
||||
}
|
||||
|
||||
func (enc *Encoder) setErr(err error) {
|
||||
if enc.err == nil {
|
||||
enc.err = err
|
||||
}
|
||||
}
|
||||
|
||||
func (enc *Encoder) writeString(s string) *Encoder {
|
||||
if enc.err != nil {
|
||||
return enc
|
||||
}
|
||||
if enc.literal {
|
||||
enc.err = fmt.Errorf("imapwire: cannot encode while a literal is open")
|
||||
return enc
|
||||
}
|
||||
if _, err := enc.w.WriteString(s); err != nil {
|
||||
enc.err = err
|
||||
}
|
||||
return enc
|
||||
}
|
||||
|
||||
// CRLF writes a "\r\n" sequence and flushes the buffered writer.
|
||||
func (enc *Encoder) CRLF() error {
|
||||
enc.writeString("\r\n")
|
||||
if enc.err != nil {
|
||||
return enc.err
|
||||
}
|
||||
return enc.w.Flush()
|
||||
}
|
||||
|
||||
func (enc *Encoder) Atom(s string) *Encoder {
|
||||
return enc.writeString(s)
|
||||
}
|
||||
|
||||
func (enc *Encoder) SP() *Encoder {
|
||||
return enc.writeString(" ")
|
||||
}
|
||||
|
||||
func (enc *Encoder) Special(ch byte) *Encoder {
|
||||
return enc.writeString(string(ch))
|
||||
}
|
||||
|
||||
func (enc *Encoder) Quoted(s string) *Encoder {
|
||||
var sb strings.Builder
|
||||
sb.Grow(2 + len(s))
|
||||
sb.WriteByte('"')
|
||||
for i := 0; i < len(s); i++ {
|
||||
ch := s[i]
|
||||
if ch == '"' || ch == '\\' {
|
||||
sb.WriteByte('\\')
|
||||
}
|
||||
sb.WriteByte(ch)
|
||||
}
|
||||
sb.WriteByte('"')
|
||||
return enc.writeString(sb.String())
|
||||
}
|
||||
|
||||
func (enc *Encoder) String(s string) *Encoder {
|
||||
if !enc.validQuoted(s) {
|
||||
enc.stringLiteral(s)
|
||||
return enc
|
||||
}
|
||||
return enc.Quoted(s)
|
||||
}
|
||||
|
||||
func (enc *Encoder) validQuoted(s string) bool {
|
||||
if len(s) > 4096 {
|
||||
return false
|
||||
}
|
||||
|
||||
for i := 0; i < len(s); i++ {
|
||||
ch := s[i]
|
||||
|
||||
// NUL, CR and LF are never valid
|
||||
switch ch {
|
||||
case 0, '\r', '\n':
|
||||
return false
|
||||
}
|
||||
|
||||
if !enc.QuotedUTF8 && ch > unicode.MaxASCII {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (enc *Encoder) stringLiteral(s string) {
|
||||
var sync *ContinuationRequest
|
||||
if enc.side == ConnSideClient && (!enc.LiteralMinus || len(s) > 4096) && !enc.LiteralPlus {
|
||||
if enc.NewContinuationRequest != nil {
|
||||
sync = enc.NewContinuationRequest()
|
||||
}
|
||||
if sync == nil {
|
||||
enc.setErr(fmt.Errorf("imapwire: cannot send synchronizing literal"))
|
||||
return
|
||||
}
|
||||
}
|
||||
wc := enc.Literal(int64(len(s)), sync)
|
||||
_, writeErr := io.WriteString(wc, s)
|
||||
closeErr := wc.Close()
|
||||
if writeErr != nil {
|
||||
enc.setErr(writeErr)
|
||||
} else if closeErr != nil {
|
||||
enc.setErr(closeErr)
|
||||
}
|
||||
}
|
||||
|
||||
func (enc *Encoder) Mailbox(name string) *Encoder {
|
||||
if strings.EqualFold(name, "INBOX") {
|
||||
return enc.Atom("INBOX")
|
||||
} else {
|
||||
if enc.QuotedUTF8 {
|
||||
name = utf7.Escape(name)
|
||||
} else {
|
||||
name = utf7.Encode(name)
|
||||
}
|
||||
return enc.String(name)
|
||||
}
|
||||
}
|
||||
|
||||
func (enc *Encoder) NumSet(numSet imap.NumSet) *Encoder {
|
||||
s := numSet.String()
|
||||
if s == "" {
|
||||
enc.setErr(fmt.Errorf("imapwire: cannot encode empty sequence set"))
|
||||
return enc
|
||||
}
|
||||
return enc.writeString(s)
|
||||
}
|
||||
|
||||
func (enc *Encoder) Flag(flag imap.Flag) *Encoder {
|
||||
if flag != "\\*" && !isValidFlag(string(flag)) {
|
||||
enc.setErr(fmt.Errorf("imapwire: invalid flag %q", flag))
|
||||
return enc
|
||||
}
|
||||
return enc.writeString(string(flag))
|
||||
}
|
||||
|
||||
func (enc *Encoder) MailboxAttr(attr imap.MailboxAttr) *Encoder {
|
||||
if !strings.HasPrefix(string(attr), "\\") || !isValidFlag(string(attr)) {
|
||||
enc.setErr(fmt.Errorf("imapwire: invalid mailbox attribute %q", attr))
|
||||
return enc
|
||||
}
|
||||
return enc.writeString(string(attr))
|
||||
}
|
||||
|
||||
// isValidFlag checks whether the provided string satisfies
|
||||
// flag-keyword / flag-extension.
|
||||
func isValidFlag(s string) bool {
|
||||
for i := 0; i < len(s); i++ {
|
||||
ch := s[i]
|
||||
if ch == '\\' {
|
||||
if i != 0 {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
if !IsAtomChar(ch) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return len(s) > 0
|
||||
}
|
||||
|
||||
func (enc *Encoder) Number(v uint32) *Encoder {
|
||||
return enc.writeString(strconv.FormatUint(uint64(v), 10))
|
||||
}
|
||||
|
||||
func (enc *Encoder) Number64(v int64) *Encoder {
|
||||
// TODO: disallow negative values
|
||||
return enc.writeString(strconv.FormatInt(v, 10))
|
||||
}
|
||||
|
||||
func (enc *Encoder) ModSeq(v uint64) *Encoder {
|
||||
// TODO: disallow zero values
|
||||
return enc.writeString(strconv.FormatUint(v, 10))
|
||||
}
|
||||
|
||||
// List writes a parenthesized list.
|
||||
func (enc *Encoder) List(n int, f func(i int)) *Encoder {
|
||||
enc.Special('(')
|
||||
for i := 0; i < n; i++ {
|
||||
if i > 0 {
|
||||
enc.SP()
|
||||
}
|
||||
f(i)
|
||||
}
|
||||
enc.Special(')')
|
||||
return enc
|
||||
}
|
||||
|
||||
func (enc *Encoder) BeginList() *ListEncoder {
|
||||
enc.Special('(')
|
||||
return &ListEncoder{enc: enc}
|
||||
}
|
||||
|
||||
func (enc *Encoder) NIL() *Encoder {
|
||||
return enc.Atom("NIL")
|
||||
}
|
||||
|
||||
func (enc *Encoder) Text(s string) *Encoder {
|
||||
return enc.writeString(s)
|
||||
}
|
||||
|
||||
func (enc *Encoder) UID(uid imap.UID) *Encoder {
|
||||
return enc.Number(uint32(uid))
|
||||
}
|
||||
|
||||
// Literal writes a literal.
|
||||
//
|
||||
// The caller must write exactly size bytes to the returned writer.
|
||||
//
|
||||
// If sync is non-nil, the literal is synchronizing: the encoder will wait for
|
||||
// nil to be sent to the channel before writing the literal data. If an error
|
||||
// is sent to the channel, the literal will be cancelled.
|
||||
func (enc *Encoder) Literal(size int64, sync *ContinuationRequest) io.WriteCloser {
|
||||
if sync != nil && enc.side == ConnSideServer {
|
||||
panic("imapwire: sync must be nil on a server-side Encoder.Literal")
|
||||
}
|
||||
|
||||
// TODO: literal8
|
||||
enc.writeString("{")
|
||||
enc.Number64(size)
|
||||
if sync == nil && enc.side == ConnSideClient {
|
||||
enc.writeString("+")
|
||||
}
|
||||
enc.writeString("}")
|
||||
|
||||
if sync == nil {
|
||||
enc.writeString("\r\n")
|
||||
} else {
|
||||
if err := enc.CRLF(); err != nil {
|
||||
return errorWriter{err}
|
||||
}
|
||||
if _, err := sync.Wait(); err != nil {
|
||||
enc.setErr(err)
|
||||
return errorWriter{err}
|
||||
}
|
||||
}
|
||||
|
||||
enc.literal = true
|
||||
return &literalWriter{
|
||||
enc: enc,
|
||||
n: size,
|
||||
}
|
||||
}
|
||||
|
||||
type errorWriter struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (ew errorWriter) Write(b []byte) (int, error) {
|
||||
return 0, ew.err
|
||||
}
|
||||
|
||||
func (ew errorWriter) Close() error {
|
||||
return ew.err
|
||||
}
|
||||
|
||||
type literalWriter struct {
|
||||
enc *Encoder
|
||||
n int64
|
||||
}
|
||||
|
||||
func (lw *literalWriter) Write(b []byte) (int, error) {
|
||||
if lw.n-int64(len(b)) < 0 {
|
||||
return 0, fmt.Errorf("wrote too many bytes in literal")
|
||||
}
|
||||
n, err := lw.enc.w.Write(b)
|
||||
lw.n -= int64(n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (lw *literalWriter) Close() error {
|
||||
lw.enc.literal = false
|
||||
if lw.n != 0 {
|
||||
return fmt.Errorf("wrote too few bytes in literal (%v remaining)", lw.n)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ListEncoder struct {
|
||||
enc *Encoder
|
||||
n int
|
||||
}
|
||||
|
||||
func (le *ListEncoder) Item() *Encoder {
|
||||
if le.n > 0 {
|
||||
le.enc.SP()
|
||||
}
|
||||
le.n++
|
||||
return le.enc
|
||||
}
|
||||
|
||||
func (le *ListEncoder) End() {
|
||||
le.enc.Special(')')
|
||||
le.enc = nil
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
// Package imapwire implements the IMAP wire protocol.
|
||||
//
|
||||
// The IMAP wire protocol is defined in RFC 9051 section 4.
|
||||
package imapwire
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ConnSide describes the side of a connection: client or server.
|
||||
type ConnSide int
|
||||
|
||||
const (
|
||||
ConnSideClient ConnSide = 1 + iota
|
||||
ConnSideServer
|
||||
)
|
||||
|
||||
// ContinuationRequest is a continuation request.
|
||||
//
|
||||
// The sender must call either Done or Cancel. The receiver must call Wait.
|
||||
type ContinuationRequest struct {
|
||||
done chan struct{}
|
||||
err error
|
||||
text string
|
||||
}
|
||||
|
||||
func NewContinuationRequest() *ContinuationRequest {
|
||||
return &ContinuationRequest{done: make(chan struct{})}
|
||||
}
|
||||
|
||||
func (cont *ContinuationRequest) Cancel(err error) {
|
||||
if err == nil {
|
||||
err = fmt.Errorf("imapwire: continuation request cancelled")
|
||||
}
|
||||
cont.err = err
|
||||
close(cont.done)
|
||||
}
|
||||
|
||||
func (cont *ContinuationRequest) Done(text string) {
|
||||
cont.text = text
|
||||
close(cont.done)
|
||||
}
|
||||
|
||||
func (cont *ContinuationRequest) Wait() (string, error) {
|
||||
<-cont.done
|
||||
return cont.text, cont.err
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package imapwire
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/emersion/go-imap/v2"
|
||||
"github.com/emersion/go-imap/v2/internal/imapnum"
|
||||
)
|
||||
|
||||
type NumKind int
|
||||
|
||||
const (
|
||||
NumKindSeq NumKind = iota + 1
|
||||
NumKindUID
|
||||
)
|
||||
|
||||
func seqSetFromNumSet(s imapnum.Set) imap.SeqSet {
|
||||
return *(*imap.SeqSet)(unsafe.Pointer(&s))
|
||||
}
|
||||
|
||||
func uidSetFromNumSet(s imapnum.Set) imap.UIDSet {
|
||||
return *(*imap.UIDSet)(unsafe.Pointer(&s))
|
||||
}
|
||||
|
||||
func NumSetKind(numSet imap.NumSet) NumKind {
|
||||
switch numSet.(type) {
|
||||
case imap.SeqSet:
|
||||
return NumKindSeq
|
||||
case imap.UIDSet:
|
||||
return NumKindUID
|
||||
default:
|
||||
panic("imap: invalid NumSet type")
|
||||
}
|
||||
}
|
||||
|
||||
func ParseSeqSet(s string) (imap.SeqSet, error) {
|
||||
numSet, err := imapnum.ParseSet(s)
|
||||
return seqSetFromNumSet(numSet), err
|
||||
}
|
||||
@@ -1,188 +0,0 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/emersion/go-imap/v2"
|
||||
"github.com/emersion/go-imap/v2/internal/imapwire"
|
||||
)
|
||||
|
||||
const (
|
||||
DateTimeLayout = "_2-Jan-2006 15:04:05 -0700"
|
||||
DateLayout = "2-Jan-2006"
|
||||
)
|
||||
|
||||
const FlagRecent imap.Flag = "\\Recent" // removed in IMAP4rev2
|
||||
|
||||
func DecodeDateTime(dec *imapwire.Decoder) (time.Time, error) {
|
||||
var s string
|
||||
if !dec.Quoted(&s) {
|
||||
return time.Time{}, nil
|
||||
}
|
||||
t, err := time.Parse(DateTimeLayout, s)
|
||||
if err != nil {
|
||||
return time.Time{}, fmt.Errorf("in date-time: %v", err) // TODO: use imapwire.DecodeExpectError?
|
||||
}
|
||||
return t, err
|
||||
}
|
||||
|
||||
func ExpectDateTime(dec *imapwire.Decoder) (time.Time, error) {
|
||||
t, err := DecodeDateTime(dec)
|
||||
if err != nil {
|
||||
return t, err
|
||||
}
|
||||
if !dec.Expect(!t.IsZero(), "date-time") {
|
||||
return t, dec.Err()
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func ExpectDate(dec *imapwire.Decoder) (time.Time, error) {
|
||||
var s string
|
||||
if !dec.ExpectAString(&s) {
|
||||
return time.Time{}, dec.Err()
|
||||
}
|
||||
t, err := time.Parse(DateLayout, s)
|
||||
if err != nil {
|
||||
return time.Time{}, fmt.Errorf("in date: %v", err) // use imapwire.DecodeExpectError?
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func ExpectFlagList(dec *imapwire.Decoder) ([]imap.Flag, error) {
|
||||
var flags []imap.Flag
|
||||
err := dec.ExpectList(func() error {
|
||||
// Some servers start the list with a space, so we need to skip it
|
||||
// https://github.com/emersion/go-imap/pull/633
|
||||
dec.SP()
|
||||
|
||||
flag, err := ExpectFlag(dec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
flags = append(flags, flag)
|
||||
return nil
|
||||
})
|
||||
return flags, err
|
||||
}
|
||||
|
||||
func ExpectCap(dec *imapwire.Decoder) (imap.Cap, error) {
|
||||
var name string
|
||||
if !dec.ExpectAtom(&name) {
|
||||
return "", dec.Err()
|
||||
}
|
||||
return canonicalCap(name), nil
|
||||
}
|
||||
|
||||
func ExpectFlag(dec *imapwire.Decoder) (imap.Flag, error) {
|
||||
isSystem := dec.Special('\\')
|
||||
if isSystem && dec.Special('*') {
|
||||
return imap.FlagWildcard, nil // flag-perm
|
||||
}
|
||||
var name string
|
||||
if !dec.ExpectAtom(&name) {
|
||||
return "", fmt.Errorf("in flag: %w", dec.Err())
|
||||
}
|
||||
if isSystem {
|
||||
name = "\\" + name
|
||||
}
|
||||
return canonicalFlag(name), nil
|
||||
}
|
||||
|
||||
func ExpectMailboxAttrList(dec *imapwire.Decoder) ([]imap.MailboxAttr, error) {
|
||||
var attrs []imap.MailboxAttr
|
||||
err := dec.ExpectList(func() error {
|
||||
attr, err := ExpectMailboxAttr(dec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
attrs = append(attrs, attr)
|
||||
return nil
|
||||
})
|
||||
return attrs, err
|
||||
}
|
||||
|
||||
func ExpectMailboxAttr(dec *imapwire.Decoder) (imap.MailboxAttr, error) {
|
||||
flag, err := ExpectFlag(dec)
|
||||
return canonicalMailboxAttr(string(flag)), err
|
||||
}
|
||||
|
||||
var (
|
||||
canonOnce sync.Once
|
||||
canonFlag map[string]imap.Flag
|
||||
canonMailboxAttr map[string]imap.MailboxAttr
|
||||
)
|
||||
|
||||
func canonInit() {
|
||||
flags := []imap.Flag{
|
||||
imap.FlagSeen,
|
||||
imap.FlagAnswered,
|
||||
imap.FlagFlagged,
|
||||
imap.FlagDeleted,
|
||||
imap.FlagDraft,
|
||||
imap.FlagForwarded,
|
||||
imap.FlagMDNSent,
|
||||
imap.FlagJunk,
|
||||
imap.FlagNotJunk,
|
||||
imap.FlagPhishing,
|
||||
imap.FlagImportant,
|
||||
}
|
||||
mailboxAttrs := []imap.MailboxAttr{
|
||||
imap.MailboxAttrNonExistent,
|
||||
imap.MailboxAttrNoInferiors,
|
||||
imap.MailboxAttrNoSelect,
|
||||
imap.MailboxAttrHasChildren,
|
||||
imap.MailboxAttrHasNoChildren,
|
||||
imap.MailboxAttrMarked,
|
||||
imap.MailboxAttrUnmarked,
|
||||
imap.MailboxAttrSubscribed,
|
||||
imap.MailboxAttrRemote,
|
||||
imap.MailboxAttrAll,
|
||||
imap.MailboxAttrArchive,
|
||||
imap.MailboxAttrDrafts,
|
||||
imap.MailboxAttrFlagged,
|
||||
imap.MailboxAttrJunk,
|
||||
imap.MailboxAttrSent,
|
||||
imap.MailboxAttrTrash,
|
||||
imap.MailboxAttrImportant,
|
||||
}
|
||||
|
||||
canonFlag = make(map[string]imap.Flag)
|
||||
for _, flag := range flags {
|
||||
canonFlag[strings.ToLower(string(flag))] = flag
|
||||
}
|
||||
|
||||
canonMailboxAttr = make(map[string]imap.MailboxAttr)
|
||||
for _, attr := range mailboxAttrs {
|
||||
canonMailboxAttr[strings.ToLower(string(attr))] = attr
|
||||
}
|
||||
}
|
||||
|
||||
func canonicalFlag(s string) imap.Flag {
|
||||
canonOnce.Do(canonInit)
|
||||
if flag, ok := canonFlag[strings.ToLower(s)]; ok {
|
||||
return flag
|
||||
}
|
||||
return imap.Flag(s)
|
||||
}
|
||||
|
||||
func canonicalMailboxAttr(s string) imap.MailboxAttr {
|
||||
canonOnce.Do(canonInit)
|
||||
if attr, ok := canonMailboxAttr[strings.ToLower(s)]; ok {
|
||||
return attr
|
||||
}
|
||||
return imap.MailboxAttr(s)
|
||||
}
|
||||
|
||||
func canonicalCap(s string) imap.Cap {
|
||||
// Only two caps are not fully uppercase
|
||||
for _, cap := range []imap.Cap{imap.CapIMAP4rev1, imap.CapIMAP4rev2} {
|
||||
if strings.EqualFold(s, string(cap)) {
|
||||
return cap
|
||||
}
|
||||
}
|
||||
return imap.Cap(strings.ToUpper(s))
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
)
|
||||
|
||||
func EncodeSASL(b []byte) string {
|
||||
if len(b) == 0 {
|
||||
return "="
|
||||
} else {
|
||||
return base64.StdEncoding.EncodeToString(b)
|
||||
}
|
||||
}
|
||||
|
||||
func DecodeSASL(s string) ([]byte, error) {
|
||||
if s == "=" {
|
||||
// go-sasl treats nil as no challenge/response, so return a non-nil
|
||||
// empty byte slice
|
||||
return []byte{}, nil
|
||||
} else {
|
||||
return base64.StdEncoding.DecodeString(s)
|
||||
}
|
||||
}
|
||||
37
internal/testcert.go
Normal file
37
internal/testcert.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package internal
|
||||
|
||||
// LocalhostCert is a PEM-encoded TLS cert with SAN IPs
|
||||
// "127.0.0.1" and "[::1]", expiring at Jan 29 16:00:00 2084 GMT.
|
||||
// generated from src/crypto/tls:
|
||||
// go run generate_cert.go --rsa-bits 1024 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
|
||||
var LocalhostCert = []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIICEzCCAXygAwIBAgIQMIMChMLGrR+QvmQvpwAU6zANBgkqhkiG9w0BAQsFADAS
|
||||
MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw
|
||||
MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB
|
||||
iQKBgQDuLnQAI3mDgey3VBzWnB2L39JUU4txjeVE6myuDqkM/uGlfjb9SjY1bIw4
|
||||
iA5sBBZzHi3z0h1YV8QPuxEbi4nW91IJm2gsvvZhIrCHS3l6afab4pZBl2+XsDul
|
||||
rKBxKKtD1rGxlG4LjncdabFn9gvLZad2bSysqz/qTAUStTvqJQIDAQABo2gwZjAO
|
||||
BgNVHQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUw
|
||||
AwEB/zAuBgNVHREEJzAlggtleGFtcGxlLmNvbYcEfwAAAYcQAAAAAAAAAAAAAAAA
|
||||
AAAAATANBgkqhkiG9w0BAQsFAAOBgQCEcetwO59EWk7WiJsG4x8SY+UIAA+flUI9
|
||||
tyC4lNhbcF2Idq9greZwbYCqTTTr2XiRNSMLCOjKyI7ukPoPjo16ocHj+P3vZGfs
|
||||
h1fIw3cSS2OolhloGw/XM6RWPWtPAlGykKLciQrBru5NAPvCMsb/I1DAceTiotQM
|
||||
fblo6RBxUQ==
|
||||
-----END CERTIFICATE-----`)
|
||||
|
||||
// LocalhostKey is the private key for localhostCert.
|
||||
var LocalhostKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICXgIBAAKBgQDuLnQAI3mDgey3VBzWnB2L39JUU4txjeVE6myuDqkM/uGlfjb9
|
||||
SjY1bIw4iA5sBBZzHi3z0h1YV8QPuxEbi4nW91IJm2gsvvZhIrCHS3l6afab4pZB
|
||||
l2+XsDulrKBxKKtD1rGxlG4LjncdabFn9gvLZad2bSysqz/qTAUStTvqJQIDAQAB
|
||||
AoGAGRzwwir7XvBOAy5tM/uV6e+Zf6anZzus1s1Y1ClbjbE6HXbnWWF/wbZGOpet
|
||||
3Zm4vD6MXc7jpTLryzTQIvVdfQbRc6+MUVeLKwZatTXtdZrhu+Jk7hx0nTPy8Jcb
|
||||
uJqFk541aEw+mMogY/xEcfbWd6IOkp+4xqjlFLBEDytgbIECQQDvH/E6nk+hgN4H
|
||||
qzzVtxxr397vWrjrIgPbJpQvBsafG7b0dA4AFjwVbFLmQcj2PprIMmPcQrooz8vp
|
||||
jy4SHEg1AkEA/v13/5M47K9vCxmb8QeD/asydfsgS5TeuNi8DoUBEmiSJwma7FXY
|
||||
fFUtxuvL7XvjwjN5B30pNEbc6Iuyt7y4MQJBAIt21su4b3sjXNueLKH85Q+phy2U
|
||||
fQtuUE9txblTu14q3N7gHRZB4ZMhFYyDy8CKrN2cPg/Fvyt0Xlp/DoCzjA0CQQDU
|
||||
y2ptGsuSmgUtWj3NM9xuwYPm+Z/F84K6+ARYiZ6PYj013sovGKUFfYAqVXVlxtIX
|
||||
qyUBnu3X9ps8ZfjLZO7BAkEAlT4R5Yl6cGhaJQYZHOde3JEMhNRcVFMO8dJDaFeo
|
||||
f9Oeos0UUothgiDktdQHxdNEwLjQf7lJJBzV+5OtwswCWA==
|
||||
-----END RSA PRIVATE KEY-----`)
|
||||
16
internal/testutil.go
Normal file
16
internal/testutil.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package internal
|
||||
|
||||
type MapListSorter []interface{}
|
||||
|
||||
func (s MapListSorter) Len() int {
|
||||
return len(s) / 2
|
||||
}
|
||||
|
||||
func (s MapListSorter) Less(i, j int) bool {
|
||||
return s[i*2].(string) < s[j*2].(string)
|
||||
}
|
||||
|
||||
func (s MapListSorter) Swap(i, j int) {
|
||||
s[i*2], s[j*2] = s[j*2], s[i*2]
|
||||
s[i*2+1], s[j*2+1] = s[j*2+1], s[i*2+1]
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
package utf7
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"unicode/utf16"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// ErrInvalidUTF7 means that a decoder encountered invalid UTF-7.
|
||||
var ErrInvalidUTF7 = errors.New("utf7: invalid UTF-7")
|
||||
|
||||
// Decode decodes a string encoded with modified UTF-7.
|
||||
//
|
||||
// Note, raw UTF-8 is accepted.
|
||||
func Decode(src string) (string, error) {
|
||||
if !utf8.ValidString(src) {
|
||||
return "", errors.New("invalid UTF-8")
|
||||
}
|
||||
|
||||
var sb strings.Builder
|
||||
sb.Grow(len(src))
|
||||
|
||||
ascii := true
|
||||
for i := 0; i < len(src); i++ {
|
||||
ch := src[i]
|
||||
|
||||
if ch < min || (ch > max && ch < utf8.RuneSelf) {
|
||||
// Illegal code point in ASCII mode. Note, UTF-8 codepoints are
|
||||
// always allowed.
|
||||
return "", ErrInvalidUTF7
|
||||
}
|
||||
|
||||
if ch != '&' {
|
||||
sb.WriteByte(ch)
|
||||
ascii = true
|
||||
continue
|
||||
}
|
||||
|
||||
// Find the end of the Base64 or "&-" segment
|
||||
start := i + 1
|
||||
for i++; i < len(src) && src[i] != '-'; i++ {
|
||||
if src[i] == '\r' || src[i] == '\n' { // base64 package ignores CR and LF
|
||||
return "", ErrInvalidUTF7
|
||||
}
|
||||
}
|
||||
|
||||
if i == len(src) { // Implicit shift ("&...")
|
||||
return "", ErrInvalidUTF7
|
||||
}
|
||||
|
||||
if i == start { // Escape sequence "&-"
|
||||
sb.WriteByte('&')
|
||||
ascii = true
|
||||
} else { // Control or non-ASCII code points in base64
|
||||
if !ascii { // Null shift ("&...-&...-")
|
||||
return "", ErrInvalidUTF7
|
||||
}
|
||||
|
||||
b := decode([]byte(src[start:i]))
|
||||
if len(b) == 0 { // Bad encoding
|
||||
return "", ErrInvalidUTF7
|
||||
}
|
||||
sb.Write(b)
|
||||
|
||||
ascii = false
|
||||
}
|
||||
}
|
||||
|
||||
return sb.String(), nil
|
||||
}
|
||||
|
||||
// Extracts UTF-16-BE bytes from base64 data and converts them to UTF-8.
|
||||
// A nil slice is returned if the encoding is invalid.
|
||||
func decode(b64 []byte) []byte {
|
||||
var b []byte
|
||||
|
||||
// Allocate a single block of memory large enough to store the Base64 data
|
||||
// (if padding is required), UTF-16-BE bytes, and decoded UTF-8 bytes.
|
||||
// Since a 2-byte UTF-16 sequence may expand into a 3-byte UTF-8 sequence,
|
||||
// double the space allocation for UTF-8.
|
||||
if n := len(b64); b64[n-1] == '=' {
|
||||
return nil
|
||||
} else if n&3 == 0 {
|
||||
b = make([]byte, b64Enc.DecodedLen(n)*3)
|
||||
} else {
|
||||
n += 4 - n&3
|
||||
b = make([]byte, n+b64Enc.DecodedLen(n)*3)
|
||||
copy(b[copy(b, b64):n], []byte("=="))
|
||||
b64, b = b[:n], b[n:]
|
||||
}
|
||||
|
||||
// Decode Base64 into the first 1/3rd of b
|
||||
n, err := b64Enc.Decode(b, b64)
|
||||
if err != nil || n&1 == 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode UTF-16-BE into the remaining 2/3rds of b
|
||||
b, s := b[:n], b[n:]
|
||||
j := 0
|
||||
for i := 0; i < n; i += 2 {
|
||||
r := rune(b[i])<<8 | rune(b[i+1])
|
||||
if utf16.IsSurrogate(r) {
|
||||
if i += 2; i == n {
|
||||
return nil
|
||||
}
|
||||
r2 := rune(b[i])<<8 | rune(b[i+1])
|
||||
if r = utf16.DecodeRune(r, r2); r == utf8.RuneError {
|
||||
return nil
|
||||
}
|
||||
} else if min <= r && r <= max {
|
||||
return nil
|
||||
}
|
||||
j += utf8.EncodeRune(s[j:], r)
|
||||
}
|
||||
return s[:j]
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
package utf7_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/emersion/go-imap/v2/internal/utf7"
|
||||
)
|
||||
|
||||
var decode = []struct {
|
||||
in string
|
||||
out string
|
||||
ok bool
|
||||
}{
|
||||
// Basics (the inverse test on encode checks other valid inputs)
|
||||
{"", "", true},
|
||||
{"abc", "abc", true},
|
||||
{"&-abc", "&abc", true},
|
||||
{"abc&-", "abc&", true},
|
||||
{"a&-b&-c", "a&b&c", true},
|
||||
{"&ABk-", "\x19", true},
|
||||
{"&AB8-", "\x1F", true},
|
||||
{"ABk-", "ABk-", true},
|
||||
{"&-,&-&AP8-&-", "&,&\u00FF&", true},
|
||||
{"&-&-,&AP8-&-", "&&,\u00FF&", true},
|
||||
{"abc &- &AP8A,wD,- &- xyz", "abc & \u00FF\u00FF\u00FF & xyz", true},
|
||||
|
||||
// Illegal code point in ASCII
|
||||
{"\x00", "", false},
|
||||
{"\x1F", "", false},
|
||||
{"abc\n", "", false},
|
||||
{"abc\x7Fxyz", "", false},
|
||||
|
||||
// Invalid UTF-8
|
||||
{"\xc3\x28", "", false},
|
||||
{"\xe2\x82\x28", "", false},
|
||||
|
||||
// Invalid Base64 alphabet
|
||||
{"&/+8-", "", false},
|
||||
{"&*-", "", false},
|
||||
{"&ZeVnLIqe -", "", false},
|
||||
|
||||
// CR and LF in Base64
|
||||
{"&ZeVnLIqe\r\n-", "", false},
|
||||
{"&ZeVnLIqe\r\n\r\n-", "", false},
|
||||
{"&ZeVn\r\n\r\nLIqe-", "", false},
|
||||
|
||||
// Padding not stripped
|
||||
{"&AAAAHw=-", "", false},
|
||||
{"&AAAAHw==-", "", false},
|
||||
{"&AAAAHwB,AIA=-", "", false},
|
||||
{"&AAAAHwB,AIA==-", "", false},
|
||||
|
||||
// One byte short
|
||||
{"&2A-", "", false},
|
||||
{"&2ADc-", "", false},
|
||||
{"&AAAAHwB,A-", "", false},
|
||||
{"&AAAAHwB,A=-", "", false},
|
||||
{"&AAAAHwB,A==-", "", false},
|
||||
{"&AAAAHwB,A===-", "", false},
|
||||
{"&AAAAHwB,AI-", "", false},
|
||||
{"&AAAAHwB,AI=-", "", false},
|
||||
{"&AAAAHwB,AI==-", "", false},
|
||||
|
||||
// Implicit shift
|
||||
{"&", "", false},
|
||||
{"&Jjo", "", false},
|
||||
{"Jjo&", "", false},
|
||||
{"&Jjo&", "", false},
|
||||
{"&Jjo!", "", false},
|
||||
{"&Jjo+", "", false},
|
||||
{"abc&Jjo", "", false},
|
||||
|
||||
// Null shift
|
||||
{"&AGE-&Jjo-", "", false},
|
||||
{"&U,BTFw-&ZeVnLIqe-", "", false},
|
||||
|
||||
// Long input with Base64 at the end
|
||||
{"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa &2D3eCg- &2D3eCw- &2D3eDg-",
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \U0001f60a \U0001f60b \U0001f60e", true},
|
||||
|
||||
// Long input in Base64 between short ASCII
|
||||
{"00000000000000000000 &MEIwQjBCMEIwQjBCMEIwQjBCMEIwQjBCMEIwQjBCMEIwQjBCMEIwQjBCMEIwQjBCMEIwQjBCMEIwQjBCMEIwQjBCMEIwQjBCMEI- 00000000000000000000",
|
||||
"00000000000000000000 " + strings.Repeat("\U00003042", 37) + " 00000000000000000000", true},
|
||||
|
||||
// ASCII in Base64
|
||||
{"&AGE-", "", false}, // "a"
|
||||
{"&ACY-", "", false}, // "&"
|
||||
{"&AGgAZQBsAGwAbw-", "", false}, // "hello"
|
||||
{"&JjoAIQ-", "", false}, // "\u263a!"
|
||||
|
||||
// Bad surrogate
|
||||
{"&2AA-", "", false}, // U+D800
|
||||
{"&2AD-", "", false}, // U+D800
|
||||
{"&3AA-", "", false}, // U+DC00
|
||||
{"&2AAAQQ-", "", false}, // U+D800 'A'
|
||||
{"&2AD,,w-", "", false}, // U+D800 U+FFFF
|
||||
{"&3ADYAA-", "", false}, // U+DC00 U+D800
|
||||
}
|
||||
|
||||
func TestDecoder(t *testing.T) {
|
||||
for _, test := range decode {
|
||||
out, err := utf7.Decode(test.in)
|
||||
if out != test.out {
|
||||
t.Errorf("UTF7Decode(%+q) expected %+q; got %+q", test.in, test.out, out)
|
||||
}
|
||||
if test.ok {
|
||||
if err != nil {
|
||||
t.Errorf("UTF7Decode(%+q) unexpected error; %v", test.in, err)
|
||||
}
|
||||
} else if err == nil {
|
||||
t.Errorf("UTF7Decode(%+q) expected error", test.in)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
package utf7
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"unicode/utf16"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// Encode encodes a string with modified UTF-7.
|
||||
func Encode(src string) string {
|
||||
var sb strings.Builder
|
||||
sb.Grow(len(src))
|
||||
|
||||
for i := 0; i < len(src); {
|
||||
ch := src[i]
|
||||
|
||||
if min <= ch && ch <= max {
|
||||
sb.WriteByte(ch)
|
||||
if ch == '&' {
|
||||
sb.WriteByte('-')
|
||||
}
|
||||
|
||||
i++
|
||||
} else {
|
||||
start := i
|
||||
|
||||
// Find the next printable ASCII code point
|
||||
i++
|
||||
for i < len(src) && (src[i] < min || src[i] > max) {
|
||||
i++
|
||||
}
|
||||
|
||||
sb.Write(encode([]byte(src[start:i])))
|
||||
}
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// Converts string s from UTF-8 to UTF-16-BE, encodes the result as base64,
|
||||
// removes the padding, and adds UTF-7 shifts.
|
||||
func encode(s []byte) []byte {
|
||||
// len(s) is sufficient for UTF-8 to UTF-16 conversion if there are no
|
||||
// control code points (see table below).
|
||||
b := make([]byte, 0, len(s)+4)
|
||||
for len(s) > 0 {
|
||||
r, size := utf8.DecodeRune(s)
|
||||
if r > utf8.MaxRune {
|
||||
r, size = utf8.RuneError, 1 // Bug fix (issue 3785)
|
||||
}
|
||||
s = s[size:]
|
||||
if r1, r2 := utf16.EncodeRune(r); r1 != utf8.RuneError {
|
||||
b = append(b, byte(r1>>8), byte(r1))
|
||||
r = r2
|
||||
}
|
||||
b = append(b, byte(r>>8), byte(r))
|
||||
}
|
||||
|
||||
// Encode as base64
|
||||
n := b64Enc.EncodedLen(len(b)) + 2
|
||||
b64 := make([]byte, n)
|
||||
b64Enc.Encode(b64[1:], b)
|
||||
|
||||
// Strip padding
|
||||
n -= 2 - (len(b)+2)%3
|
||||
b64 = b64[:n]
|
||||
|
||||
// Add UTF-7 shifts
|
||||
b64[0] = '&'
|
||||
b64[n-1] = '-'
|
||||
return b64
|
||||
}
|
||||
|
||||
// Escape passes through raw UTF-8 as-is and escapes the special UTF-7 marker
|
||||
// (the ampersand character).
|
||||
func Escape(src string) string {
|
||||
var sb strings.Builder
|
||||
sb.Grow(len(src))
|
||||
|
||||
for _, ch := range src {
|
||||
sb.WriteRune(ch)
|
||||
if ch == '&' {
|
||||
sb.WriteByte('-')
|
||||
}
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
package utf7_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/emersion/go-imap/v2/internal/utf7"
|
||||
)
|
||||
|
||||
var encode = []struct {
|
||||
in string
|
||||
out string
|
||||
ok bool
|
||||
}{
|
||||
// Printable ASCII
|
||||
{"", "", true},
|
||||
{"a", "a", true},
|
||||
{"ab", "ab", true},
|
||||
{"-", "-", true},
|
||||
{"&", "&-", true},
|
||||
{"&&", "&-&-", true},
|
||||
{"&&&-&", "&-&-&--&-", true},
|
||||
{"-&*&-", "-&-*&--", true},
|
||||
{"a&b", "a&-b", true},
|
||||
{"a&", "a&-", true},
|
||||
{"&b", "&-b", true},
|
||||
{"-a&", "-a&-", true},
|
||||
{"&b-", "&-b-", true},
|
||||
|
||||
// Unicode range
|
||||
{"\u0000", "&AAA-", true},
|
||||
{"\n", "&AAo-", true},
|
||||
{"\r", "&AA0-", true},
|
||||
{"\u001F", "&AB8-", true},
|
||||
{"\u0020", " ", true},
|
||||
{"\u0025", "%", true},
|
||||
{"\u0026", "&-", true},
|
||||
{"\u0027", "'", true},
|
||||
{"\u007E", "~", true},
|
||||
{"\u007F", "&AH8-", true},
|
||||
{"\u0080", "&AIA-", true},
|
||||
{"\u00FF", "&AP8-", true},
|
||||
{"\u07FF", "&B,8-", true},
|
||||
{"\u0800", "&CAA-", true},
|
||||
{"\uFFEF", "&,+8-", true},
|
||||
{"\uFFFF", "&,,8-", true},
|
||||
{"\U00010000", "&2ADcAA-", true},
|
||||
{"\U0010FFFF", "&2,,f,w-", true},
|
||||
|
||||
// Padding
|
||||
{"\x00\x1F", "&AAAAHw-", true}, // 2
|
||||
{"\x00\x1F\x7F", "&AAAAHwB,-", true}, // 0
|
||||
{"\x00\x1F\x7F\u0080", "&AAAAHwB,AIA-", true}, // 1
|
||||
{"\x00\x1F\x7F\u0080\u00FF", "&AAAAHwB,AIAA,w-", true}, // 2
|
||||
|
||||
// Mix
|
||||
{"a\x00", "a&AAA-", true},
|
||||
{"\x00a", "&AAA-a", true},
|
||||
{"&\x00", "&-&AAA-", true},
|
||||
{"\x00&", "&AAA-&-", true},
|
||||
{"a\x00&", "a&AAA-&-", true},
|
||||
{"a&\x00", "a&-&AAA-", true},
|
||||
{"&a\x00", "&-a&AAA-", true},
|
||||
{"&\x00a", "&-&AAA-a", true},
|
||||
{"\x00&a", "&AAA-&-a", true},
|
||||
{"\x00a&", "&AAA-a&-", true},
|
||||
{"ab&\uFFFF", "ab&-&,,8-", true},
|
||||
{"a&b\uFFFF", "a&-b&,,8-", true},
|
||||
{"&ab\uFFFF", "&-ab&,,8-", true},
|
||||
{"ab\uFFFF&", "ab&,,8-&-", true},
|
||||
{"a\uFFFFb&", "a&,,8-b&-", true},
|
||||
{"\uFFFFab&", "&,,8-ab&-", true},
|
||||
|
||||
{"\x20\x25&\x27\x7E", " %&-'~", true},
|
||||
{"\x1F\x20&\x7E\x7F", "&AB8- &-~&AH8-", true},
|
||||
{"&\x00\x19\x7F\u0080", "&-&AAAAGQB,AIA-", true},
|
||||
{"\x00&\x19\x7F\u0080", "&AAA-&-&ABkAfwCA-", true},
|
||||
{"\x00\x19&\x7F\u0080", "&AAAAGQ-&-&AH8AgA-", true},
|
||||
{"\x00\x19\x7F&\u0080", "&AAAAGQB,-&-&AIA-", true},
|
||||
{"\x00\x19\x7F\u0080&", "&AAAAGQB,AIA-&-", true},
|
||||
{"&\x00\x1F\x7F\u0080", "&-&AAAAHwB,AIA-", true},
|
||||
{"\x00&\x1F\x7F\u0080", "&AAA-&-&AB8AfwCA-", true},
|
||||
{"\x00\x1F&\x7F\u0080", "&AAAAHw-&-&AH8AgA-", true},
|
||||
{"\x00\x1F\x7F&\u0080", "&AAAAHwB,-&-&AIA-", true},
|
||||
{"\x00\x1F\x7F\u0080&", "&AAAAHwB,AIA-&-", true},
|
||||
|
||||
// Russian
|
||||
{"\u041C\u0430\u043A\u0441\u0438\u043C \u0425\u0438\u0442\u0440\u043E\u0432",
|
||||
"&BBwEMAQ6BEEEOAQ8- &BCUEOARCBEAEPgQy-", true},
|
||||
|
||||
// RFC 3501
|
||||
{"~peter/mail/\u53F0\u5317/\u65E5\u672C\u8A9E", "~peter/mail/&U,BTFw-/&ZeVnLIqe-", true},
|
||||
{"~peter/mail/\u53F0\u5317/\u65E5\u672C\u8A9E", "~peter/mail/&U,BTFw-/&ZeVnLIqe-", true},
|
||||
{"\u263A!", "&Jjo-!", true},
|
||||
{"\u53F0\u5317\u65E5\u672C\u8A9E", "&U,BTF2XlZyyKng-", true},
|
||||
|
||||
// RFC 2152 (modified)
|
||||
{"\u0041\u2262\u0391\u002E", "A&ImIDkQ-.", true},
|
||||
{"Hi Mom -\u263A-!", "Hi Mom -&Jjo--!", true},
|
||||
{"\u65E5\u672C\u8A9E", "&ZeVnLIqe-", true},
|
||||
|
||||
// 8->16 and 24->16 byte UTF-8 to UTF-16 conversion
|
||||
{"\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007", "&AAAAAQACAAMABAAFAAYABw-", true},
|
||||
{"\u0800\u0801\u0802\u0803\u0804\u0805\u0806\u0807", "&CAAIAQgCCAMIBAgFCAYIBw-", true},
|
||||
|
||||
// Invalid UTF-8 (bad bytes are converted to U+FFFD)
|
||||
{"\xC0\x80", "&,,3,,Q-", false}, // U+0000
|
||||
{"\xF4\x90\x80\x80", "&,,3,,f,9,,0-", false}, // U+110000
|
||||
{"\xF7\xBF\xBF\xBF", "&,,3,,f,9,,0-", false}, // U+1FFFFF
|
||||
{"\xF8\x88\x80\x80\x80", "&,,3,,f,9,,3,,Q-", false}, // U+200000
|
||||
{"\xF4\x8F\xBF\x3F", "&,,3,,f,9-?", false}, // U+10FFFF (bad byte)
|
||||
{"\xF4\x8F\xBF", "&,,3,,f,9-", false}, // U+10FFFF (short)
|
||||
{"\xF4\x8F", "&,,3,,Q-", false},
|
||||
{"\xF4", "&,,0-", false},
|
||||
{"\x00\xF4\x00", "&AAD,,QAA-", false},
|
||||
}
|
||||
|
||||
func TestEncoder(t *testing.T) {
|
||||
for _, test := range encode {
|
||||
out := utf7.Encode(test.in)
|
||||
if out != test.out {
|
||||
t.Errorf("UTF7Encode(%+q) expected %+q; got %+q", test.in, test.out, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
// Package utf7 implements modified UTF-7 encoding defined in RFC 3501 section 5.1.3
|
||||
package utf7
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
)
|
||||
|
||||
const (
|
||||
min = 0x20 // Minimum self-representing UTF-7 value
|
||||
max = 0x7E // Maximum self-representing UTF-7 value
|
||||
)
|
||||
|
||||
var b64Enc = base64.NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,")
|
||||
Reference in New Issue
Block a user