getimap/vendor/github.com/emersion/go-imap/read.go
2020-03-10 13:57:28 +01:00

467 lines
8.9 KiB
Go

package imap
import (
"bytes"
"errors"
"io"
"strconv"
"strings"
)
const (
sp = ' '
cr = '\r'
lf = '\n'
dquote = '"'
literalStart = '{'
literalEnd = '}'
listStart = '('
listEnd = ')'
respCodeStart = '['
respCodeEnd = ']'
)
const (
crlf = "\r\n"
nilAtom = "NIL"
)
// TODO: add CTL to atomSpecials
var (
quotedSpecials = string([]rune{dquote, '\\'})
respSpecials = string([]rune{respCodeEnd})
atomSpecials = string([]rune{listStart, listEnd, literalStart, sp, '%', '*'}) + quotedSpecials + respSpecials
)
type parseError struct {
error
}
func newParseError(text string) error {
return &parseError{errors.New(text)}
}
// IsParseError returns true if the provided error is a parse error produced by
// Reader.
func IsParseError(err error) bool {
_, ok := err.(*parseError)
return ok
}
// A string reader.
type StringReader interface {
// ReadString reads until the first occurrence of delim in the input,
// returning a string containing the data up to and including the delimiter.
// See https://golang.org/pkg/bufio/#Reader.ReadString
ReadString(delim byte) (line string, err error)
}
type reader interface {
io.Reader
io.RuneScanner
StringReader
}
// ParseNumber parses a number.
func ParseNumber(f interface{}) (uint32, error) {
// Useful for tests
if n, ok := f.(uint32); ok {
return n, nil
}
var s string
switch f := f.(type) {
case RawString:
s = string(f)
case string:
s = f
default:
return 0, newParseError("expected a number, got a non-atom")
}
nbr, err := strconv.ParseUint(string(s), 10, 32)
if err != nil {
return 0, &parseError{err}
}
return uint32(nbr), nil
}
// ParseString parses a string, which is either a literal, a quoted string or an
// atom.
func ParseString(f interface{}) (string, error) {
if s, ok := f.(string); ok {
return s, nil
}
// Useful for tests
if a, ok := f.(RawString); ok {
return string(a), nil
}
if l, ok := f.(Literal); ok {
b := make([]byte, l.Len())
if _, err := io.ReadFull(l, b); err != nil {
return "", err
}
return string(b), nil
}
return "", newParseError("expected a string")
}
// Convert a field list to a string list.
func ParseStringList(f interface{}) ([]string, error) {
fields, ok := f.([]interface{})
if !ok {
return nil, newParseError("expected a string list, got a non-list")
}
list := make([]string, len(fields))
for i, f := range fields {
var err error
if list[i], err = ParseString(f); err != nil {
return nil, newParseError("cannot parse string in string list: " + err.Error())
}
}
return list, nil
}
func trimSuffix(str string, suffix rune) string {
return str[:len(str)-1]
}
// An IMAP reader.
type Reader struct {
MaxLiteralSize uint32 // The maximum literal size.
reader
continues chan<- bool
brackets int
inRespCode bool
}
func (r *Reader) ReadSp() error {
char, _, err := r.ReadRune()
if err != nil {
return err
}
if char != sp {
return newParseError("expected a space")
}
return nil
}
func (r *Reader) ReadCrlf() (err error) {
var char rune
if char, _, err = r.ReadRune(); err != nil {
return
}
if char == lf {
return
}
if char != cr {
err = newParseError("line doesn't end with a CR")
return
}
if char, _, err = r.ReadRune(); err != nil {
return
}
if char != lf {
err = newParseError("line doesn't end with a LF")
}
return
}
func (r *Reader) ReadAtom() (interface{}, error) {
r.brackets = 0
var atom string
for {
char, _, err := r.ReadRune()
if err != nil {
return nil, err
}
// TODO: list-wildcards and \
if r.brackets == 0 && (char == listStart || char == literalStart || char == dquote) {
return nil, newParseError("atom contains forbidden char: " + string(char))
}
if char == cr || char == lf {
break
}
if r.brackets == 0 && (char == sp || char == listEnd) {
break
}
if char == respCodeEnd {
if r.brackets == 0 {
if r.inRespCode {
break
} else {
return nil, newParseError("atom contains bad brackets nesting")
}
}
r.brackets--
}
if char == respCodeStart {
r.brackets++
}
atom += string(char)
}
r.UnreadRune()
if atom == "NIL" {
return nil, nil
}
return atom, nil
}
func (r *Reader) ReadLiteral() (Literal, error) {
char, _, err := r.ReadRune()
if err != nil {
return nil, err
} else if char != literalStart {
return nil, newParseError("literal string doesn't start with an open brace")
}
lstr, err := r.ReadString(byte(literalEnd))
if err != nil {
return nil, err
}
lstr = trimSuffix(lstr, literalEnd)
nonSync := strings.HasSuffix(lstr, "+")
if nonSync {
lstr = trimSuffix(lstr, '+')
}
n, err := strconv.ParseUint(lstr, 10, 32)
if err != nil {
return nil, newParseError("cannot parse literal length: " + err.Error())
}
if r.MaxLiteralSize > 0 && uint32(n) > r.MaxLiteralSize {
return nil, newParseError("literal exceeding maximum size")
}
if err := r.ReadCrlf(); err != nil {
return nil, err
}
// Send continuation request if necessary
if r.continues != nil && !nonSync {
r.continues <- true
}
// Read literal
b := make([]byte, n)
if _, err := io.ReadFull(r, b); err != nil {
return nil, err
}
return bytes.NewBuffer(b), nil
}
func (r *Reader) ReadQuotedString() (string, error) {
if char, _, err := r.ReadRune(); err != nil {
return "", err
} else if char != dquote {
return "", newParseError("quoted string doesn't start with a double quote")
}
var buf bytes.Buffer
var escaped bool
for {
char, _, err := r.ReadRune()
if err != nil {
return "", err
}
if char == '\\' && !escaped {
escaped = true
} else {
if char == cr || char == lf {
r.UnreadRune()
return "", newParseError("CR or LF not allowed in quoted string")
}
if char == dquote && !escaped {
break
}
if !strings.ContainsRune(quotedSpecials, char) && escaped {
return "", newParseError("quoted string cannot contain backslash followed by a non-quoted-specials char")
}
buf.WriteRune(char)
escaped = false
}
}
return buf.String(), nil
}
func (r *Reader) ReadFields() (fields []interface{}, err error) {
var char rune
for {
if char, _, err = r.ReadRune(); err != nil {
return
}
if err = r.UnreadRune(); err != nil {
return
}
var field interface{}
ok := true
switch char {
case literalStart:
field, err = r.ReadLiteral()
case dquote:
field, err = r.ReadQuotedString()
case listStart:
field, err = r.ReadList()
case listEnd:
ok = false
case cr:
return
default:
field, err = r.ReadAtom()
}
if err != nil {
return
}
if ok {
fields = append(fields, field)
}
if char, _, err = r.ReadRune(); err != nil {
return
}
if char == cr || char == lf || char == listEnd || char == respCodeEnd {
if char == cr || char == lf {
r.UnreadRune()
}
return
}
if char == listStart {
r.UnreadRune()
continue
}
if char != sp {
err = newParseError("fields are not separated by a space")
return
}
}
}
func (r *Reader) ReadList() (fields []interface{}, err error) {
char, _, err := r.ReadRune()
if err != nil {
return
}
if char != listStart {
err = newParseError("list doesn't start with an open parenthesis")
return
}
fields, err = r.ReadFields()
if err != nil {
return
}
r.UnreadRune()
if char, _, err = r.ReadRune(); err != nil {
return
}
if char != listEnd {
err = newParseError("list doesn't end with a close parenthesis")
}
return
}
func (r *Reader) ReadLine() (fields []interface{}, err error) {
fields, err = r.ReadFields()
if err != nil {
return
}
r.UnreadRune()
err = r.ReadCrlf()
return
}
func (r *Reader) ReadRespCode() (code StatusRespCode, fields []interface{}, err error) {
char, _, err := r.ReadRune()
if err != nil {
return
}
if char != respCodeStart {
err = newParseError("response code doesn't start with an open bracket")
return
}
r.inRespCode = true
fields, err = r.ReadFields()
r.inRespCode = false
if err != nil {
return
}
if len(fields) == 0 {
err = newParseError("response code doesn't contain any field")
return
}
codeStr, ok := fields[0].(string)
if !ok {
err = newParseError("response code doesn't start with a string atom")
return
}
if codeStr == "" {
err = newParseError("response code is empty")
return
}
code = StatusRespCode(strings.ToUpper(codeStr))
fields = fields[1:]
r.UnreadRune()
char, _, err = r.ReadRune()
if err != nil {
return
}
if char != respCodeEnd {
err = newParseError("response code doesn't end with a close bracket")
}
return
}
func (r *Reader) ReadInfo() (info string, err error) {
info, err = r.ReadString(byte(lf))
if err != nil {
return
}
info = strings.TrimSuffix(info, string(lf))
info = strings.TrimSuffix(info, string(cr))
info = strings.TrimLeft(info, " ")
return
}
func NewReader(r reader) *Reader {
return &Reader{reader: r}
}
func NewServerReader(r reader, continues chan<- bool) *Reader {
return &Reader{reader: r, continues: continues}
}
type Parser interface {
Parse(fields []interface{}) error
}