Initial commit
This commit is contained in:
430
vendor/github.com/emersion/go-imap/read.go
generated
vendored
Normal file
430
vendor/github.com/emersion/go-imap/read.go
generated
vendored
Normal file
@@ -0,0 +1,430 @@
|
||||
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
|
||||
}
|
||||
|
||||
// Convert a field to a number.
|
||||
func ParseNumber(f interface{}) (uint32, error) {
|
||||
// Useful for tests
|
||||
if n, ok := f.(uint32); ok {
|
||||
return n, nil
|
||||
}
|
||||
|
||||
s, ok := f.(string)
|
||||
if !ok {
|
||||
return 0, newParseError("number is not a string")
|
||||
}
|
||||
|
||||
nbr, err := strconv.ParseUint(s, 10, 32)
|
||||
if err != nil {
|
||||
return 0, &parseError{err}
|
||||
}
|
||||
|
||||
return uint32(nbr), nil
|
||||
}
|
||||
|
||||
// Convert a field list to a string list.
|
||||
func ParseStringList(f interface{}) ([]string, error) {
|
||||
fields, ok := f.([]interface{})
|
||||
if !ok {
|
||||
return nil, newParseError("string list is not a list")
|
||||
}
|
||||
|
||||
list := make([]string, len(fields))
|
||||
for i, f := range fields {
|
||||
var ok bool
|
||||
if list[i], ok = f.(string); !ok {
|
||||
return nil, newParseError("string list contains a non-string")
|
||||
}
|
||||
}
|
||||
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("not a space")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Reader) ReadCrlf() (err error) {
|
||||
var char rune
|
||||
|
||||
if char, _, err = r.ReadRune(); err != nil {
|
||||
return
|
||||
}
|
||||
if char != cr {
|
||||
err = newParseError("line doesn't end with a CR")
|
||||
}
|
||||
|
||||
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 {
|
||||
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)
|
||||
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 {
|
||||
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 == listEnd || char == respCodeEnd {
|
||||
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 string, 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
|
||||
}
|
||||
|
||||
code, ok := fields[0].(string)
|
||||
if !ok {
|
||||
err = newParseError("response code doesn't start with a string atom")
|
||||
return
|
||||
}
|
||||
if code == "" {
|
||||
err = newParseError("response code is empty")
|
||||
return
|
||||
}
|
||||
|
||||
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(cr))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
info = strings.TrimSuffix(info, string(cr))
|
||||
info = strings.TrimLeft(info, " ")
|
||||
|
||||
var char rune
|
||||
if char, _, err = r.ReadRune(); err != nil {
|
||||
return
|
||||
}
|
||||
if char != lf {
|
||||
err = newParseError("line doesn't end with a LF")
|
||||
}
|
||||
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
|
||||
}
|
Reference in New Issue
Block a user