Fixing to have the proper version of go-imap from foxcpp.
This commit is contained in:
240
response.go
240
response.go
@@ -1,81 +1,181 @@
|
||||
package imap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// StatusResponseType is a generic status response type.
|
||||
type StatusResponseType string
|
||||
|
||||
const (
|
||||
StatusResponseTypeOK StatusResponseType = "OK"
|
||||
StatusResponseTypeNo StatusResponseType = "NO"
|
||||
StatusResponseTypeBad StatusResponseType = "BAD"
|
||||
StatusResponseTypePreAuth StatusResponseType = "PREAUTH"
|
||||
StatusResponseTypeBye StatusResponseType = "BYE"
|
||||
)
|
||||
|
||||
// ResponseCode is a response code.
|
||||
type ResponseCode string
|
||||
|
||||
const (
|
||||
ResponseCodeAlert ResponseCode = "ALERT"
|
||||
ResponseCodeAlreadyExists ResponseCode = "ALREADYEXISTS"
|
||||
ResponseCodeAuthenticationFailed ResponseCode = "AUTHENTICATIONFAILED"
|
||||
ResponseCodeAuthorizationFailed ResponseCode = "AUTHORIZATIONFAILED"
|
||||
ResponseCodeBadCharset ResponseCode = "BADCHARSET"
|
||||
ResponseCodeCannot ResponseCode = "CANNOT"
|
||||
ResponseCodeClientBug ResponseCode = "CLIENTBUG"
|
||||
ResponseCodeContactAdmin ResponseCode = "CONTACTADMIN"
|
||||
ResponseCodeCorruption ResponseCode = "CORRUPTION"
|
||||
ResponseCodeExpired ResponseCode = "EXPIRED"
|
||||
ResponseCodeHasChildren ResponseCode = "HASCHILDREN"
|
||||
ResponseCodeInUse ResponseCode = "INUSE"
|
||||
ResponseCodeLimit ResponseCode = "LIMIT"
|
||||
ResponseCodeNonExistent ResponseCode = "NONEXISTENT"
|
||||
ResponseCodeNoPerm ResponseCode = "NOPERM"
|
||||
ResponseCodeOverQuota ResponseCode = "OVERQUOTA"
|
||||
ResponseCodeParse ResponseCode = "PARSE"
|
||||
ResponseCodePrivacyRequired ResponseCode = "PRIVACYREQUIRED"
|
||||
ResponseCodeServerBug ResponseCode = "SERVERBUG"
|
||||
ResponseCodeTryCreate ResponseCode = "TRYCREATE"
|
||||
ResponseCodeUnavailable ResponseCode = "UNAVAILABLE"
|
||||
ResponseCodeUnknownCTE ResponseCode = "UNKNOWN-CTE"
|
||||
|
||||
// METADATA
|
||||
ResponseCodeTooMany ResponseCode = "TOOMANY"
|
||||
ResponseCodeNoPrivate ResponseCode = "NOPRIVATE"
|
||||
|
||||
// APPENDLIMIT
|
||||
ResponseCodeTooBig ResponseCode = "TOOBIG"
|
||||
)
|
||||
|
||||
// StatusResponse is a generic status response.
|
||||
//
|
||||
// See RFC 9051 section 7.1.
|
||||
type StatusResponse struct {
|
||||
Type StatusResponseType
|
||||
Code ResponseCode
|
||||
Text string
|
||||
// Resp is an IMAP response. It is either a *DataResp, a
|
||||
// *ContinuationReq or a *StatusResp.
|
||||
type Resp interface {
|
||||
resp()
|
||||
}
|
||||
|
||||
// Error is an IMAP error caused by a status response.
|
||||
type Error StatusResponse
|
||||
|
||||
var _ error = (*Error)(nil)
|
||||
|
||||
// Error implements the error interface.
|
||||
func (err *Error) Error() string {
|
||||
var sb strings.Builder
|
||||
fmt.Fprintf(&sb, "imap: %v", err.Type)
|
||||
if err.Code != "" {
|
||||
fmt.Fprintf(&sb, " [%v]", err.Code)
|
||||
// ReadResp reads a single response from a Reader.
|
||||
func ReadResp(r *Reader) (Resp, error) {
|
||||
atom, err := r.ReadAtom()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
text := err.Text
|
||||
if text == "" {
|
||||
text = "<unknown>"
|
||||
tag, ok := atom.(string)
|
||||
if !ok {
|
||||
return nil, newParseError("response tag is not an atom")
|
||||
}
|
||||
fmt.Fprintf(&sb, " %v", text)
|
||||
return sb.String()
|
||||
|
||||
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 := Atom(r.Tag)
|
||||
if tag == "" {
|
||||
tag = Atom("*")
|
||||
}
|
||||
|
||||
fields := []interface{}{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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user