182 lines
3.7 KiB
Go
182 lines
3.7 KiB
Go
package imap
|
|
|
|
import (
|
|
"strings"
|
|
)
|
|
|
|
// Resp is an IMAP response. It is either a *DataResp, a
|
|
// *ContinuationReq or a *StatusResp.
|
|
type Resp interface {
|
|
resp()
|
|
}
|
|
|
|
// ReadResp reads a single response from a Reader.
|
|
func ReadResp(r *Reader) (Resp, error) {
|
|
atom, err := r.ReadAtom()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tag, ok := atom.(string)
|
|
if !ok {
|
|
return nil, newParseError("response tag is not an atom")
|
|
}
|
|
|
|
if tag == "+" {
|
|
if err := r.ReadSp(); err != nil {
|
|
r.UnreadRune()
|
|
}
|
|
|
|
resp := &ContinuationReq{}
|
|
resp.Info, err = r.ReadInfo()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
if err := r.ReadSp(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Can be either data or status
|
|
// Try to parse a status
|
|
var fields []interface{}
|
|
if atom, err := r.ReadAtom(); err == nil {
|
|
fields = append(fields, atom)
|
|
|
|
if err := r.ReadSp(); err == nil {
|
|
if name, ok := atom.(string); ok {
|
|
status := StatusRespType(name)
|
|
switch status {
|
|
case StatusRespOk, StatusRespNo, StatusRespBad, StatusRespPreauth, StatusRespBye:
|
|
resp := &StatusResp{
|
|
Tag: tag,
|
|
Type: status,
|
|
}
|
|
|
|
char, _, err := r.ReadRune()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
r.UnreadRune()
|
|
|
|
if char == '[' {
|
|
// Contains code & arguments
|
|
resp.Code, resp.Arguments, err = r.ReadRespCode()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
resp.Info, err = r.ReadInfo()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return resp, nil
|
|
}
|
|
}
|
|
} else {
|
|
r.UnreadRune()
|
|
}
|
|
} else {
|
|
r.UnreadRune()
|
|
}
|
|
|
|
// Not a status so it's data
|
|
resp := &DataResp{Tag: tag}
|
|
|
|
var remaining []interface{}
|
|
remaining, err = r.ReadLine()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resp.Fields = append(fields, remaining...)
|
|
return resp, nil
|
|
}
|
|
|
|
// DataResp is an IMAP response containing data.
|
|
type DataResp struct {
|
|
// The response tag. Can be either "" for untagged responses, "+" for continuation
|
|
// requests or a previous command's tag.
|
|
Tag string
|
|
// The parsed response fields.
|
|
Fields []interface{}
|
|
}
|
|
|
|
// NewUntaggedResp creates a new untagged response.
|
|
func NewUntaggedResp(fields []interface{}) *DataResp {
|
|
return &DataResp{
|
|
Tag: "*",
|
|
Fields: fields,
|
|
}
|
|
}
|
|
|
|
func (r *DataResp) resp() {}
|
|
|
|
func (r *DataResp) WriteTo(w *Writer) error {
|
|
tag := RawString(r.Tag)
|
|
if tag == "" {
|
|
tag = RawString("*")
|
|
}
|
|
|
|
fields := []interface{}{RawString(tag)}
|
|
fields = append(fields, r.Fields...)
|
|
return w.writeLine(fields...)
|
|
}
|
|
|
|
// ContinuationReq is a continuation request response.
|
|
type ContinuationReq struct {
|
|
// The info message sent with the continuation request.
|
|
Info string
|
|
}
|
|
|
|
func (r *ContinuationReq) resp() {}
|
|
|
|
func (r *ContinuationReq) WriteTo(w *Writer) error {
|
|
if err := w.writeString("+"); err != nil {
|
|
return err
|
|
}
|
|
|
|
if r.Info != "" {
|
|
if err := w.writeString(string(sp) + r.Info); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return w.writeCrlf()
|
|
}
|
|
|
|
// ParseNamedResp attempts to parse a named data response.
|
|
func ParseNamedResp(resp Resp) (name string, fields []interface{}, ok bool) {
|
|
data, ok := resp.(*DataResp)
|
|
if !ok || len(data.Fields) == 0 {
|
|
return
|
|
}
|
|
|
|
// Some responses (namely EXISTS and RECENT) are formatted like so:
|
|
// [num] [name] [...]
|
|
// Which is fucking stupid. But we handle that here by checking if the
|
|
// response name is a number and then rearranging it.
|
|
if len(data.Fields) > 1 {
|
|
name, ok := data.Fields[1].(string)
|
|
if ok {
|
|
if _, err := ParseNumber(data.Fields[0]); err == nil {
|
|
fields := []interface{}{data.Fields[0]}
|
|
fields = append(fields, data.Fields[2:]...)
|
|
return strings.ToUpper(name), fields, true
|
|
}
|
|
}
|
|
}
|
|
|
|
// IMAP commands are formatted like this:
|
|
// [name] [...]
|
|
name, ok = data.Fields[0].(string)
|
|
if !ok {
|
|
return
|
|
}
|
|
return strings.ToUpper(name), data.Fields[1:], true
|
|
}
|